微服务权衡
许多开发团队发现微服务架构风格优于单体架构。但其他团队发现它们是生产力下降的负担。像任何架构风格一样,微服务也带来成本和收益。为了做出明智的选择,您必须了解这些成本和收益,并将它们应用于您的特定环境。
2015年7月1日
强大的模块边界(优点)
微服务的第一个主要优势是强大的模块边界。这是一个重要的优势,但也是一个奇怪的优势,因为理论上没有理由微服务应该比单体拥有更强大的模块边界。
那么,我所说的强大的模块边界是什么意思?我认为大多数人会同意将软件划分为模块是件好事:将软件分成彼此解耦的块。您希望您的模块能够正常工作,这样如果我需要更改系统的一部分,大多数情况下我只需要了解该系统的一小部分就可以进行更改,并且我可以很容易地找到这部分。良好的模块化结构在任何程序中都有用,但随着软件规模的增长,其重要性呈指数级增长。也许更重要的是,随着开发它的团队规模的增长,它的重要性也越来越大。
微服务的倡导者很快就会引入康威定律,即软件系统的结构反映了构建它的组织的通信结构。对于大型团队,特别是如果这些团队位于不同的地点,那么将软件结构化以认识到团队之间的沟通频率将低于团队内部的沟通,这一点很重要。微服务允许每个团队负责相对独立的单元,并具有这种沟通模式。
正如我之前所说,没有理由单体系统不应该具有良好的模块化结构。[1] 但是许多人观察到,这似乎很少见,因此泥球是最常见的架构模式。事实上,对单体常见命运的这种沮丧是促使几个团队转向微服务的原因。模块之间的解耦有效,因为模块边界是模块之间引用的障碍。问题是,对于单体系统来说,绕过障碍通常非常容易。这样做可能是快速构建功能的有用战术捷径,但如果广泛使用,它们会破坏模块化结构并破坏团队的生产力。将模块放入单独的服务中会使边界更加牢固,从而使找到这些有害的变通方法变得更加困难。
这种耦合的一个重要方面是持久数据。微服务的一个关键特征是分散的数据管理,它指出每个服务管理自己的数据库,任何其他服务都必须通过该服务的 API 来访问它。这消除了集成数据库,这是大型系统中耦合的主要来源。
重要的是要强调,完全有可能使用单体来实现牢固的模块边界,但这需要纪律。同样,您可以获得泥球微服务,但这需要付出更多努力才能做错事。我认为,使用微服务会增加您获得更好模块化的可能性。如果您对团队的纪律有信心,那么这可能会消除这种优势,但随着团队的增长,保持纪律变得越来越困难,就像维护模块边界变得越来越重要一样。
如果您没有正确设置边界,这种优势就会变成劣势。这是先单体策略的两个主要原因之一,也是为什么即使那些更倾向于早期使用微服务的人也强调您只能在充分了解领域的情况下这样做。
但我还没有完成对这一点的警告。您只能在一段时间后才能真正判断系统是否保持了模块化。因此,我们只能在看到存在至少几年的微服务系统后才能真正评估微服务是否会导致更好的模块化。此外,早期采用者往往更有才华,因此在我们可以评估由普通团队编写的微服务系统的模块化优势之前,还需要进一步延迟。即使那样,我们也必须接受普通团队编写的是普通软件,因此我们不应该将结果与顶级团队进行比较,而应该将结果软件与在单体架构下会是什么进行比较——这是一个难以评估的反事实。
目前我只能根据我所认识的一些使用这种风格的人的早期证据来进行判断。他们的判断是,维护他们的模块要容易得多。
一个案例研究特别有趣。该团队做出了错误的选择,在系统上使用了微服务,而该系统的复杂程度不足以覆盖微服务溢价。该项目遇到了麻烦,需要进行救援,因此更多的人被投入到该项目中。此时,微服务架构变得有用,因为该系统能够吸收开发人员的快速涌入,并且该团队能够比单体更轻松地利用更大的团队人数。结果,该项目加速到比单体预期更高的生产力,使该团队能够赶上进度。结果仍然是负面的,因为软件的员工工时成本高于使用单体的情况,但微服务架构确实支持了快速增长。
分布式(缺点)
因此,微服务使用分布式系统来改进模块化。但是,分布式软件有一个主要缺点,那就是它是分布式的。一旦您使用分布式,就会产生一系列复杂性。我认为微服务社区不像分布式对象运动那样天真地看待这些成本,但复杂性仍然存在。
第一个是性能。您必须处于非常不寻常的位置才能看到进程内函数调用变成性能热点,但远程调用很慢。如果您的服务调用了六个远程服务,每个服务又调用了另外六个远程服务,那么这些响应时间加起来会产生一些可怕的延迟特性。
当然,您可以做很多事情来缓解这个问题。首先,您可以增加调用的粒度,这样您就可以减少调用次数。这会使您的编程模型变得复杂,您现在必须考虑如何批处理服务间交互。它也只会让你走这么远,因为你至少要调用每个协作服务一次。
第二个缓解措施是使用异步。如果您并行进行六个异步调用,那么您的速度现在只取决于最慢的调用,而不是所有延迟的总和。这可能是一个很大的性能提升,但会带来另一个认知成本。异步编程很难:很难做对,更难调试。但是,我听到的大多数微服务故事都需要异步才能获得可接受的性能。
紧随速度之后是可靠性。您期望进程内函数调用能够正常工作,但远程调用随时可能失败。对于许多微服务来说,潜在的故障点更多。明智的开发人员知道这一点,并为故障而设计。幸运的是,您处理异步协作所需的策略也适合处理故障,结果可以提高弹性。然而,这并不能弥补太多,您仍然需要为每个远程调用额外复杂地弄清楚故障的后果。
而这仅仅是分布式计算谬误的前两个。
这个问题有一些注意事项。首先,许多这些问题随着单体的增长而出现。很少有单体是真正自包含的,通常会有其他系统,通常是遗留系统,需要与之协作。与它们交互涉及通过网络进行通信,并遇到这些相同的问题。这就是为什么许多人倾向于更快地转向微服务来处理与远程系统的交互。这个问题也是经验有助于解决的问题,一个更有经验的团队将能够更好地处理分布式问题。
但分布式始终是一种成本。我总是很犹豫是否要使用分布式,我认为太多人过快地使用分布式,因为他们低估了问题。
最终一致性(缺点)
我相信您知道一些需要耐心才能使用的网站。您对某件事进行更新,它刷新了您的屏幕,但更新不见了。您等待一两分钟,点击刷新,它就出现了。
这是一个非常令人恼火的可用性问题,几乎肯定是由最终一致性的危害造成的。您的更新被粉色节点接收,但您的获取请求被绿色节点处理。在绿色节点从粉色节点获取更新之前,您将一直处于不一致窗口中。最终它会变得一致,但在那之前,您会怀疑是否出了问题。
像这样的不一致已经够令人恼火了,但它们可能更加严重。业务逻辑最终可能会根据不一致的信息做出决策,如果发生这种情况,那么诊断问题将非常困难,因为任何调查都将在不一致窗口关闭很久之后进行。
微服务由于坚持分散的数据管理,引入了最终一致性问题。对于单体来说,您可以在单个事务中一起更新一堆东西。微服务需要更新多个资源,而分布式事务是不受欢迎的(有充分的理由)。因此,现在,开发人员需要了解一致性问题,并弄清楚如何在代码后悔之前检测到事物不同步。
单体架构并非没有这些问题。随着系统规模的增长,对缓存的需求越来越大,以提高性能,而缓存失效是另一个难题。大多数应用程序需要离线锁,以避免长时间的数据库事务。外部系统需要无法与事务管理器协调的更新。业务流程通常比你想象的更能容忍不一致,因为企业通常更重视可用性(业务流程长期以来对CAP 定理有着本能的理解)。
因此,与其他分布式问题一样,单体架构并不能完全避免不一致问题,但它们受到的影响要小得多,尤其是在规模较小的时候。
独立部署(优点)
模块化边界和分布式系统复杂性之间的权衡一直存在于我从事这个行业的整个职业生涯中。但有一件事在过去十年中发生了明显的变化,那就是发布到生产环境中的角色。在 20 世纪,生产发布几乎普遍是一个痛苦且罕见的事件,需要进行昼夜不停的周末轮班,才能将一些笨拙的软件放到可以发挥作用的地方。但如今,熟练的团队频繁地发布到生产环境中,许多组织实践持续交付,允许他们每天多次进行生产发布。
微服务是 DevOps 革命后的第一个架构
-- Neal Ford
这种转变对软件行业产生了深远的影响,它与微服务运动密切相关。许多微服务工作是由部署大型单体架构的困难所引发的,在单体架构中,对单体架构一部分的微小更改可能会导致整个部署失败。微服务的一个关键原则是服务是组件,因此可以独立部署。因此,现在当你进行更改时,你只需要测试和部署一个小型服务。如果你搞砸了,你不会让整个系统崩溃。毕竟,由于需要为故障设计,即使你的组件完全失败,也不应该阻止系统的其他部分工作,尽管会有一些形式的优雅降级。
这种关系是双向的。由于许多微服务需要频繁部署,因此必须协调你的部署行为。这就是为什么快速应用程序部署和基础设施快速配置是微服务先决条件。对于任何超出基本内容的东西,都需要进行持续交付。
持续交付的最大好处是缩短了从想法到运行软件的周期时间。进行持续交付的组织可以快速响应市场变化,并比竞争对手更快地推出新功能。
虽然许多人将持续交付作为使用微服务的理由,但必须提到,即使是大型单体架构也可以持续交付。Facebook 和 Etsy 是最著名的两个案例。也有很多案例表明,尝试的微服务架构在独立部署方面失败了,其中多个服务需要仔细协调其发布[2]。虽然我确实听到很多人争论说,使用微服务进行持续交付要容易得多,但我对这一点的认同程度不如它们在模块化方面的实际重要性——尽管模块化自然与交付速度密切相关。
运营复杂性(缺点)
能够快速部署小型独立单元对于开发来说是一个巨大的福音,但它会给运营带来额外的压力,因为六个应用程序现在变成了数百个小型微服务。许多组织会发现处理如此大量快速变化的工具的难度是不可接受的。
这强化了持续交付的重要作用。虽然持续交付对于单体架构来说是一项宝贵的技能,几乎总是值得付出努力去获得,但对于严肃的微服务设置来说,它变得必不可少。没有持续交付所促进的自动化和协作,就无法处理数十个服务。由于管理这些服务和监控的需求增加,运营复杂性也随之增加。同样,对于单体应用程序来说有用的成熟度水平,如果微服务也包含在内,就变得必要了。
微服务支持者喜欢指出,由于每个服务都比较小,因此更容易理解。但危险在于,复杂性并没有消除,而是仅仅转移到了服务之间的互连上。这可能会导致运营复杂性增加,例如跨服务调试行为的困难。良好的服务边界选择会减少这个问题,但边界位置错误会使问题变得更糟。
处理这种运营复杂性需要一组新的技能和工具——最重要的是技能。工具仍然不成熟,但我的直觉告诉我,即使有了更好的工具,微服务环境中的技能门槛也更高。
然而,处理这些运营复杂性的最难的部分不是对更好技能和工具的需求。为了有效地完成所有这些工作,你还需要引入DevOps 文化:开发人员、运营人员以及参与软件交付的每个人之间的更多协作。文化变革是困难的,尤其是在规模更大、历史更悠久的组织中。如果你不进行这种技能提升和文化变革,你的单体应用程序将受到阻碍,但你的微服务应用程序将受到创伤。
技术多样性(优点)
由于每个微服务都是一个独立部署的单元,因此你在其中有很大的技术选择自由。微服务可以用不同的语言编写,使用不同的库,并使用不同的数据存储。这允许团队为工作选择合适的工具,某些语言和库更适合某些类型的问题。
关于技术多样性的讨论通常集中在最佳工具上,但微服务最大的好处往往是更平淡无奇的版本控制问题。在单体架构中,你只能使用库的单个版本,这种情况往往会导致升级问题。系统的一部分可能需要升级以使用其新功能,但由于升级会破坏系统的另一部分,因此无法升级。处理库版本控制问题是那些随着代码库规模增大而呈指数级增长的难题之一。
这里存在一个危险,那就是技术多样性太多,以至于开发组织可能会不堪重负。我所知道的多数组织都鼓励使用有限的技术集。这种鼓励得到了供应通用工具的支持,例如监控工具,这些工具使服务更容易坚持使用一小部分通用环境。
不要低估支持实验的价值。对于单体系统来说,早期对语言和框架的决定很难逆转。十年左右之后,这些决定可能会将团队锁定在笨拙的技术中。微服务允许团队尝试使用新工具,并且还可以逐步迁移系统,一次迁移一个服务,如果出现更高级的技术变得相关。
次要因素
我认为以上内容是需要考虑的主要权衡。这里还有几件事,我认为不太重要。
微服务支持者经常说,服务更容易扩展,因为如果一个服务获得了大量负载,你可以只扩展它,而不是扩展整个应用程序。但是,我很难回忆起一份令人信服的经验报告,证明这种选择性扩展实际上比通过复制完整应用程序进行模板扩展更有效。
微服务允许你分离敏感数据,并为该数据添加更严格的安全性。此外,通过确保微服务之间的所有流量都是安全的,微服务方法可以使利用入侵变得更加困难。随着安全问题变得越来越重要,这可能会成为使用微服务的重大考虑因素。即使没有这些,对于主要使用单体系统的系统来说,创建单独的服务来处理敏感数据也不足为奇。
微服务的批评者谈论了测试微服务应用程序比测试单体架构更困难。虽然这是一个真实的困难——分布式应用程序复杂性的一个部分——但有一些使用微服务进行测试的良好方法。这里最重要的是要有纪律认真对待测试,相比之下,测试单体架构和测试微服务之间的差异是次要的。
总结
任何关于任何架构风格的通用文章都存在通用建议的局限性。因此,阅读像这样的文章并不能为你制定决定,但这些文章可以帮助你确保你考虑了应该考虑的各种因素。这里每个成本和收益对于不同的系统将有不同的权重,甚至在成本和收益之间进行交换(强大的模块边界在更复杂的系统中是好的,但在简单的系统中是一个障碍)。你做出的任何决定都取决于将这些标准应用于你的环境,评估哪些因素对你系统最重要以及它们如何影响你的特定环境。此外,我们对微服务架构的经验相对有限。你通常只能在系统成熟之后,并且你了解了在开发开始后几年内使用它的感觉之后,才能判断架构决策。我们还没有关于长期存在的微服务架构的很多轶事。
单体架构和微服务不是简单的二元选择。两者都是模糊的定义,这意味着许多系统将位于模糊的边界区域。还有其他系统不属于这两个类别。包括我在内的多数人谈论微服务是为了与单体架构形成对比,因为将它们与更常见的风格形成对比是有意义的,但我们必须记住,有些系统并不适合这两个类别。我认为单体架构和微服务是架构空间中的两个区域。它们值得命名,因为它们具有有趣的特征,值得讨论,但没有一个明智的架构师将它们视为架构空间的全面划分。
也就是说,一个似乎被广泛接受的概括性总结点是存在微服务溢价:微服务会对生产力造成成本,只有在更复杂的系统中才能弥补。因此,如果你可以用单体架构管理系统的复杂性,那么你不应该使用微服务。
但微服务对话的音量不应该让我们忘记驱动软件项目成功和失败的更重要问题。团队中人员的素质、他们之间如何协作以及与领域专家的沟通程度等软因素,将比是否使用微服务产生更大的影响。在纯粹的技术层面上,更重要的是关注代码清洁、良好测试和关注演进式架构等方面。
脚注
1: 有些人认为“单体架构”是一种侮辱,总是暗示模块化结构很差。微服务世界中的多数人不会这样做,他们将“单体架构”纯粹定义为构建为单个单元的应用程序。当然,微服务倡导者认为,大多数单体架构最终都会变成“一团乱麻”,但我不知道有任何倡导者会争辩说不可能构建一个结构良好的单体架构。
2: 能够独立部署服务是微服务定义的一部分。因此,可以合理地说,必须协调其部署的一组服务不是微服务架构。也可以合理地说,许多尝试微服务架构的团队最终遇到了麻烦,因为他们最终不得不协调服务部署。
进一步阅读
Sam Newman 在他书的第一章(构建微服务系统的基本来源)中列出了微服务的优势。
Benjamin Wootton 在 High Scaleability 上发表的博文微服务 - 不是免费午餐!是最早也是最好的总结微服务缺点的文章之一。
致谢
Brian Mason、Chris Ford、Rebecca Parsons、Rob Miles、Scott Robinson、Stefan Tilkov、Steven Lowe 和 Unmesh Joshi 与我讨论了本文的草稿。