微服务架构设计模式


date: 2023-07-25

静态代码类型:在编译时报错 [Java,Go,C,C++] 动态代码类型:在运行时报错 [Python, JS]

第一章:为什么用微服务

  • 单体的好处和坏处

    • 好处

      • 应用的开发很简单

      • 易用对应用程序进行大规模的更改

      • 测试相对简单直观

      • 部署简单明了: 将jar包部署到web server

      • 横向扩展简单: 部署多个web server使用负载均衡器进行调度

    • 坏处

      • 过度的复杂性会吓退开发者

      • 开发速度缓慢

      • 代码提交到实际部署的周期很长,而且容易出问题

      • 难以扩展: 不能单模块扩展

      • 交付可靠的单体应用是一项挑战: 大型单体无法进行全面测试

      • 需要长期依赖某个可能已经过时的技术栈

  • 微服务的好处和坏处

    • 好处

      • 使大型的复杂应用程序可以持续交付和持续部署

      • 每个服务都相对较小并容易维护

      • 服务可以独立部署

      • 服务可以独立扩展

      • 微服务架构实现团队的自治

      • 更容易实验和采纳新的技术: 影响范围小,可以尝试新技术

      • 更好的容错性

    • 坏处

      • 服务的拆分和定义是一项挑战

      • 分布式系统带来的各种复杂性,使开发、测试和部署变得更困呐

      • 当部署跨多个服务的功能时需要更谨慎地协调更多开发团队

      • 开发者需要思考到底应该在应用的什么阶段使用微服务架构

第二章:服务拆分策略

  • 架构分解

    • 系统操作: 在有详细的需求文档后,分析出抽象的领域模型,然后定义系统行为,从行为故事中剥离出实体,建立实体间的关联

    • 业务模型:由业务决定服务拥有的能力,分析出业务能力(能创造价值的部分),分析出实体/服务,实现能力(功能)

    • 领域模型(划分子域): 由领域决定服务拥有的能力,分析子域(实体/服务),限界上下文(子域行为)

    • 按单一原则和闭包原则来拆分微服务,每个服务只关心一件(类)事,所有该事件的改动都在同一个包中(内聚)。

  • 微服务难点

    • 网络延迟

    • 数据一致性

    • 服务通信的代价

    • 服务间低耦合的代价: 服务可能需要冗余其他服务的数据 (如:配送服务需要订单信息,可能在delivery实体中包含了order number信息)

第三章:进程通信

  • 服务交互考量

    • 客户端与服务端 1:1

      • 请求/响应

      • 请求/异步响应

      • 单向通知:客户端只管发,不关心返回

    • 客户端与服务端 1:*

      • 发布/订阅

      • 发布/异步响应: 对事件感兴趣服务端发起回调

  • api版本组成

    • Major:对api进行不兼容更改

    • Minor:对api进行向后兼容的增强

    • Patch:对api进行向后兼容的错误修复时

  • api同步异步的考量

    • 同步

      • REST API

        • 成熟度模型

          • 0: 只使用POST发起请求,每个请求指明需要执行的操作

          • 1: 引入资源的概念

          • 2: 使用HTTP动词来执行操作,如GET获取,POST创建,PUT更新

          • 3: HATEOAS,通过GET请求获取到资源信息可执行的操作,免去客户端硬编码url

        • 优点

          • 易用,简化架构

          • 支持请求/响应方式通信

          • HTTP对防火墙友好

        • 缺点

          • 只支持请求/响应方式通信

          • 客户端必须知道服务实例的位置(URL)

          • 单个请求获取多个资源具有挑战性,且有时很难将复杂的逻辑用简单的URL与HTTP动词表达

      • gRPC

        • 使用protocol buffers作为消息格式

        • 好处

          • 高效紧凑的进程间通信机制,尤其在交换大量消息时

          • 支持双向流

          • 解耦服务端和客户端语言,可以各用各的

        • 坏处

          • 相比REST,gRPC需要做更多工作

      • 服务可用性保证

        • 服务保护:超时,熔断,降级

        • 服务发现:引入第三方(Nacos,Eureka..)来实现服务发现+负载均衡,或者k8s中的dns

    • 异步

      • 交互方式

        • 单向通知

        • 发布/订阅

        • 发布/异步响应

      • 实现

        • 有代理[MQ]

          • 好处

            • 解耦服务

            • 消息缓存

          • 坏处

            • 性能瓶颈

            • 维护高可用性

            • 重复消费:维护messageId/幂等消费来解决

            • 顺序消费: kafka-partition,或者

            • 分布式事务问题:轮询消息表/事务日志拖尾[同步binlog到MQ-开源:Eventuate Tram]

        • 无代理[ZeroMQ规范]

          • 好处

            • 不需要引入第三者

          • 坏处

            • 与同步的坏处相似

            • 消息持久化问题

