与我一起学习微服务架构设计模式13—微服务架构的重构策略
- 2020 年 1 月 2 日
- 笔记
重构到微服务需要考虑的问题
为什么要重构单体应用
单体地狱造成的业务问题举例:
- 交付缓慢
- 充满故障的软件交付
- 可扩展性差 先尝试改善软件开发流程(如自动化测试)等方案,问题仍然存在,则迁移到微服务。
绞杀单体应用
不要做“一步到位,推倒重来”式的改造。
单体应用逐渐被由服务组成的绞杀者应用程序(如绞杀藤蔓一般)所取代。最终,单体应用完全被绞杀者应用程序取代或成为另一个微服务。
Amazon.com花了几年时间重构它的单体。
尽早且频繁地体现出价值
逐步重构微服务可以立即获得投资回报,“一步到位”在“到位”之前不会带来任何好处。
应先将单体中高价值部分迁移到微服务架构。
更早交付价值有助于获得业务团队对重构工作的支持。
尽可能少对单体作出修改
它耗时、昂贵且具有风险
部署基础设施:这对你来说还为时过早
只在架构重构前期进行最小投资。
将单体应用重构为微服务的若干策略
将新功能实现为服务
降低单体的增长速度。
新功能作为服务实现,服务是绞杀者应用程序的一部分。集成胶水将服务与单体架构集成,并由实现同步和异步API的适配器组成。
集成胶水提供服务访问单体所拥有的数据。如单体发布了实体的领域事件,服务使用这些事件并更新这些实体的副本。或者使用单体的查询API检索数据。
API Gateway将调用新功能的请求路由到服务。
若新功能无法作为服务实现,则解决方案是首先在单体实现功能,之后将功能以及其他相关功能提取到自己的服务。
隔离表现层和后端
从后端拆分出表现层与后端业务逻辑层,可以使每个部分独立部署,它还公开了用于服务调用的API。
通过将功能提取到服务来分解单体
你想要提取到服务中的功能是对单体应用自上而下的一个“垂直切片”。它包含:
- 实现API端点的入站适配器
- 领域逻辑
- 出站适配器,如数据库访问逻辑
- 单体的数据库模式
首次提取迭代可以保留命令和查询等公开的API,只提取算法的核心部分。
新提取的服务和单体通过集成胶水提供的API进行协作,通过API Gateway进行请求路由。
挑战:
拆解领域模型
其中一个挑战是消除跨越服务边界的对象引用,解决方法是用主键代替。
更大挑战是提取嵌入在具有其他职责的类中的功能。
重构数据库
需要将表从单体的数据库移动到服务的数据库。
拆分实体时,需要拆分相应的数据库表并将新表移动到服务中。
复制数据以避免更广泛的更改:
通过将与新提取的服务相关的数据复制回单体的数据库,最大限度地减少对单体的更改范围。
确定提取何种服务以及何时提取
需要专注于提取能够带来最大收益的服务。
使用时间框架定义工作,定义一组服务作为目标。
提取策略:
1、有效冻结单体架构的开发并按需提取服务。弊端是有时大量的工作只能换来较小收益。
2、这种策略更有计划,即根据提取应用程序模块获得的预期收益,对应用程序模块进行排名。
收益考虑以下几点:
- 加速开发
- 解决性能、可扩展性或可靠性问题
- 允许提取其他服务(简化另一个服务的提取)
设计服务与单体的协作方式
一个重要的问题是维护服务和单体之间的数据一致性。
设计集成胶水
集成胶水促成了服务与单体间访问彼此的数据,它由实现API的适配器组成。一些API基于消息,有些基于远程调用。
设计集成胶水的API
根据是为了查询数据还是为了更新数据,由几种不同风格的接口可供选择。服务的业务逻辑不需要知道集成胶水用于检索信息的进程间通信机制,该机制应用程序使用接口API封装。
设计单体调用服务接口时,可以设计让调用能够在现有功能和新服务间动态切换,降低推出新服务的风险。
选择交互方式和进程间通信机制
这取决于一方查询或更新另一方的需求。
查询
一方查询另一方,一种选择是实现存储库接口的适配器,以调用数据提供者的API。通常使用请求/响应方式,如REST或gRPC。好处是简单,坏处是可能效率低下,降低可用性。
另一种方法是让数据使用者维护数据的副本,副本本质上是CQRS视图。数据使用者通过订阅数据提供者发布的领域事件使副本保持最新。好处是避免重复查询数据提供者的开销。缺点是维护副本的复杂性。
更新
需要维护服务和单体的数据一致性。解决方案是服务和单体使用由框架实现的事务消息进行通信。
实现反腐层
反腐层是一个软件层,用于在两个不同的领域模型之间进行转换,防止一个模型的概念污染另一个模型。如一个服务具有Delivery实体,职责范围窄,单体具有Order实体,职责多。
单体如何发布和订阅领域事件
将单体更改为发布和使用事件有几种方式。一种是使用与服务相同的领域事件发布机制。在代码特定位置插入调用,但更改耗时且容易出错。
另一种方法是在数据库级别发布领域事件,如使用事务逻辑拖尾或轮询。弊端是通常很难确定更新的原因,并发布适当、高阶的业务事件。
单体订阅以服务方式发布的领域事件则容易些,可使用框架编写事件处理程序。
在服务和单体之间维持数据一致性
可使用Saga确保单体与服务间的一致性,但Saga必须使用补偿事务撤销变更,这需要对单体进行大量且耗时的更改。单体也可能需要实现特定策略解决Saga间缺乏隔离的问题。
修改单体应用使其支持补偿事务的挑战
要支持补偿事务,可能需要引入新的实体状态(如APPROVAL_PENDING,这是一个语义锁对策),这可能需要对单体进行大范围的更改。
Saga并不总是要求单体应用支持补偿事务
如果每个单体的事务都是一个关键性事务或可重复性事务,那么单体就永远不需要执行补偿事务。你只需要对单体进行最小的更改即可支持Saga。
选择合适的服务提取顺序,以避免在单体中实现补偿事务
通过对提取的服务进行排序,可以避免必须对单体进行大范围修改以支持可补偿事务。必须确保单体的事务是关键性事务或可重复性事务。
处理身份验证和访问授权
需要同时支持基于单体和基于JWT的安全机制。
单体登录处理程序返回一个额外的USERINFO cookie,包含用户信息。API Gateway在调用服务时将USERINFO cookie转换为一个访问授权头部,服务验证USERINFO令牌并提取用户信息。
java达人
ID:drjava
(长按或扫码识别)