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