第四章:使用Saga管理事务

  • 分布式事务

    • 原因: 微服务中完成一个操作,可能需要不同的服务共同完成,但是为了保证一致性,必须保证操作是全部失败或者全部成功的

    • 从CAP定理中知道,一个服务只能从中取两者,不得不放弃强一致性,从而取可用性。

  • Saga

    • 是一种再微服务架构中维护数据一致性的机制,避免分布式事务带来的问题。(是一种机制,不是具体技术方案)

    • 一个Saga表示需要更新多个服务中数据的一个系统操作,Saga由一连串的本地事务组成,每个本地事务都有自己私有的数据库( 单独服务)

    • 实现方案 [将Saga的决策和执行顺序逻辑分布在 where?]

      • 协同式:每个参与方的实现中 [可以异步消息事件]

        • 好处:实现简单,松耦合

        • 坏处:操作不易理解,服务依赖关系复杂,消息双方绑定[耦合]了[消息定义变,双方都要变]

      • 编排式:单独的一个编排器中 [业务逻辑处理器]

        • 好处:依赖关系简单,较少耦合,关注点隔离,简化业务逻辑:都在编排器中处理成功失败的逻辑了

        • 坏处:编排中存在过多的业务逻辑

一致性由ACD保证,不包括隔离性。 多个分布式事务影响同一个数据,由于没有隔离性,导致不同顺序的成功失败可能由不同的结果

  • 缺少隔离性的Saga

    • 异常

      • 数据丢失: 一个Saga直接更新覆盖了另一个Saga所作的更改

      • 脏读: 读取尚未完成的saga所更新的数据

      • 模糊或不可重复读:一个saga两次读取的数据不一样

    • 解决方案

      • 语义锁: 在变化状态时,增加一个state,其他saga在变更数据时判断state是否满足条件

      • 交换式更新: 将更新操作设计为可交换的, 既不关心谁先更新,只关心终态

      • 悲观视图: 重排序saga的步骤,以达到降低某些风险的效果

      • 重读值: 在更新是判断值是否改变[数据库乐观锁]

      • 版本文件: 将操作记录,并在执行后续操作做判断是否需要跳过

      • 业务风险评级: 基于风险的评级来决定使用策略,低风险使用saga,高风险使用强一致的分布式事务

第五章:微服务架构中的业务逻辑设计

  • 业务逻辑组织模式

    • 基于过程的事务模式 [简单的业务可以用]

    • 基于对象的领域模式 [DDD 领域驱动设计]

    • 基于DDD聚合: [将相关联的边界聚合到同一领域,如Order,包含了OrderItem,OrderCharge,OrderTimeline...]

      • 关键在于识别聚合

      • 不同领域间使用主键来关联,而不是保存整个实体

      • 发布领域事件,而降低耦合

第六章:使用事件溯源开发业务逻辑

  • 事件溯源

    • 解决什么问题

      • 保留事件历史, 可以重放

    • 怎么做

      • 发布领域事件: 如订单,需要发布订单状态变化的所有事件

    • 有什么风险

      • 并发更新: 使用乐观锁预防

      • 事件结构随着业务变化而变化

Eventuate Saga + 事件溯源实践案例

