Cookpad近期微服务经验总结

译者注:

关于版权

本文首发于Cookpad公司技术博客《クックパッドにおける最近のMicroservices事例》,本译文已获得原作者基于禁止商业用途的许可,有转载需求请自行联系原作者,责任自负,因转载出现的任何问题皆与本站和本人无关。

关于Cookpad介绍

Cookpad是日本最大的在线菜谱分享公司,上市企业;这都不算啥,说到Rails,日本人没有不知道Cookpad的,可以说Cookpad将Rails用到了极致,相信他们在微服务方面的经验也能为我们带来帮助。

以下是正文


大家好,我是Cookpad技术部的吉川。

最近微服务这个概念很流行,其技术也逐渐变得系统化、结构化。另一方面就是关于微服务概念上或者抽象的内容比较多,很难让人对微服务有一个具体的印象。

Cookpad公司的技术博客在1年半之前已经对 Cookpad和微服务 进行了介绍。我们也将当时公司内部使用的工具Garage进行了 开源 ,现在Cookpad的系统已经比当时有了更大的发展。

所以,在前几天的 Microservices Casual Talks 上,我们也对Cookpad最近关于微服务的使用经验进行了分享。

这是当时的 演示幻灯片(日文)打不开的话自备工具

本文并不打算对微服务一些抽象难懂的概念进行解说,而是更专注于具体实例。本文应该会对那些理解一些微服务的基本概念,而实际中又不知道如何应用的人有很大帮助,因此我们决定通过本博客再来进行一下介绍。

服务粒度

大家常说微服务就是职责单一的服务,但是单一职责又需要具体到什么地步才算单一呢?Cookpad的服务主要分为3种类型。

用户服务

根据产品关注点的分类。一个根本原则是微服务需要根据业务领域来划分。Cookpad提供了包括美味料理、料理教室、料理视频、Cookpad博客、大家的咖啡等在内的众多服务。

尽管大多数服务都采用了Rails进行开发(也有一部分服务只有手机客户端),但是每个团队的特点都不一样。比如ES6的话既使用了React.js,也使用了CoffeeScript;有的团队使用了Rubocop,有的则没有使用。

由于产品的不同,目标用户也不太一样,因此我们可以很灵活地选择所使用的技术。如果能够很方便地使用其他Domain的model,则很容易产生协同效应。换句话说,即使最终用户看起来服务千变万化,但是从公司内部的其他服务来看,使用的是一个统一的Model。

View服务

Domain model相同但是展现形式不一样。

从形式上很像 BFF(Backend for frontends) 模式,但是由于Cookpad需要兼容各种各样的设备,因此同一个domain model需要以不同版本(这里的版本不是指alpha、beta这样的版本号,而是类似个人版、商业版这样的概念 – 译者注)的形式来提供。OEM版就是一个例子,提供的东西是一样的,但是却以不同版本的UI或者用户系统来提供服务。

共通基础服务

指的是为各种服务提供功能的服务。比如下面这些都是共通服务:

  • OAuth provider
  • 结算
  • Push通知、邮件服务
  • 活动
  • 各种日志
  • 敏感(个人)信息存储
  • 视频

服务之间的集成

RESTful Hypermedia API

微服务之间如何通信一直以来都是一个值得讨论的话题。虽然我们认为一般都会通过HTTP使用REST API,但是应该也有人会选择Protocol Buffers或者Thrift这样基于RPC的方式。

Cookpad微服务之间的通信采用了 基于Garage的RESTful Hypermedia API 方案。在之前的博客中我们已经介绍过Garage了,这里就略过了。在这篇博客中,我们会对一些Garage解决不了的问题进行介绍。

并行处理和容错

服务之间的集成越来越多,带来了两个问题。第一个问题是性能开销。一体化应用的话只需要连接到数据库把数据取回来就可以了,但是如果只是将这个功能采用REST API来实现的话,性能开销会变大。为了提高吞吐量,需要做很多努力,就微服务架构来说,调用方在发出请求之后,将会进入单纯的IO等待状态,非常适合采用并行处理的方式。因此对服务进行合适粒度的拆分,各服务的责任更独立,更容易采取并行处理的方式。

第二个问题是容错。如果某一服务阻塞了,等待该服务超时的客户端也会被阻塞,进而导致阻塞的连锁反应。好不容易将服务拆分了,当然不想轻易地出现这样的故障影响正常运行。

或者将请求并行来处理,或者进行重试,在超过一定数量的错误发生后停止对该服务的请求。我们采用 Expeditor 实现了该需求。熟悉这些需求的人可能已经想到了,这就是 Netflix/Hystrix 的Ruby版。当然现在还没有Hystrix那么多功能,但是一些基本功能,比如有依赖关系的Asynchronous Execution和Fallback、Retry、Circuit Breaker都已经实现了。

服务间的测试

REST API集成的问题之一就是API的兼容性。

通常在CI能够发挥作用的测试级别的测试中,发给其他服务的请求都是stub的形式,不能确认是否有不兼容该服务接口的变动混入。使用Protocol Buffers这样方式的话,由于需要共享proto文件,可能不会出什么问题。而使用REST API的方式进行集成的话,也有一种使用JSON Schema的方法。不过由于是Ruby + HTTP风格的文化,相比使用类型系统进行管理的开发风格,我们的工程师们更习惯于进行动态测试这种风格。这也是 Rack::VCR 产生的背景。但是使用各服务自己的CI来保证兼容性的话,如果服务之间的构建频率不一致,在Cookpad这样发布非常频繁的公司,很可能会出现在发布之后才能发现API不兼容的问题(即发布之后API提供方的API才检测到调用方的调用有问题 – 译者注)。因此我们决定转向 Consumer-Driven Contract testing ,现在已经迁移到 Pact 了。

