【微服务架构设计模式】加速加速
实现微服务架构并不是你的目标。你的目标是加速大型复杂应用程序的开发。
更新历史
- 2022.12.17:完成初稿
读后感
这本书针对微服务给出了一张比较完整的地图,这个方式是我非常喜欢的,后面可以按图索骥,快速落地并逐步找到最合适自己团队的方法。
读书笔记
前言
- 要记住微服务不是解决所有问题的万能“银弹”
- 编写整洁的代码和使用自动化测试至关重要,这是现代软件开发的基础
- 关注微服务的本质,即服务的分解和定义,而不是技术,如容器和其他工具
- 确保你的服务松耦合,并且可以独立开发、测试和部署,不要搞成分布式单体,那将会是巨大的灾难
- 不能只是在技术上采用微服务架构。拥抱 DevOps 的原则和实践,在组织结构上实现跨职能的资质团队,这必不可少
最重要的:实现微服务架构并不是你的目标。你的目标是加速大型复杂应用程序的开发。
逃离单体地狱
早期,应用程序相对较小,单体架构具有以下好处:
- 应用的开发很简单
- 易于对应用程序进行大规模的更改
- 测试相对简单直观
- 部署简单明了
- 横向扩展不费吹灰之力
随着时间的推移,开发、测试、部署和扩展都会变得更加困难,因为:
- 过度的复杂性会吓退开发者
- 开发速度缓慢
- 从代码提交到实际部署的周期很长,而且容易出问题
- 难以扩展
- 交付可靠的单体应用是一项挑战
- 需要长期以来某个可能已经过时的技术栈
而微服务架构可以提供如下好处:
- 使大型的复杂应用程序可以持续交付和持续部署
- 每个服务都相对较小并容易维护
- 服务可以独立部署
- 服务可以独立扩展
- 微服务架构可以实现团队的自治
- 更容易实验和采纳新的技术
- 更好的容错性
当然,微服务架构也有显著的弊端:
- 服务的拆分和定义是一项挑战
- 一门艺术
- 如果拆分出错,就构建出一个分布式的单体应用,坏上加坏
- 分布式系统带来的各种复杂性,是开发、测试和部署变得困难
- 自动化部署工具
- 产品化的 PaaS 平台
- Docker 容器编排平台
- 当部署跨越多个服务的功能时需要谨慎地协调更多开发团队
- 开发者需要思考到底应该在应用的什么阶段使用微服务架构
为了更好去设计微服务架构,我们需要一些模式,即针对特定上下文中发生的问题的可重用解决方案。常用的模式结构为:
- 需求(Forces):必须解决的问题
- 必须按照优先级排序
- 结果上下文(Resulting context):采用模式后可能带来的后果
- 好处:这个模式的好处和它解决了什么需求
- 弊端:这个模式的弊端和它没有解决哪些需求
- 问题:使用这个模式所引入的新问题
- 相关模式(Related patterns):5 种不同类型的模式和模式之间关系
- 前导(Predecessor):催生这个模式的需求的模式。例如,微服务架构模式是除单体架构模式以外整个模式语言中所有模式的前导模式。
- 后续(Successor):用来解决当前模式引入的新问题的模式。流入,如果采纳了微服务架构模式,就需要一系列后续的模式来解决诸如服务发现、断路器等微服务带来的新问题。
- 替代(Alternative):当前模式的替代模式,提供了另外的解决方案。例如,单体架构和微服务架构就是互为替代的模式,他们都是应用的架构风格,可以选择其一。
- 泛化(Generalization):针对一个问题的一般性解决方案
- 特化(Specialization):针对特定模式的具体解决方案
服务拆分的相关模式
通信模式
数据一致性 - Saga 模式
数据查询模式
部署模式
可观测性模式:
- 健康检查 API
- 日志聚合
- 分布式追踪
- 异常跟踪
- 应用指标
- 审计日志
自动化测试模式:
- 消费端驱动的契约测试:验证服务满足客户端所期望的功能
- 消费端契约测试:验证服务的客户端可以正常与服务通信
- 服务组件测试:在隔离的环境中测试服务
服务的拆分策略
- 架构决定了软件的各种非功能性因素,比如可维护性、可测试性、可补属性和可扩展性,它们会直接影响开发速度
- 微服务架构是一种架构风格,它给应用程序带来了更高的可维护性、可测试性、可部署性和可扩展性(DataOps 的逻辑是一样的)
- 微服务中的服务是根据业务需求进行组织的,按照业务能力或者子域,而不是技术上的考量
- 有两种分解模式:
- 按业务能力分解,起源于业务架构
- 基于领域驱动设计的概念,通过子域进行分解
- 通过应用 DDD 并为每个服务定义单独的领域模型来消除上帝类,正是上帝类引起了阻碍分解的交织依赖项
微服务架构中的进程间通信
- 微服务架构是一种分布式架构,因此进程间通信起着关键作用
- 仔细管理服务 API 的演化至关重要。向后兼容的更改是最容易进行的,因为它们不会影响客户端。如果对服务的 API 进行重大更改,通常需要同时支持旧版本和新版本,直到客户端升级为止
- 有许多进程间通信技术,每种技术都有不同的利弊。一个关键的设计决策是选择同步远程过程调用模式或异步消息模式。基于同步远程过程调用的协议(如 REST)是最容易使用的。但是,理想情况下,服务应使用异步消息进行通信,以提高可用性
- 为了防止故障通过系统层层蔓延,使用同步协议服务的客户端必须设计成能够处理局部故障,这些故障是在被调用的服务停机或表现出高延迟时发生的。特别是,它必须在发出请求是使用超时,限制未完成请求的数量,并使用断路器模式来避免调用失败的服务
- 使用同步协议的架构必须包含服务发现机制,以便客户端确定服务实例的网络位置
- 设计基于消息的架构的一种好方法是使用消息和通道模型,它抽象底层消息系统的细节。让后,你可以将该设计映射到特定的消息基础结构,该基础结构通常基于消息代理
- 使用消息机制的一个关键挑战是以原子化的方式同时完成数据库更新和发布消息。一个好的解决方案是使用事务性发件箱模式,并首先奖消息作为数据库事务的一部分写入数据库。然后,一个单独的进程使用轮询发布者模式或事务日志拖尾模式从数据库中检索消息,并将其发布给消息代理。
微服务架构中的业务逻辑设计
- 事务脚本模式通常是实现简单业务逻辑的的好方法。但是在实现复杂的业务逻辑时,应该考虑使用面向对象的领域模型模式。
- 设计服务的业务逻辑的好方法是使用 DDD 聚合。DDD 聚合很有用,因为它们把领域模型模块化,消除了服务之间对象的直接引用,并确保每个 ACID 事务都在服务内。
- 创建或更新聚合时应发布领域事件。领域事件具有广泛的用途。
使用事件溯源开发业务逻辑
- 事件溯源奖聚合作为一系列事件持久化保存。每个事件代表聚合的创建或状态更改。应用程序通过重放事件来重建聚合的当前状态。事件溯源保留领域对象的历史记录,提供准确的审计日志,并可靠地发布领域事件。
- 快照通过减少必须重放的事件数来提高性能。
- 事件存储在事件存储库中,该存储库是数据库和消息代理的混合。当服务在事件存储库中保存事件时,它会将事件传递给订阅者。
- 使用事件溯源的一个挑战是处理事件的演变。应用程序在重放事件时可能必须处理多个事件版本。一个好的解决方案是使用向上转换,当事件存储库加载时,它会将事件升级到最新版本
- 事件溯源可以很容易实现基于协同的 Saga。服务具有事件处理程序,用于监听基于事件溯源的聚合发布的事件。
- 我们也可以使用事件溯源技术实现 Saga 编排器。你可以编写专门使用事件存储库的应用程序。
在微服务架构中实现查询
- 实现从多个服务检索数据的查询具有挑战性,因为每个服务的数据都是私有的
- 有两种方法可以实现这些类型的查询:API 组合模式和命令查询职责隔离(CQRS)模式
- 从多个服务获取数据的 API 组合模式是实现查询的最简单方法,应尽可能使用
- API 组合模式的局限性是某些复杂查询需要大型数据集的低效内存连接
- 使用视图数据库实现查询的 CQRS 模式功能更加强大,但实现起来更复杂
- CQRS 视图模块必须程序并发更新以及检测和丢弃重复事件
- CQRS 有助于改善问题隔离,服务不必为自己拥有的数据实现查询功能
- 客户必须处理 CQRS 视图的最终一致性
外部 API 模式
网关模式很重要
微服务架构中的测试策略
- 自动化测试是快速、安全地交付软件的重要基石。更重要的是,由于微服务架构固有的复杂性,要从微服务架构中充分受益,必须实现自动化测试。
- 测试的目的是验证被侧系统(SUT)的行为。在这个定义中,系统是一个泛指,意味着被测试的软件元素。
- 简化和加快测试的一个好方法是使用测试替身。测试替身是一个模拟被测系统依赖项的行为的对象。有两种类型的测试替身:桩和模拟。桩是一个测试替身,它将值返回给被测系统。模拟也是一个测试替身,由测试用来验证被测系统是否正确调用依赖
- 使用测试金字塔可确定将测试工作重点放在服务的哪个部分。大多数测试应该是快速、可靠且易于编写的单元测试。必须尽量减少端到端测试的数量,因为它们写入速度慢、脆弱且耗时。
- 使用契约作为示例消息来驱动服务之间交互的测试。编写测试以验证两个服务的适配器是否符合契约,而不是编写运行两个服务及其传递依赖关系的慢速测试
- 编写组件测试以通过其 API 验证服务的行为。应该通过单独测试服务来简化和加速组件测试,使用桩来解决其依赖项。
- 编写用户旅程测试,以最大限度地减少端到端测试的数量,这些测试缓慢、脆弱又耗时。用户旅程测试模拟用户在应用程序中的旅程,并验证相对较大的应用程序功能片段的高级行为。因为测试很少,所以每次测试开销的数量(例如测试设置)被最小化,这就加快了测试速度。
部署微服务应用
- 你应该选择支持服务要求的最轻量级部署模式。按以下顺序评估选项:Serverless、容器、虚拟机和特定于语言的程序包
- Serverless 部署不适合每项服务,因为长尾延迟和使用基于事件/请求的编程模型的要求
微服务架构的重构策略
绞杀模式
- 将微服务引入架构的一个好方法是将新功能作为服务实现。这样做可以使你使用现代技术和开发过程快速轻松地开发功能。这是快速展示迁移到微服务价值的好方法。
- 打破单体的主要方法是逐步将功能从单体转移到服务中。重点是提取提供最大利益的服务。