第七章 在微服务架构中实现查询

  • 问题: 想要查找不同服务中的数据,并不容易

  • 解决方案

    • api组合: 调用方聚合所有服务的数据后,过滤出自己需要的

      • 难点

        • 不同服务需要提供查询接口 + 确定聚合地方 + 正确的聚合逻辑 + 内存消耗

        • 多次调用产生的延时 + 可用性低[一个服务报错,功能就失效了] + 缺少事务一致性

    • 查询命令职责隔离: 创建一个视图来存储会用到的所有数据

      • 难点

        • 构建视图数据图,需要同步所有其他服务的数据[可以通过监听事件]

        • 维护数据[并发]更新的复杂度,保持一致性

  • 数据存储选择

    • 关系型(MySQL): 支持事务,索引查询

    • 非关系型(MongoDB,Redis): JSON化文档格式存储,无事务

第八章 外部 API 模式

  • 问题

    • 交互页面需要获取多次api来组装数据,网络耗时

    • 不同客服端可能用不同的交互协议,加重转换负担

    • 有客户端的应用,在不要求强制升级时,需要考虑版本兼容性问题

    • 如果有对外暴露给三方的接口,可能需要考虑永远的向后兼容问题,负担重

  • 方案

    • 使用 API Gateway

      • 在这层做协议统一,聚合api,路由转发,熔断,限流,验证等

      • gateway分两层,一层对外api定义层,一层对内api处理层

      • 问题:谁来维护它

        • api定义层 由不同客户端的团队自行维护,增加团队自治

        • api处理层 由api gateway团队维护

      • 问题:职责不明确,api gateway团队需要处理的事情很多

    • 使用后端前置模式

      • 该模式是 API Gateway 模式的一种优化, 各个团队自行维护自己的 api定义层 和 api处理层

    增加一个组件,对系统多一分风险,但是有些风险是值得的。

  • Gateway 注意事项

    • 扩展性: I/O 密集型 / CPU 密集型 ; 同步异步 / 阻塞非阻塞 选择

    • 可用性: 集群部署,服务发现,熔断下线

  • Gateway 功能

    • 路由规则构建

    • HTTP代理功能,HTTP头信息转发等

    • 可扩展:身份验证 熔断降级 服务发现

  • GraphQL:一个基于图形的查询语言框架

    • 编写面向图形的模式来描述服务端支持的查询和数据模型, 通过编写检索数据的解析器,将该模式映射到服务。 [由于频繁解析,性能低,可考虑加缓存]

    • 客户端可以指定服务端需要返回的参数,这让多客户端使用同一个可变API成为可能。

第九章 微服务架构中的测试策略 (上) / 第十章 (下)

  • 手动测试

    • 由QA团队手动测试所有功能

  • 自动化测试

    • 由自动化测试团队编写测试用例来自动测试

    • 测试步骤:

      • 设置环境

      • 执行测试

      • 验证结果

      • 清理环境

  • 模拟 & 桩

    • 很相似,但有些不同;桩代替依赖项来向被测系统发送调用的返回值,模拟用来验证被测试系统是否正确调用依赖项

  • 自动化测试框架

    • Junit

    • Mockito

  • 测试类型 [TDD 测试驱动开发]

    • 单元测试:测试服务一小部分,比如类

    • 集成测试:验证服务与基础设施(MySQL)或其他服务的交互

    • 组件测试: 单个服务的验收测试

    • 端到端测试: 整个程序的验收测试

  • 组织测试方式

    • 测试象限:

      • Q1 协助开发/面向技术:单元和集成测试; 自动化

      • Q2 协助开发/面向业务:组件和端到端测试; 自动化

      • Q3 寻找产品缺陷/面向业务: 易用性和探索性测试; 手工

      • Q4 寻找产品缺陷/面向技术: 非功能性验收测试,如性能测试; 手工/自动化

    • 测试金字塔

      • 从下往上移动时,编写的测试越来越少

      • 从下往上: 单元 -> 集成 -> 组件 -> 端到端

  • 分布式微服务的测试难点

    • 服务依赖交错,测试复杂

  • 解决方案

    • 消费者驱动的契约测试 [集成测试]

      • [spring-cloud-contract 提供者定义stub,消费者测试用例中调用提供者,会直接调用到提供者定义的stub中,消费者引入stub打包好的jar]

      • example: https://www.cnblogs.com/freshchen/p/12229452.html

    • Cherkin+Cucumber => BDD 行为驱动开发 [集成测试/端到端测试]

    • docker 启动依赖服务 [组件测试/端到端测试]

