文本转自:InfoQ 架构头条
微服务是一种非常流行的 Web 软件架构,有着大量知名的实践者和支持者。
Facebook、Uber、Groupon、Klarna、Amazon、Netflix、eBay、Comcast 等公司都采用了微服务架构。
你的公司可能与这些公司不同,也就是说,你的团队可能和这些公司的团队一点都不像,你可能不会面临和他们一样的问题。
如果你就在这些公司当中,那么请停止阅读本文,因为你确实可能需要微服务。
微服务提供了许多所谓的好处。但问题的本质不在于微服务是否提供了这些好处,而在于有些问题并非只有微服务能够解决。
在某种程度上,这些问题中的大部分(如果不是全部的话)都可以通过单体服务来解决。
当有人例举微服务架构的优点时,其潜台词就是——为了解决这些问题,必须采用微服务架构。
事实上,微服务架构就是要你通过增加额外的层和一定程度的灵活性来解决这些问题。
这里的关键是增加额外的东西。
微服务不是免费的,它的构建和维护成本是出了名的高。
如果你确实需要额外的灵活性,那么从整体上看,付出额外的成本可能可以获得相应的回报。这个时候,也许微服务架构是正确的选择,也许你可以认真考虑一下。
但如果你不需要额外的灵活性呢?那么你的技术栈就是过度设计了,这将严重阻碍你的团队向客户交付价值的能力。
接下来,我们将列出微服务最经常被提及的好处,并看看如何用单体服务来达到同样的目的。
在微服务架构中,应用程序的每一个功能都运行在自己的资源上,并且这些资源可以独立伸缩。这将为你提供高度的控制能力,你可以精确地控制分配给每个功能的资源的数量和类型。
但你真的需要这种程度的控制能力吗?
应用程序的不同功能是否需要处理不同级别的负载?
它们会以不同的速度增长吗?
它们在 CPU、内存、存储和 GPU 方面有不同的要求吗?
对于许多团队来说,通过简单地增加服务器数量或升级服务器配置就可以解决资源差异问题,这样做的成本更低。也就是说,在大多数情况下,不优化基础设施更符合成本效益。
解决单体服务的性能问题和瓶颈比迁移到新的架构更容易。这与具体的技术栈有关,但应该不需要太大的工作量。
如果你有多台服务器,可以在这些服务器前面运行某种负载均衡器。你可以利用这个负载均衡器将流量路由到独立可伸缩的集群中。
你还可以使用独立可伸缩的队列将异步任务放在后台执行。不过你需要确保有足够多的队列来实现控制粒度,保持基础设施成本位于合理的水平。
应用程序的单个故障不影响其他特性,这是设计合理的微服务架构能够为我们带来的一个好处。
将不同类型的流量路由到不同的集群,这样有助于避开某些故障。
总结一下大多数历史错误来自哪里,这样你就知道该如何更好地路由流量。如果你想解决“吵闹的邻居”问题,可能需要基于帐户 ID 进行路由。如果你有一个脆弱但不那么重要的功能,很容易拖垮服务,那么可以把它的流量路由到它自己的应用实例集群中。如果你有经常出问题的后台作业,请将它们放入自己的队列中,避免影响了其他作业。
预防胜于治疗。如果你能够防止或减少故障数量,那就不需要过多地担心如何隔离它们。
关键在于要培养一种健康的测试文化,确保对交付的产品的质量有足够的信心。这种信心不仅是指能够可靠地交付所需的功能,而且需要能够应对生产负载的压力。
在为服务引入新的编程语言和技术时,如果服务不是独立的,就没有太多选择。
在大多数情况下,这更像是一个特性而不是一个 Bug。在选择编程语言和技术时,给工程师太多的选择可能会导致技术栈过于分散和复杂。
我们应该拥抱单体服务的简单性和一致性。
如果你确实对特定编程语言有特定的需求,可以考虑使用单独的服务,但你需要权衡额外的技术所带来的好处和它的维护成本。
你觉得保护单体服务很难吗?在微服务架构中,这项工作会更加复杂。技术栈复杂性的增加只会增加攻击表面积。
确实,将功能隔离到不同的服务中有助于对每个服务应用不同级别的安全性。但是,我们还需要考虑一下这种级别的控制是否是必需的。
将整个单体服务的安全性设置为最高级别是否更容易些?
你的数据是否有不同的安全性需求?
我非常喜欢自治的跨职能团队,但令我感到困惑的是为什么要通过引入边界来实现团队自治。
让每个团队拥有特定的独立系统似乎是一种提高团队自治的方法,但实际上,它可能会适得其反。
假设我的团队需要修改由另一个团队维护的功能。在微服务架构中,我需要先了解这个服务才能对其做出修改,甚至可能需要等待这个服务所属的团队来做出变更。如果这个功能是在单体系统中,那么我很有可能已经很熟悉它的代码,或至少熟悉它的约定。
团队自治的程度取决于模块化的好坏和系统的一致性,无论是单体还是微服务都无法在这两方面同时保证或破坏团队自治。微服务要求系统的模块化,而单体倾向于鼓励更多的一致性。
从本质上讲,微服务架构将迫使你对系统进行模块化。在这里,单体并不能提供太多帮助(尽管你所选择的单体框架可能会有所帮助),但它们也不会妨碍你实现模块化。
我们可以考虑使用具有有限关注点的松散耦合的模块化代码,无论你是否碰巧在这些关注点之间增加了边界复杂性。
虽然微服务强制进行模块化,但并不能保证良好的模块化。如果没有经过充分的设计,微服务很容易成为紧密耦合的“分布式单体”。
如果你无法成功地将一个单体模块化,也就无法构建出一个成功的微服务架构。
微服务强制进行模块化,但要实现良好的模块化是很困难的。
在进行独立部署时,你需要做很多变更,这些会成为你的日常工作,并且会成为瓶颈。
协调大量的服务部署将使快速发布变得更加困难和复杂。
在单体系统中,我们也可以将复杂、有风险的变更分解为单独的部署。一个常见的例子是使用单独的 PR 和部署进行向前或向后兼容的迁移。
这显然增加了与微服务类似的发布复杂性。你可能不会轻率地使用它,但它确实提供了微服务的一些好处,所以你不必在每次发生变更时都使用它。
在微服务架构中,你可以为每一个服务使用独立的依赖项,但你真的需要这么做吗?
在一个大型的单体系统中管理依赖项已经足够困难了,虽然将它分解成几个较小的服务可以简化单个服务的管理,但从整体上看却进一步复杂化了。
对于单体系统,你迟早会陷入“依赖地狱”,并出现依赖冲突。微服务并不能确保可以完全避免这种情况,但应该可以减少出现问题的可能性。
保持依赖项最新显然是可取的,但在现实当中,通常允许漏掉一些东西。
保持依赖项最新可用版本可能会加剧这个问题,因为一个依赖项可能比其他相关依赖项发布得更快。但不管怎样,“保持最新”并不意味着在任何情况下都要更新到最新版本。
这个好处往好了说是有点虚伪的,往坏了说就是赤裸裸的谎言。
当然,每一个微服务都更简单,更容易理解,但整个系统会更加复杂,更难理解。你并没有消除复杂性,反而增加了复杂性,只是把它转移到了其他地方。
我们没有必要为了让工程师更容易理解代码而引入边界和隔离。
将单体分解成具有明确定义和有限关注点的模块与将一个系统分解成单独的服务相比,理解起来同样容易(如果不是更容易的话)。
如果你正在遭遇单体的痛点,那可能是因为你的单体存在设计问题,而不是因为它是一个单体。
你可能已经努力通过使用工具和进行模块化来改进单体的代码质量,如果还在经历痛点,那么可能是时候考虑微服务了。但如果你还没这么做,可能只需要尝试改进你的单体。
开发软件并非易事,开发一个包含了大量随时间变化的活动部件的大型系统更是难上加难。
我不想评判任何人,但如果你认为你所面临的问题都可以通过微服务来解决,甚至简化,那么你将陷入痛苦的深渊。
单体和微服务是两种相互排斥的思维模式。新旧之别,对错之别,二选一。
事实是,它们都是有效的方法,但需要做出不同的权衡。选择哪一个取决于具体的情况,而且需要经过深度的思考。
这个选择本身并不是简单的二选一,在某些情况下,需要在逐个特性的基础上做出选择,而不是简单地针对整个组织的工程团队做出选择。
这要视情况而定。你可能可以从微服务架构中受益。
在某些情况下,微服务确实可以为我们带来好处,但如果你是一个中小型的团队或处于项目的早期,那么你可能不需要微服务。
如果你看一下整个行业,你会发现无数关于微服务的警示故事,以及它们给团队带来的问题。
Segment 就是一个非常典型的逃离微服务架构的例子。
这同样适用于单体与微服务的选择,在执行过程中出了问题并不意味着它们存在根本性的缺陷。
如果你打算采用微服务架构,你需要睁大眼睛,接受折衷方案,并为取得成功准备好所需的资源。
对于中小型的工程团队来说,单体应该仍然是默认的选择。微服务也是一种选择,但你需要有足够的理由来证明采用它是合理的。
对于中大型团队,可以考虑使用微服务,但要注意做出合理的权衡。