微服务架构设计模式
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