这样,如果客户端破坏了API的兼容性,会被提供方(provider)的CI会检测到,从而防止不兼容的修改被发布。

服务间日志的事务关联

服务分开之后,日志追踪会变得很困难。特别是错误日志,当一个服务发生错误时,在调用方发现了错误的话,如果在被调用方不能追查到发生了什么错误的话,就很难找到问题的根本原因。一般的解决方式是为请求设置一个ID,以此来对各服务中的调用进行分析。特别是Rails默认就支持X-Request-Id HTTP头,没有ID的话也会自动设置一个。而且我们的各服务通过使用 GarageClient 进行集成,GarageClient会在发起请求时设置X-Request-Id并将这个ID传递下去。

Cookpad错误日志管理采用了 Sentry ,错误日志通过将请求ID作为标签来进行管理。

服务的运行环境、配置关联

随着服务的增多,给基础设施也带来了很大的压力。Cookpad本来使用的配置管理工具是Puppet,随着模块的增多,操作也变得复杂,同样的应用程序,有时还得区分是作为在线应用服务使用还是作为批处理作业使用来分开管理,总之会出现各种复杂的场景。

现在除了一部分巨型Rails应用程序以外,包括测试环境和生产环境在内,Cookpad几乎所有的应用都跑在Docker中。

构建pipeline

各服务的修改合并代码之后,就会启动CI,测试通过后构建新的Docker镜像。这个新的Docker镜像会自动部署到staging环境中。同时生产环境使用的也是这个镜像,而且没有为批处理作业和在线服务准备不同的环境,只需要拉取最新的镜像运行即可。当然这个镜像也可以在开发者本地运行。在Scale out或者进行渗透测试需要创建独立的运行环境的时候,采用Docker可以快速部署。

部署

Cookpad的Docker运行环境是ECS,实际部署的时候会将容器和ELB结合起来一起配置、注入环境变量等操作。Hako 就是一个这样能将部署过程中各种步骤连接起来的一个工具。有了Hako,在第一次部署的时候可以自动创建ELB,使用Route53设置域名指向,而以前这些工作都需要基础设施工程师单独一个个的去设置。利用这种机制,在生产环境下,一个应用程序对应一个ELB,在测试环境下,一个ELB则可以对应多个应用程序,进行非常灵活的设置。

配置管理

即使是同一个镜像,DB连接信息、API key等也不同,都依赖于具体的环境。Hako支持环境变量注入功能,这在一定程度上可以进行环境变量管理工作,但是需要安全处理的信息则使用了 etcenvetcvault 来进行管理。后端存储是etcd,不过由于etcd没有ACL管理功能,因此数据都采用了加密后存储的方式。etcenv 有一个UI工具 etcweb ,可以通过Web进行管理。

公司级别的支持

采用了微服务架构,团队也随着服务而独立。不同团队之间的协作方法,全体的决策方式等也逐渐发生着变化。

技术领域课题共有会

各团队的独立性越高,对其他不同团队的具体工作内容就越难以把握。各自业务固有的烦恼也会增加。由此会出现很多问题,比如有一些共通难题,如果团队内部认为只有自己才有这个烦恼的话,就不会将这个问题提到公共层面,而实际上隔壁的团队也许早已经解决了这个问题。因此Cookpad各团队的leader和基础设施、共通服务的工程师每个月都会聚到一起通过会议的方式来讨论一些共通话题。

在这个会议上各个团队会提出自己目前遇到的问题,如果一些问题已经有了解决方案,则将解决方案在公司范围内共享,如果没有解决方案,则会选择一个合适的团队专门负责来解决这个问题。此外对全局都可能带来影响的需求变更也会拿到会上讨论。这样就会发现很多问题,比如有些事自己认为不是问题,可是大家都在为此烦恼;或者自己认为大家都在使用的服务实际上并没有人用,自己却还努力在维护。

共通基础技术工程师

译者注:即公司内部开发公共服务的工程师

随着团队变得多样化,有的团队有擅长共通基础的成员,有的则没有;有的团队新产品较多,所以对公司内部最新的共通基础技术都很了解,而有的团队则对公司的共通基础技术认识不足,甚至还有重复制造轮子的现象。也有一些团队根本没有精力去对共通基础技术进行改善。

所以Cookpad将共通基础团队的成员分配给各个服务,作为各个团队的共通基础技术工程师。一个人对应多个服务,而不是专属于某一服务团队,所以他们可以称为是团队的外部共通基础技术工程师。共通基础技术工程师使用服务团队的协作(Chat)工具,谈论各种话题,有时候也全身心投入开发环境的准备工作中。

总结

本文我们对Cookpad近期在微服务方面的使用心得进行了介绍。Docker带来的可移植性给公司内部的开发风格带来了很大的改变,加速了微服务的落地。同时随着Pact等工具的出现以及公司层面的支持,Cookpad的开发风格在这一年有了显著的进步。

有了微服务各个团队也更能将精力集中在本职工作上。集中精力在产品上,也就是相当于集中精力在用户身上一样。根据产品的不同选择最合适的技术,团队成员对自己的产品负责,可以自己主导产品开发。

这里我们只是对Cookpad中微服务的概要进行了介绍,后面我想可能还会带来对Hako或者Pact等工具的介绍。