背景
假设你在开发一个服务端应用。该应用必须支持各种各样的客户端,包括桌面浏览器、手机浏览器和本地手机应用。应用可能也需要公开部分API供第三方使用,还可能与其他应用通过web service或消息代理(message broker)相集成。应用执行业务逻辑来处理请求(HTTP请求或者消息);访问数据库;与其他系统交换消息;并返回HTML/JSON/XML类型的响应。
应用或是多层架构或是六角架构,并且包含多种类型的组件:
- 表示组件(Presentation components) - 响应处理HTTP请求,并返回HTML或JSON/XML(对于web service API)
- 业务逻辑(Business logic) - 应用的业务逻辑
- 数据库访问逻辑(Database access logic) - 数据访问对象用于访问数据库
- 应用集成逻辑(Application integration logic) - 消息层,如基于Spring的集成
这些逻辑组件分别响应应用中不同的功能模块。
问题
应用的部署架构是什么?
动机
- 该应用由一个开发者团队在维护
- 团队新成员必须快速上手
- 应用应该易于理解和修改
- 你想对应用进行持续集成
- 你必须在多台机器上部署多份应用的拷贝,以满足可伸缩性和可用性的要求
- 你想使用新技术(框架、编程语言等)
解决方案
通过采用伸缩立方(Scale Cube)(特别是y轴方向上的伸缩)来架构应用,将应用按功能分解为一组相互协作的服务的集合。每个服务实现一组有限并相关的功能。比如,一个应用可能包含订单管理服务,客户管理服务等。
服务间通过HTTP/REST等同步协议或AMQP等异步协议进行通讯。
服务独立开发和部署。
每个服务为了与其他服务解耦,都有自己的数据库。必要时,数据库间的一致性通过数据库复制机制或应用级事件来维护。
举例
我们假设你在构建一个电子商务应用,应用从客户接收订单,验证库存和可用额度,并派送订单。应用包含多个组件,包括StoreFrontUI,用来实现用户接口,以及一些后台服务,用于检测信用额度、维护库存和派送订单。
应用作为一组服务部署。
结果
这个方案有一些好处:
- 每个微服务都相对较小
- 易于开发者理解
- IDE反应更快,开发者更高效
- web容器启动更快,开发者更高效,并提升了部署速度
- 每个服务都可以独立部署 - 易于频繁部署新版本的服务
- 易于伸缩开发组织结构。你可以对多个团队的开发工作进行组织。每个(双披萨[1])团队负责单个服务。每个团队可以独立于其他团队开发、部署和伸缩服务。
- 提升故障隔离(fault isolation)。比如,如果一个服务存在内存泄露,那么只有该服务受影响,其他服务仍然可以处理请求。相比之下,一体架构的一个出错组件可以拖垮整个系统。
- 每个服务可以单独开发和部署
- 消除了任何对技术栈(technologh stack)的长期投入(long-term commitments)
这个方案也有一些缺点:
- 开发者要处理分布式系统的额外复杂度。
- 开发者工具/IDE是面向构建一体应用的,并没有显示提供对开发分布式应用的支持
- 测试更加困难
- 开发者需要实现服务间通信机制
- 不使用分布式事务实现跨服务的用例更加困难
- 实现跨服务的用例需要团队间的细致协作
- 生产环境的部署复杂度。对于包含多种不同服务类型的系统,部署和管理的操作复杂度仍然存在。
- 内存消耗增加。微服务架构使用NxM个服务实例来替代N个一体应用实例。如果每个服务运行在自己独立的JVM(或类似)上,通常有必要对实例进行隔离,对这么多运行的JVN,就有M倍的开销。另外,如果每个服务运行在独立的VM(如EC2实例),如Netflix,开销会更高。
挑战
什么时候使用微服务架构
使用该方法的一个挑战就是决定何时使用才合理。在开发应用的初期,你通常不会遇到这种方法试图解决的问题。而且,使用这个精细、分布式的架构将会拖慢开发进度。对初创公司,这是个严重问题,因为它们的最大挑战通常是如何快速发展业务模型及相关应用。使用Y轴切分使快速迭代更加困难。但是之后,当挑战变成如何伸缩,你需要使用功能分解将一体应用切分为一组服务时,混乱的依赖关系可能使之变得困难。
如何拆分应用到服务
另一个挑战是如何将系统分隔为微服务。这是个技术活,但有些策略可能有帮助。
- 按业务能力分解,定义与业务能力对应的服务。
- 按领域驱动设计的子域分解。
- 按动词或用例分解并定义负责特定操作的服务。例如,负责装运完整订单的运输服务。
- 通过定义负责对给定类型的实体/资源执行所有操作的服务,按名词或资源进行分解。例如,负责管理用户帐户的帐户服务。
理论上,每个服务应该只承担很小的职责。Bob Martin(大叔)讲过使用单一职责原则(SRP)来设计类。SRP定义类的职责作为变化的原因,而且类应该只有一个变化的原因。使用SRP来设计服务也是合理的。
另一个有助于服务设计的类比是Unix实用工具的设计方法。Unix提供大量的实用工具如grep、cat和find。每个工具只做一件事,通常做得非常好,并且可以跟其他工具使用shell脚本组合来执行复杂任务。
如何保持数据一致性
为了确保松耦合,每个服务都有自己的数据库。维护服务之间的数据一致性是一个挑战,因为对于许多应用程序来说,两阶段提交/分布式事务不是一个选项。应用程序必须使用SAGA模式。服务在其数据更改时发布事件。其他服务使用该事件并更新其数据。有几种可靠更新数据和发布事件的方法,包括事件源和事务日志跟踪。
如何实现查询
另一个挑战是实现需要检索多个服务拥有的数据的查询。
API组合和命令查询责任分离(CQR)模式。
相关模式
有许多与微服务模式相关的模式。单片架构是微服务架构的替代方案。其他模式解决了应用微服务体系结构时会遇到的问题。如API网关模式定义了客户端如何访问服务。
- 分解模式
- 按业务能力分解
- 按子域分解
- 每个服务模式的数据库描述了每个服务如何拥有自己的数据库,以确保松耦合。
- API网关模式定义了客户机如何访问微服务体系结构中的服务。
- 客户端发现和服务器端发现模式用于将客户端请求路由到微服务体系结构中的可用服务实例。
- 消息传递和远程过程调用模式是两种不同的服务通信方式。
- 每个主机的单个服务和每个主机的多个服务模式是两种不同的部署策略。
- 交叉关注模式:微服务机箱模式和外部化配置
- 测试模式:服务组件测试和服务集成契约测试
- 断路器
- 访问令牌
- 可观测性模式:
- 日志聚合
- 应用程序度量
- 审核日志记录
- 分布式跟踪
- 异常跟踪
- 健康检查API
- 记录部署和更改
- 用户界面模式:
- 服务器端页面片段组合
- 客户端UI组成
著名案例
大多数大规模的web站点,如 Netflix, Amazon和eBay都从一体架构转变为微服务架构。
Netflix是个非常受欢迎的视频流服务提供商,占有多达30%的互联网流量,它有着大规模、基于服务的架构。他们每天处理800+不同类型设备超过10亿次视频流API的请求。每个API可以展开成平均6次对后端服务的调用。
Amazon.com原有个两层架构。为了伸缩,他们迁移到一个包含上百个后端服务的基于服务的架构。调用这些服务的应用中包括实现Amazon.com网站和web service API的应用。Amazon.com网站应用调用100-150个服务来获取数据用于构建网页。
拍卖网站ebay.com也从一体架构发展成基于服务的架构。应用层包含多个独立的应用。每个应用实现特定功能模块(如购买或销售)的业务逻辑。每个应用使用X轴的分隔,有些应用如搜索,使用Z轴分隔。Ebay.com也对数据库层采用X,Y,Z的组合伸缩方式。