正确实现的微服务较单体应用有很多优势。许多组织都希望将他们的单体应用程序代码换成微服务代码。但事实证明,迁移到微服务并非易事。你应该问的第一个问题是,你真的需要微服务吗?单体存在的许多问题都可以使用模块化的单体架构轻松解决。一旦你确定自己真的需要微服务,就必须制定一套将单体应用转换为微服务的计划。本文介绍了一些模式,可以帮助你创建所需的计划。
在我们具体讨论这些拆分单体的模式之前,我们先来谈谈不应该做的一些事情。
不要做大爆炸重写
大爆炸重写(Big Bang Rewrite),顾名思义,是说我们必须在许多微服务中重写整个单体应用的代码,并一次性将它们都部署到生产环境中。Martin Fowler 说过的一句话非常正确:
大爆炸重写唯一能保证的就是大爆炸!
大爆炸重写都是很危险的。大爆炸重写需要漫长的开发时间,因为你必须对单体应用程序中的所有内容重新编码。此外,在微服务架构的开发过程中,你必须冻结单体应用中新的开发工作,因为单体应用中所做的所有更改都必须复制到微服务中。对于大多数公司来说,冻结应用程序开发工作可能存在风险,因为他们必须根据业务环境的变化随时调整软件。
对于一个组织来说,从单体逐渐转向微服务的路径一定是更好的选项。下面列举的一些可用设计模式可以帮助你逐步从单体架构转向微服务架构。
扼杀者(Strangler Fig)
扼杀者是 Martin Fowler 设计的一种模式。它的灵感来自于自然界的无花果。无花果是从自己寄生的树冠分枝开始生长的,其根向地面慢慢延伸。它的根会逐渐到达地面并继续生长,甚至在这一过程中会杀死寄主树。同样,在软件世界中,我们可以根据这种模式围绕单个单体应用程序来构建微服务。我们会在系统中逐步添加更多微服务,最终有一天会替换掉整个单体系统。
在扼杀者模式中,我们围绕现有单体架构的边缘创建新服务。单体的边缘是什么意思?我们通过具体的例子来深入理解。
图 1:单体应用
在图 1 中,你可以看到我们有一个单体应用程序。在上图中,产品库存、订单管理和计费管理模块位于应用程序的边缘。通知管理有多个来自应用程序内的入站调用。因此,我们无法将所有入站调用从外部应用程序重定向到通知管理。我们有另一种模式来将通知管理迁移到微服务,将在稍后讨论。
假设我们想将订单管理迁移到微服务。我们可以使用以下步骤。
- 插入代理:除非你已经有了一个代理,否则我们需要部署一个 HTTP 代理。在第 1 步中,我们部署一个 HTTP 代理,它将所有调用直接重定向到单体应用程序。引入 HTTP 代理后,你还可以了解网络上是否存在其他内容会延迟 API 调用。如果延迟很大,那么你必须先停止迁移并首先改进你的网络,然后再继续。
- 部署微服务:在第 2 步中,你将在生产环境中部署微服务。我们的微服务上不会有任何实时流量。在第 2 步中,我们将只测试微服务是否工作正常。
- 重定向流量:在第 3 步中,我们会将实际流量从 HTTP 代理重定向到我们新部署的微服务。如果出现问题,我们可以更改 HTTP 代理定向来轻松回滚。
所有步骤如图 2 所示。
抽象分支
当你需要提取其他模块所依赖的一个模块时,抽象分支的模式可能会很有用。假设在前面的示例中,我们想将通知管理转换为微服务。在这种情况下,我们就会使用抽象分支。我们需要执行以下步骤来提取模块。
- 创建抽象。你需要围绕要替换的模块创建抽象。
- 将现有功能的客户端更改为使用新抽象:你需要重构旧代码,让旧实现使用在步骤 1 中创建的抽象。
- 创建新的实现。你需要为功能创建一个新的微服务实现,将其部署到生产环境中并运行一些测试。
- 切换实现。当你运行了一些测试,有了一些信心后,你就可以切换到新的代码上。
- 清理。当你的微服务启动并运行后,最好清理旧代码库并删除旧模块。如果需要,你也可以删除抽象。在许多情况下,你之前创建的抽象只会改进你的代码库质量,这时完全可以保留它。
为了更好地理解整个过程,请参考图 3。
图 3:抽象分支
抽象分支模式可以用在很多地方。我们建议尽可能使用 Strangler Fig 而不是抽象分支。如果你确定你不能用 Stranger Fig 来将单体应用的某些部分替换为微服务,那么你就应该考虑抽象分支。
并行运行
无论你做了多少测试,出现错误的可能性仍然会存在。当你迁移一个关键系统时,你一点都不能指望运气。在这种情况下,并行运行模式可能会有所帮助。在这种模式中,我们会在生产环境部署我们新开发的微服务和旧的单体应用。我们会让数据流经两个系统。单体系统一开始会是唯一事实来源。我们将新开发的微服务的结果与单体结果做对比。如果我们发现存在任何不匹配情况,就要在微服务应用程序中修复它。一段时间后,当我们对新的微服务系统有足够的信心时,就可以停用单体应用的对应功能,并让微服务成为唯一事实来源。
在前面的例子中,假设我们想将计费管理从单体迁移到微服务。在这个模式下,我们将开发一个微服务并将相同的流量发送到我们新的微服务。每天结束时,我们可以用一个批处理作业来对比旧系统和新系统生成的账单是否相同。一旦我们有了足够的信心,就可以从单体应用中停用计费管理功能。我们还有一些开源库(比如 Github 的 scientist 库),可以帮助你更好地实现这种模式。
图 4:并行运行模式
当你的功能已经存在于单体应用中时,上面介绍的这种模式会很有用。假设你需要添加新的功能,比如你想在每次成功交易后通过电子邮件向用户发送下一次交易的折扣券。很简单,你只需在单体应用的订单模块中添加新代码即可调用新创建的折扣微服务。但是如果你没有代码呢?假设你正在使用其他供应商的解决方案,或正在使用某些 SAAS,你也依旧可以实现它。接下来的两种模式就是针对这种情况量身定制的。
装饰协作者(Decorating Collaborator)
这种模式的灵感来自我们熟悉和喜爱的一种模式——装饰者模式。在这种模式下,就像扼杀者模式一样,我们必须引入一个代理。我们让调用通过代理传递到单体应用,然后根据单体应用的响应,代理将调用我们新创建的微服务。
图 5:装饰协作者
图 5 展示了装饰协作者模式的机制。仅当微服务所需的所有数据都已存在于请求或响应中时,我们才应该使用这种模式。如果数据不存在,那么我们新创建的微服务就必须连接到单体数据库上。也就是说我们新的微服务需要与单体数据库耦合,这绝不是一个好主意。
更改数据捕获模式
在这种模式中,我们将对数据库中发生的更改做出反应。比方说,我们想为系统中创建的每个客户创建一张会员卡。在这种模式下,我们可以监听客户表中的更改。一旦我们检测到有新客户创建了客户表,我们就可以调用 Loyalty 微服务。然后这个微服务可以向客户发放会员卡,并向他们发送包含详细信息的电子邮件。你可以使用多种方法来监听数据库中的更改。你可以使用触发器,也可以使用数据库的事务日志。还可以编写一个每隔几分钟触发并检查数据库中发生的更改的流程。
总 结
正确实现的微服务具有许多优势。将你的单体应用程序转换为微服务并不是朝夕就能完成的工作。你还应该记住,转换为微服务不是一场竞赛,而是一场漫长的马拉松。它需要足够的耐心,并且必须做出良好的架构决策才行。
在本文中,我们讨论了一些你可以使用的模式。大多数情况下,你需要应用多种模式才能将单体应用程序完全转换为微服务。最后,我只是建议,在迁移到微服务之前花点时间弄清楚你准备使用的策略,这种准备工作迟早会获得回报的。
— 本文结束 —