许多开发团队认为,微服务架构优于单体架构;也有一些团队认为,微服务反而会降低生产力。和任何架构风格一样,微服务既有优势,也有劣势。要做出明智选择,就必须充分理解这些利弊,并结合具体场景进行判断。
本文将围绕微服务架构的核心权衡展开,重点分析微服务相对于单体架构在模块边界、分布式复杂性、最终一致性、独立部署、运维复杂性和技术多样性等方面的优势与代价。

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