第十一章 开发面向生产环境的微服务应用

  • 服务安全性

    • 身份验证[操作人] -- JWT / OAuth2.0

    • 访问授权[操作权限]

    • 审计 [跟踪用户的操作]

    • 安全的进程通信 [TLS加密]

    框架: Spring-security, Apache-shiro, Passport

  • 服务可配置性

    • 基于推送的配置模式

      • 配置环境变量 -> docker 容器中的程序读取环境变量完成配置

      • Spring 配置文件,程序启动时读取

    • 基于拉取的推送模式

      • Spring Cloud Config: 远程配置,服务会主动拉取

  • 服务的可观测性

    • 健康检测API: 服务健康,服务可用性检测[k8s pod就绪指针]

    • 日志聚合: elk

    • 分布式跟踪: 服务调用链路 [zipkin/Spring Cloud Sleuth]

    • 异常跟踪: 异常报警,记录日志,rca

    • 应用程序指标: 系统指标/自定义指标收集后发送给收集服务[prometheus]

    • 审核日志记录: 1.与业务代码融合 2.AOP 3.时间溯源

    微服务基底 是一个具有处理以上问题的一个框架或一组框架。

  • 服务网格 [Istio/Linkerd/Conduit]

    • 将流量处理从服务的范畴中剥离,由服务网格处理与流量相关的所有共性问题,如熔断器,分布式追踪,服务发现,负载均衡,基于规则路由,安全通讯

第十二章 部署微服务应用

  • 部署选项

    • jar/war

      • 优点

        • 快速部署

        • 系统资源复用

      • 缺点

        • 如果技术栈需要特定的环境运行,部署时就需要额外的开销

        • 多个服务部署在同一个web服务器中,无法做到单服务资源隔离

        • 无法自动确定服务实例应该放置哪个服务器中

    • 服务打包成虚拟机镜像

      • 优点

        • 不关心实现技术,只需要上传的war/jar

        • 服务隔离

        • 成熟的云计算基础设施

      • 缺点

        • 资源利用率低

        • 部署慢

        • 系统管理需要额外的开销

    • 服务打包成容器,在docker/k8s上运行

      • docker

        • 优点

          • 利用容器api来管理服务

          • 服务和资源有隔离

        • 缺点

          • 需要承担容器镜像的管理工作

      • k8s

        • 优点

          • 使用 k8s 的api可以管理服务,并且基于基础设施解决了服务发现,路由等问题。

          • 不停机更新

      istio提供的流量管理,即 蓝绿部署

    • 使用serverless

      • 优点

        • 基于使用情况定价

        • 不需要关心系统问题

      • 缺点

        • 不适合部署延迟敏感的服务

        • 不适合长时间运行的服务

第十三章 微服务架构的重构策略

  • 绞杀者应用模式

    • 将单体中的功能一步步拆分成微服务,并不需要一步到位,这是一个漫长的过程,但是稳定。

    • 方式

      • 将新功能实现为服务

      • 隔离表现层和后端

      • 将功能提取到服务来分解单体

        • 拆解领域模型: 单体中提取领域模型

        • 重构数据库: 提取出领域对象后,要管理领域的依赖,建立自己的数据模型

        • 提取时机[目的]

          • 加速开发

          • 解决性能、可扩展性或可靠性问题

          • 允许提取其他一些服务:提取一个服务为了简化另一个服务

    • 难点

      • 设计集成胶水

        • 剥离服务后,单体应用与服务之间的交互集成

        • 设计防腐层,防止单体模型影响领域模型

      • 维护数据一致性

        • saga

        • 服务提取顺序

      • 安全机制

        • 全局cookie/session:由gateway转发给服务

Last updated