本文要点
多年来,我多次参与并密切观察了将单体遗留系统迁移到微服务架构的工作。通过这些痛苦的经历,我学到了很多,掉进过很多陷阱,也遇到过很多挑战。其中的一次尝试让我个人损失了一大笔钱,同时也是毁掉一家相关公司的一个关键因素。在这里我不详述细节,但我肯定不想让别人也经历这样的痛苦。
我试着总结出我所学到的一些经验教训,希望你在重构这片艰难的土地上能够顺利地进行迁移。对于这个话题,人们已经说了很多,但还是有一些建议(我多么希望之前就有人能给我这些建议),其中有一些是我以前从未听说过的。
我是Garden公司的联合创始人。我们的目标是让开发人员能够更容易地测试、审查和诊断云原生微服务应用程序(我们的核心产品是开源的——如果感兴趣,可以看看),所以我花了很多时间与正在经历迁移的用户一起工作。通过这些互动,我总结了一些重要的并且值得分享的经验教训。
即使到了 2020 年,很多成熟的企业仍然在使用遗留系统,这些系统越来越难以扩展、运营和维护。它们通常是承担了太多职责的复杂的多功能系统。它们被称为单体,现在是不遵循现代开发架构、不满足可伸缩性需求或与组织结构不匹配的应用程序的代名词。太多的人在同一个紧密耦合的代码库上开发,可能会造成严重的瓶颈,减慢开发速度,并且这种耦合可能会导致可靠性和可伸缩性问题。
因此,考虑迁移到微服务架构是完全合理的。以下是我建议你在计划下一步时需要考虑的一些事情。
认真想一想你是否真的需要拆分你的单体系统,如果真的要这么做,确保你的理由是正确的。重构有很多理由,但如果有以下这些迹象,则应该避免进行重构:
如果你对以上 4 点都点头赞同,那么结论就很明显了——你不需要进行迁移。所以,在将来的项目中再考虑采用微服务架构吧,但请将你的重心放在实际的业务问题上,让系统完成它应该做的工作。
但是,有可能你没有读到这篇文章,所以从一开始就考虑到要进行重构。现实是复杂的,你的情况可能符合上述 4 点中的 3 点,或者一点都不符合。如果一点都不符合,你就没有太多的选择。对于介于两者之间的情况,就需要再思考一下,你可能有其他更好的理由。
你需要权衡你要面临的挑战和迁移成本。你的估算成本至少应包括以下这些:
考虑所有这些因素,做出估计,然后乘以 3,以便应对所有可能存在的不确定性。
如果所有这些加起比当前令你头疼的问题还要轻,那么就继续吧。但如果你仍然不确定,或许下面的一些建议可以帮你决定是否值得这么去做。
这个可能需要解释一下,我所说的项目是指需要你安排和计划,并分配数周的开发时间,等等。也就是说,你希望持续、不间断地做这件事,并且有一个开始和计划的结束日期。把迁移作为一个项目,并以完成该项目为目标,这是对技术债务的一种常见回应,我发现这是错误的。
原因如下:
与其把它变成一个项目,不如把它作为一种持续的任务。这个区别很关键,因为任务不一定都有时间表,在理想情况下不会阻碍其他项目。当然,它也应该是你的业务目标之一。
然而,这只有在你投入时间的情况下才会奏效,而且在你确定工作优先级时不要一次又一次地忽略了它。在任务上投入意味着需要让你的组织参与进来,并确保这项工作得到文化上的支持。
人们很容易在会议上赞同一些东西,但很快被下一个 Sprint、一周、季度的优先事项推翻。在我们的日常工作中,“重要”往往会被“紧急”打败,所以你需要不停地提醒自己,避免被无休止的“紧急”事项淹没。
以下是你可以做的一些事情,以确保你的团队可以持续在这项工作上投入:
对于任何一个系统(包括遗留系统)来说,缺乏自动化测试是很常见的,也是很正常的。如果你乐于重构已经提供了测试代码的系统,那你应该会感激它们。也许你已经注意到了,也许并没有注意到,因为有了测试代码,一切都进行得太顺利了。
如果没有自动化测试,就很难评估新代码做的事情是否与旧代码一样。无论是重写(可能是用一种新的编程语言)还是移动旧代码,都是一样的道理。即使是最微不足道的重构也会导致微妙的问题或不一致性,无论开发人员有多熟练。
如果你对当前的测试覆盖率感觉不满意,那么就从解决这个问题开始。编写好的测试代码将极大地简化工作,并且是理解代码、结构、复杂性和特性的一种很好的方式。
如果已经有测试代码,那么就要深入研究它们,看看它们涵盖了哪些内容。它们是从外部测试系统(例如在 API 级别)还是只测试内部代码?后者(基本上是单元测试)虽然重要,但在拆分代码和移动代码时却没有太大直接的价值。良好的集成和端到端测试可以让系统保持原样,并在迁移过程中作为基准测试。
这包含了两个独立的建议。第一个是进行增量式的拆分。不要想当然地认为你可以实现一次性成功的迁移,然后按下开关,一切都会变好,这太冒险了。正确的方式是先找出热点问题、最大的瓶颈或挑战,并逐一解决它们。
第二个可能是可选的,并不总是适用于所有的情况,但我强烈建议在可能的情况下将单体作为新服务的前端。这样做的好处是可以为服务的用户/消费者提供相同的 API,并在整个迁移过程中保持相同的集成和端到端测试。
一个明智的替代方案是先创建一个代理,代理背后只有一个单体,然后在拆分或添加新服务后将流量路由到不同的服务。这些取决于你目前的系统和你要达成的目标。不管是哪一种情况,你都可以在用户不知情或不关心的情况下逐步迁移单体的各个部分。
因此,在将功能转移到新服务中时,单体在整个重构过程甚至是在重构之后的一段时间内仍然可能存在——要么作为新功能的门面,要么作为隐藏在门面背后的代理。
你总希望这一次做的事情可以避免将来再次重构,这种想法是可以被理解的。
但我强烈建议在这方面保持谨慎,并考虑采用已有的模式。否则,你可能会发现自己同时被两个问题所左右,陷入一个新的兔子洞。大多数公司都负担不起开拓新技术的费用,而那些能够负担得起的公司往往会在业务的关键路径之外开拓新技术。
出于同样的原因,通常建议避免在重构过程中使用新语言进行重写——甚至是一些看起来无关痛痒的东西,比如从 Python 2 迁移到 Python 3——因为这会让调试变得加倍困难。
不管怎样,不要试图发明一种全新的微服务开发方式。可能你会认为需要自己发明解决方案来解决问题,而不是采用一个已知的解决方案,但我认为你大可不必。继续寻找已有的解决方案,问问周围的人,在组织之外寻求解决问题的方法。
尽管存在一些限制,但单体架构确实有一些内在的好处。其中之一就是它通常很简单,你只需要一个管道和一组开发工具。迁移到分布式架构涉及到很多额外的复杂性,并且需要考虑很多可移动的部分,特别是如果这是你第一次这么做的话。你需要选一组工具来提升开发者体验,可能还需要自己开发一些工具(如果能避免,我建议尽量避免)。
另外,微服务的运维与单体的运维有很大的不同。你需要在监控工具上投入,而且在组织积累运维专业知识的过程中,需要做出学习和调整的计划。
这些运维上的差异既有组织层面的,也有技术层面的。如果多个不同的团队彼此独立地开发和部署他们的服务,你可能还需要重新审视你的沟通渠道和规范。
这样做很可能还是值得的,但不要让这些“后续问题”把你弄得措手不及。
遗留系统是我们生活的一部分,而且一直都是。世界发展得越来越快,我们总是会面临某种技术债务。作为开发者,我们需要接受这个事实。
单体是我们大多数人都知道的技术债务之一,可能现在你眼前就有一个。这可能是一个紧迫的问题,也可能不是,但如果真的是,那么就努力逐步拆分它,并确保得到所有参与人的支持。
你可能会遇到麻烦,但如果你能够理智地处理,总结教训,就会安然无恙。请放心,你并不是一个人在战斗。
作者简介:
Jon Edvald 是 Garden 公司的首席执行官和联合创始人,这家公司开发了一个开源的 Kubernetes 开发和测试平台。在从事软件开发的 10 多年时间里,Jon 在 Clue 和 QuizUp 担任过工程领导职务,也是 CLARA(被 Jive Software 收购)的联合创始人和首席技术官。他热衷于开发者体验、抽象、音乐和抽象音乐。
原文链接:
Seven Hard-Earned Lessons Learned Migrating a Monolith to Microservices
领取专属 10元无门槛券
私享最新 技术干货