译者 | 朱钢
策划 | 云昭
微服务架构设计中,如何拆分单体是一件非常重要且令架构师十分头疼的问题。在这篇文章中,会展现一些关于如何准备和执行单体应用程序拆分的思路和步骤说明。
概述
单体拆分必须追求的一些目标:不仅仅是拆分,而是通过拆分获得一些收益。 如果考虑到拆分的成本和效果,可能其他一些方法(例如应用程序扩展或数据库硬件更新)可能更可取。
要实现的另一个好处的例子可能只是应用程序现代化。 然而这里是一个或多或少正式的单体拆分方式,它试图考虑拆分的原因和目标。 当然这不是教条,你可以找到几种拆分的方法。 此迁移路线图旨在将单体应用程序迁移到微服务以此获得微服务的优势,而不会(或极小)造成应用程序不可用。
明确基准线
由于应用程序支持性,单体应用程序可能看起来非常混乱,但这是意料之中的。 最好的情况是,期待一个带有大量调整的大型耦合分层应用程序。 无论如何,第一步是你需要收集有关当前应用程序的所有信息:需求、用例、ASR、组件、部署图等。
理解应用程序(整体)
在大多数情况下,应用程序缺少文档,这些文档可能描述了基准线,那么你至少需要收集以下信息:
- 应用程序的功能模块、它们之间的关系和外部环境;
- API和服务接口描述;
- 带有模块描述的组件图就足够了。这是因为在大多数情况下,拆分将按照功能或换句话说应用程序功能执行;
- 部署图,显示物理硬件以及系统和软件配置。这是因为一些NRF可能会受到部署的影响(例如,故障隔离的可扩展性)。
理解数据
为什么数据图表和潜在数据映射很重要? 在大多数情况下,微服务将与底层数据和数据库一起抽取。杂乱无章的数据可能会影响此类 NRF 的性能。 在任何情况下,它都有助于发现潜在的问题,例如数据缺乏或过多、数据混乱以及使用某些微服务进行数据抽取的边界。
理解上下文
在某些情况下,需要抽取的功能可能已经作为单独的外部服务存在了。不需要抽取一些业务能力,只需要复用一些已经存在的外部服务。像 TTM 这样的微服务优势可以用更少的努力来实现。
理解用户
这虽然与上下文重叠,但显示了用户/外部系统是如何使用应用程序的,以便了解使用什么功能以及如何使用,也许应用程序的某些部分已过时且未使用。
其余关于应用程序的信息将用于做出决策并显示拆分的必要性,如何拆分以及抽取什么。
为什么要拆分单体?
可能很难回答为什么我们需要将单体应用拆分为微服务,答案可以更加面向业务或更加面向技术。在大多数情况下,企业是看不到将单体架构重构或拆分为微服务的任何好处。
业务驱动
乍一看,可以找到以下先决条件,但列表可能会扩大。 无论如何,业务驱动都是与增加收入和减少损失有关的,因此新的项目都只会从这两个问题中衍生出来。
- 缩短上市时间
- 降低 TCO:仅扩展必要的部分
- 减少损失:弹性、高可用性和容错解决方案、测试覆盖率
- 提供更好的用户体验
此外,一些奇怪的却动力可能默默地出现,但不会展现出来:它很主流并且也拆散了整体。
技术驱动
技术驱动只是扩展了业务驱动的列表,可能如下所示:
- 摆脱过时的技术并顺利迁移到现代/受支持的技术
- 降低复杂性
- 提高可支持性
- 提高测试覆盖率
- 可重用性
- 透明且可预测的变化
- 团队重用
微服务的优势
下面提供了微服务的好处以及它们如何满足业务和技术需求。 当然,表格可以通过几个额外的微服务好处进行扩展,但它们大多是派生出来的,并与表格内容重叠。
记住优势的同时,也请不要忘记缺点。
拆分步骤
1、准备
以下是或多或少拆分单体应用的正式步骤。这些步骤可能会根据业务需求、单体状态、CI/CD 程序等进行调整。
在建立基线时早期获得的模块/服务列表应围绕业务能力进行组织,并且可能更深一层。例如欺诈检测系统和促销服务可能需要地址。
可以在拆分过程的中间实现期望的目标。从业务的角度来看,这可能是件好事,但从技术的角度来看,继续使用包含多个抽取服务的单体应用程序就很糟糕。
2、重构优先
需要拆分的应用程序几乎不可能具备良好的形态。因此可能会在单体应用中执行几轮服务重构,一些服务可能正在重构中,而另一个正在抽取中。
3、典型的分层应用
这是服务抽取的起点,此时应用程序的主要层,如控制器、应用程序服务、DAO 等,是可观察和可理解的。
4、服务/功能边界和 API
DDD 可能有助于定义服务边界,但我们需要从现有应用程序中提取业务功能,而不是对业务模型进行建模。只需开始将业务功能映射到现有的应用程序服务和域模型中以识别服务边界即可。
在分层应用程序中,控制器可以描述要提取的 API。API 可能会被调整/扩展,但这里的重点是整个应用程序必须与服务一起工作,并且更改最少。更改与我们需要使用抽取功能(代理和外观)的原因和方式有关。
5、创建服务facade
当定义服务边界时需要改变与功能交互的方式,而不是处理一组代表业务功能的应用程序服务,我们需要在单体应用程序中创建一个外观并通过facade工作。
换句话说,我们需要在提取服务之前获得一个松散耦合的单体。
6、重构数据
使用数据库抽取一项服务可能会影响单体应用的多个不同服务。所以只能通过 API 访问服务数据,而不是通过数据库。因此,由于直接访问了数据,即使是一个服务的提取,也可能需要大量工作。
7、停止将新功能写入单体应用程序
拆分到微服务和向解决方案中添加新功能着两个活动并行执行,这是一种常见的情况,因此从一开始就将新功能创建为单独的微服务是有意义的。
8、拆分
大多数情况下,所有准备步骤都应在实际拆分之前完成。应用程序应呈现为松散耦合的单体,具有细粒度的服务、良好描述的 API 和边界以及每个服务的隔离数据(避免跨数据库共享数据)。
9、优先提取服务
更正确的做法是不进行服务抽取,而是进行业务特征抽取。尝试按业务能力/领域对现有应用进行重组/重构/分组,每个应用可能包含多个应用服务。
潜在的优先标准:
- 最常更改的服务:最小化对部署的影响。
- 可能被第三方服务取代的服务:只是为了让代码库更轻量级。
- 需要扩展的服务:优化性能。
- 与整个单体耦合:使代码库更轻巧且更易于理解。
- 服务的复杂性:收集经验、建立CI/CD流程、沟通方式等。
清单可能会不同, 可能添加诸如将底层数据库模型从关系数据库模型更改为 NoSQL、技术更改(编程语言)、团队利用率等标准。
结果,可以选择至少一项服务进行提取。
10、选择微服务之间的通信方式
这不是一个非常复杂的步骤。但是我们不仅需要选择协议和序列化类型,还需要考虑云提供商的限制(即广播支持)。在大多数情况下,REST 或 gRPC 是请求-响应同步通信的首选方式。这是由于相对简单、团队经验、不同工具的支持等原因。很难想象新的或抽取的微服务使用干净的 TCP。
基于消息的通信方式由于异步性、单向(当然可以使用“reply to”机制,但会导致服务耦合)等原因,不能被视为请求-响应的替代方案。异步适用于事件源(即发即弃),也适用于消费者构建自己的世界图景。一个例子是关于成功付款的通知将反映在订单履行系统中以组装交付和财务系统,可以使用 SQS、Kafka 或其他一些消息传递服务。
11、实施服务
使用选定的通信方法、拥有的数据库等实现微服务。创建一个用于测试目的的服务模拟来测试单体和其他相关微服务是有意义的。
12、创建服务代理
创建具有额外责任的服务代理。在单体应用中使用服务或使用抽取服务,暗示代理可用于与删除/抽取的微服务进行通信。
该代理将允许在单体应用中的现有服务实现和抽取的服务之间轻松切换。可以使用canary发布方法。
13、切换到微服务
经过测试,在生产环境中进行测试的canary发布仅对微服务使用和从旧代码中清理单体应用有意义。
检查是否达到了初始目标,并决定停止或继续拆分过程。在继续的情况下,你需要选择下一个特征并执行提取。
译者介绍
朱钢,51CTO社区编辑,2019年CSDN博客专家20强,2020年腾讯云+社区优秀作者,10年一线开发经验,曾参与猎头服务网站架构设计,企业智能客服以及大型电子政务系统开发,主导某大型央企内部防泄密和电子文档安全监控系统的建设,目前在BIM头部企业从事招投标软件开发。
原文Split the Monolith: What, When, How,作者:Igor Azarny