许多开发团队认为微服务架构优于单体架构也有一些团队认为微服务反而会降低生产力。和任何架构风格一样微服务既有优势也有劣势。要做出明智选择就必须充分理解这些利弊并结合具体场景进行判断。本文将围绕微服务架构的核心权衡展开重点分析微服务相对于单体架构在模块边界、分布式复杂性、最终一致性、独立部署、运维复杂性和技术多样性等方面的优势与代价。强模块边界微服务架构的核心优势微服务架构最重要的优势是它能够形成更强的模块边界。这当然是一项重要优势但从理论上看这一点又有些奇怪微服务的模块边界并没有天然理由一定比单体架构更强。那么我所说的“强模块边界”是什么意思我想大多数人都会同意将软件划分为模块——也就是彼此解耦的软件单元——是有益的。理想情况下模块应该这样工作当我需要修改系统中的某一部分时通常只需要理解系统中很小的一部分而且能够很容易定位到这部分。良好的模块化结构对任何程序都有价值但随着软件规模增长它的重要性会呈指数级上升。更重要的是随着开发团队规模扩大模块化的重要性也会显著提高。微服务的支持者经常提到康威定律软件系统的结构会反映构建它的组织的沟通结构。对于规模较大的团队尤其是成员分布在不同地点的团队软件结构必须承认一个现实团队之间的沟通频率通常低于团队内部沟通方式也往往更加正式。微服务允许每个团队维护相对独立的单元从而更好地适应这种沟通模式。正如我之前所说单体系统完全可以拥有良好的模块化结构。¹ 但许多人注意到这种情况似乎并不常见因此“大泥球”才会成为最常见的架构模式之一。事实上正是对单体系统常见结局的失望促使许多团队转向微服务架构。模块解耦之所以有效是因为模块边界限制了模块之间的引用。问题在于在单体系统中这类边界通常很容易被绕过。绕过边界也许是一种快速实现功能的便捷手段但如果这种做法被普遍采用就会破坏模块化结构并严重影响团队生产力。将模块拆分为独立服务可以强化边界约束从而大幅降低这种有害“捷径”出现的可能性。这种耦合的一个重要方面是持久化数据。微服务的关键特征之一是去中心化数据管理也就是说每个服务管理自己的数据库其他服务必须通过该服务的 API 才能访问这些数据。这消除了共享数据库带来的问题而共享数据库正是大型系统中严重耦合的主要来源之一。需要强调的是单体架构完全可以实现清晰的模块边界但这需要很强的纪律性。同样微服务架构也可能变得混乱只是出现这种情况的概率相对更低。在我看来使用微服务确实有助于提高模块化程度。如果你非常确信团队能够长期保持良好的工程纪律那么这种优势也许并不明显但随着团队规模扩大维持纪律会越来越困难而维护模块边界的重要性也会越来越高。如果边界划分不清这项优势就会转化为劣势。这也是“单体优先”策略的两大主要理由之一即使是那些倾向于较早采用微服务的人也会强调只有在对领域有充分理解之后才应该这么做。不过我还想补充几点。一个系统的模块化表现究竟如何只有经过一段时间的检验才能判断。因此只有当我们看到已经运行至少数年的微服务系统之后才能真正评估微服务是否带来了更好的模块化。此外早期采用者往往是能力更强的团队所以我们需要更长时间才能评估普通团队构建的微服务系统是否也具备模块化优势。即便如此我们也必须承认普通团队写出的软件通常也只是普通水平。因此与其把最终结果同顶尖团队的成果相比不如把它与同一团队在单体架构下可能写出的软件相比——而这又是一个很难评估的反事实问题。目前我只能根据一些采用过这种方式的人给我的早期反馈来判断。他们的评价是用这种方式维护模块确实容易得多。其中一个案例研究尤其有趣。团队最初做出了错误选择在一个复杂度不足以发挥微服务高级能力的系统上采用了微服务。项目随后陷入困境需要紧急补救于是大量人员被投入项目。此时微服务架构的优势反而显现出来系统能够更快吸收新增开发人员团队也比使用单体架构时更容易利用更多人手。因此项目开发速度加快生产力甚至超过了使用单体架构时的预期使团队得以及时赶上进度。尽管最终结果仍然是负面的——因为软件开发消耗的人力时间比使用单体架构时更多——但微服务架构确实支持了团队的快速扩张。分布式系统微服务架构的主要劣势微服务通过分布式系统来提升模块化程度。但分布式软件有一个主要缺点它是分布式的。一旦采用分布式架构就会引入一整套复杂性。我认为微服务社区并不像当年的分布式对象运动支持者那样对这些成本视而不见但这些复杂性依然存在。首先是性能问题。如今进程内函数调用很少成为性能瓶颈但远程调用要慢得多。如果你的服务调用了六个远程服务而每个远程服务又调用另外六个远程服务那么这些响应时间累积起来就会造成非常糟糕的延迟。当然你可以采取许多措施来缓解这个问题。第一种做法是提高调用粒度减少调用次数。但这会让编程模型变得更复杂因为你现在必须考虑如何批量处理服务之间的交互。而且这种方法也只能解决部分问题因为你至少仍然需要调用每个协作服务一次。第二种缓解方式是使用异步。如果并行发起六个异步调用那么总延迟只取决于最慢的那次调用而不是所有调用延迟之和。这可以显著提升性能但也会带来认知成本。异步编程很难不仅难以正确实现也更难调试。而在我了解的大多数微服务案例中要获得可接受的性能几乎都需要使用异步。紧随性能之后的是可靠性。你通常会期待进程内函数调用正常完成但远程调用随时可能失败。微服务越多潜在故障点就越多。有经验的开发者深知这一点并会在设计时充分考虑故障处理。令人欣慰的是异步协作所需的许多策略也适用于故障处理从而有助于提升系统韧性。然而这并不能完全抵消分布式带来的代价。你仍然需要额外考虑每一次远程调用失败后的后果这无疑增加了系统复杂度。而这还只是分布式计算中几个主要谬误所带来的问题。这个问题也有一些需要注意的地方。首先随着单体架构不断增长许多类似问题也会出现。很少有单体应用是真正完全独立的它们通常都需要与其他系统交互尤其是遗留系统。与这些系统交互需要经过网络因此也会遇到同样的问题。这也是许多人倾向于更早转向微服务架构来处理远程系统交互的原因。此外经验在解决这类问题时至关重要经验丰富的团队通常能更好地应对分布式架构带来的挑战。但分布式始终是有成本的。我一向不愿轻易动用“分布式”这张牌也认为很多人过早转向分布式是因为他们低估了其中的问题。最终一致性问题微服务的数据一致性代价你很可能遇到过这样的网站页面加载或数据更新需要一点耐心。你更新了某项内容刷新页面后却发现更新不见了。等上一两分钟再刷新更新内容又出现了。这是一个非常恼人的可用性问题几乎可以肯定是最终一致性带来的负面影响。你的更新被某个节点接收但你的 GET 请求却由另一个尚未同步更新的节点处理。在后一个节点收到更新之前你会一直处于不一致状态。数据最终会趋于一致但在那之前你会一直怀疑是不是出了什么问题。这种不一致固然令人恼火但后果可能更加严重。业务逻辑最终可能会基于不一致的信息做出决策。一旦发生这种情况诊断问题将极其困难因为任何调查通常都要等到不一致窗口期结束很久之后才会进行。微服务因为坚持去中心化数据管理——尽管这种坚持本身值得肯定——而引入了最终一致性问题。在单体架构中你可以在一个事务中同时更新多个对象。而在微服务架构中一次更新往往需要涉及多个资源并且分布式事务通常不被提倡理由也很充分。因此开发人员必须意识到一致性问题并在系统执行任何将来可能后悔的操作之前设法检测数据是否已经不同步。单体架构并非完全没有这些问题。随着系统规模扩大对缓存的需求也会增加而缓存失效本身就是一个难题。大多数应用程序需要使用离线锁来避免长时间占用数据库事务。外部系统也需要进行一些无法与事务管理器协调的更新。业务流程通常比你想象的更能容忍不一致因为企业往往更看重可用性——从某种意义上说业务流程很早就凭直觉理解了 CAP 定理。因此和其他分布式问题一样单体架构并不能完全避免不一致问题但它们受这些问题影响的程度要小得多尤其是在系统规模较小时。独立部署微服务架构的重要优势模块边界与分布式系统复杂性之间的权衡贯穿了我的整个职业生涯。但有一点发生了显著变化尤其是在过去十年里那就是发布到生产环境的方式。在二十世纪生产环境发布几乎总是痛苦而罕见的事情往往需要周末加班才能把一些棘手的软件勉强推到可用状态。但如今技术成熟的团队会频繁地将软件发布到生产环境许多组织都在实践持续交付使他们能够每天多次发布到生产环境。这种转变对软件行业产生了深远影响并且与微服务运动紧密相关。许多微服务项目的出现都源于部署大型单体应用的困难单体应用中哪怕一个很小的改动也可能导致整个部署失败。微服务的一项关键原则是服务本身就是组件因此可以独立部署。这样一来当你进行变更时只需要测试和部署一个小型服务。即使出错也不应该导致整个系统崩溃。毕竟既然系统必须为故障而设计那么即使某个组件完全失效也不应阻止系统其他部分继续运行尽管系统可能需要以某种方式优雅降级。这种关系是双向的。由于微服务通常需要频繁部署因此确保部署流程顺畅至关重要。这也是为什么快速应用部署和快速基础设施配置是微服务的前提条件。对于任何超出基础功能的应用来说持续交付都几乎是必要条件。对于希望将团队目标、客户反馈、需求评审、开发测试、发布上线和知识沉淀串联起来的研发团队借助 PingCode 这类智能化研发管理工具也有助于让微服务架构下的研发流程更加自动化、数据化和可追踪。持续交付最大的优势在于它缩短了从想法产生到软件运行之间的周期。采用持续交付模式的组织能够更快响应市场变化并比竞争对手更早推出新功能。尽管许多人把持续交付作为采用微服务的理由但必须指出即使是大型单体应用也能实现持续交付。海外某些互联网公司和电商平台就是常被提到的例子。此外也有不少采用微服务架构的尝试并没有实现独立部署因为多个服务的发布仍然需要精心协调。² 虽然我经常听到有人认为微服务更容易实现持续交付但我更认同微服务在模块化方面的实际重要性。当然模块化与交付速度确实密切相关。运维复杂性采用微服务必须承担的成本能够快速部署小型独立单元对开发来说是一大优势但也会给运维带来额外压力。原本六个应用程序可能会变成数百个小型微服务。许多组织会发现管理如此大量且快速变化的服务对工具和流程都是巨大挑战。这进一步凸显了持续交付的重要性。对于单体架构而言持续交付是一项宝贵能力几乎总是值得投入精力去掌握但对于真正的微服务架构而言它更是必不可少。如果没有持续交付带来的自动化和协作根本无法有效管理数十个服务。由于管理和监控这些服务的需求增加运维复杂性也随之上升。因此如果要采用微服务架构就必须具备远高于普通单体应用所需的工程成熟度。微服务架构的支持者喜欢指出由于每个服务规模更小所以更容易理解。但危险在于复杂性并没有消失只是转移到了服务之间的连接关系上。这可能导致运维复杂性上升例如跨服务调试行为会变得更加困难。合理选择服务边界可以缓解这一问题但边界划分不当则会使情况更糟。应对这种运维复杂性需要掌握一系列新的技能和工具其中技能尤为重要。相关工具目前仍不够成熟但我的直觉是即使工具变得更加完善微服务环境对团队能力的最低要求仍然更高。然而提升技能和工具并不是应对这些运维复杂性的最难部分。要高效完成所有这些工作还需要引入 DevOps 文化加强开发人员、运维人员以及所有参与软件交付人员之间的协作。文化变革从来都不容易尤其是在规模较大、历史较久的组织中。如果未能提升技能并完成文化转型单体应用会受到阻碍而微服务应用则会遭受重创。对于跨职能团队而言借助 Worktile 这类通用项目协作系统统一管理任务、项目、文档、日历、甘特图、工时和审批等协作事项也有助于降低沟通成本让开发、运维和业务团队更容易围绕共同目标协同推进。技术多样性微服务带来的技术选择自由由于每个微服务都是一个可以独立部署的单元因此你在技术选择上拥有相当大的自由度。不同微服务可以使用不同的编程语言、不同的库以及不同的数据存储。这让团队能够针对具体任务选择合适的工具因为某些语言和库确实更适合解决特定类型的问题。关于技术多样性的讨论通常集中在如何选择最佳工具上。但微服务最大的优势往往体现在版本管理这个更实际的问题上。在单体架构中你通常只能使用某个库的单一版本这经常会造成升级困难。系统的某一部分可能需要升级以使用新功能却因为升级会破坏系统其他部分而无法推进。随着代码库规模扩大处理库版本问题的难度会呈指数级上升。这里也存在风险过多技术种类可能会让开发组织不堪重负。据我所知大多数组织都会鼓励团队只使用有限的几类技术。为了支持这种做法他们会提供一些通用工具例如监控工具以便让服务更容易坚持使用一组有限的通用运行环境。不要低估支持实验的价值。在单体系统中早期对语言和框架的选择往往很难逆转。十年左右之后这些选择可能会让团队困在不再合适的技术之中。微服务允许团队尝试新工具如果出现更先进的技术团队也可以逐个服务地逐步迁移系统。其他需要考虑的微服务因素我认为以上几点是需要考虑的主要权衡因素。下面还有几点值得注意但我认为它们的重要性相对较低。微服务架构的支持者经常说服务更容易扩展因为如果某个服务负载过高你可以只扩展这个服务而不必扩展整个应用。然而我很难想起有什么可靠的经验报告能够说服我这种选择性扩展实际上比复制整个应用进行统一扩展更有效。微服务允许你隔离敏感数据并为这些数据施加更严格的安全保护。此外通过确保微服务之间的所有通信安全微服务架构也可以帮助限制入侵影响。随着安全问题日益重要这可能会成为采用微服务的重要考量。即便不考虑这一点在主要采用单体架构的系统中创建独立服务来处理敏感数据也并不罕见。微服务的批评者认为测试微服务应用比测试单体应用困难得多。虽然这确实是一个难题——分布式应用本身就更复杂——但微服务也有一些有效的测试方法。最重要的是认真对待测试工作相比之下单体应用与微服务应用在测试上的差异反而是次要问题。总结什么时候应该选择微服务架构任何关于架构风格的通用文章都存在“通用建议的局限性”。因此阅读这类文章并不能替你做决定但可以帮助你确保自己考虑到了各种重要因素。对于不同系统而言每项成本和收益的权重都不一样甚至成本和收益本身也会相互转换。例如在复杂系统中强模块边界是一项优势但在简单系统中它可能反而是一种负担。你的任何决定都取决于如何将这些标准应用到具体情况中评估哪些因素对你的系统最重要以及它们会如何影响你的特定环境。此外我们对微服务架构的经验仍然相对有限。通常只有当系统成熟并且你了解了开发开始数年后人们如何与系统互动才能对架构决策做出判断。目前我们还没有太多长期运行的微服务架构案例。单体架构和微服务并不是简单的二元选择。两者本身都是边界模糊的概念这意味着许多系统都处在两者之间的灰色地带。此外也有一些系统既不完全属于单体架构也不完全属于微服务。大多数人包括我自己在讨论微服务时都会将其与单体架构进行对比因为将微服务与更常见的架构风格相比较是合理的。但我们必须记住有些系统无法简单归入其中任何一种类型。我更愿意把单体架构和微服务看作架构空间中的两个区域。它们值得被命名是因为它们具有一些值得讨论的有趣特征但任何明智的架构师都不会把它们视为对整个架构空间的完整划分。也就是说一个被广泛接受的总体观点是微服务存在“额外代价”——它会降低生产力而这种损失只有在更复杂的系统中才可能得到弥补。因此如果你的系统复杂度仍然可以通过单体架构管理就不应该采用微服务。不过围绕微服务的讨论热度不应让我们忽略那些真正决定软件项目成败的更重要因素。团队成员的能力、协作效率以及与领域专家的沟通质量等软性因素其影响远大于是否采用微服务。从纯技术层面看更重要的仍然是保持代码整洁、建立良好的测试体系并重视演进式架构。