本文最初发表于 Ben Nadel 的个人网站,经作者同意由 InfoQ 中文站翻译并分享。
每当我们将InVision的一个微服务合并回单体的时候,我都会发一条庆祝的推文。在这些推文中,我都喜欢包含一张关于灭霸的动图,这张动图就是灭霸将最后一颗无限宝石放到无限手套中。我觉得这张动图非常合适,因为集齐宝石给了灭霸巨大的能量,就像重新整合微服务给了我和团队力量一样。
曾经有多次,许多人问我为何要干掉微服务。所以,我想和大家分享在 Web 开发领域这一段旅程的特殊见解。
我并不是“反微服务”
开篇,澄清一下,我并不是反微服务。
我将服务重新合并成单体并不意味着要将微服务从生活中彻底驱赶出去。这个任务的目标是“调整”单体的大小。这样做是为了解决团队的痛点。如果不能减少冲突,那么我就不会花费那么多的时间(和机会成本)来提升、转移和重构旧的代码。
每当这样做的时候,我都冒着引入新 bug 和破坏用户体验的风险。将微服务合并成单体架构的过程中,虽然有时候很令人兴奋,但总体还是令人感到恐惧的,而且需要大师级的规划、风险控制和测试。再次强调一遍,如果它不值得做的话,我就不会做了。
微服务解决了技术和人的问题
为了理解我为何要销毁一些微服务,首先很重要的一点就是要理解为什么一开始要创建这些微服务。
微服务解决的是两种类型的问题,也就是技术的问题和人的问题。
技术的问题在于应用程序的某个方面给基础设施带来了过重负担,这反过来又很可能会导致糟糕的用户体验(UX)。例如,图像处理需要大量的 CPU。如果 CPU 的负载变得非常高,这将会导致应用其他处理资源的饿死现象。这会影响系统的延迟。而且,如果更糟糕的话,那么这会影响系统的可用性。
另一方面,人的问题与应用的关系并不大,它关系到你会如何组织团队。在应用程序的特定部分,投入工作的人越多,开发和部署就会越慢,而且越容易出错。例如,如果你有 30 个工程师都在竞争“持续部署(CD)”同一个服务,就会出现很多排队的现象,这意味着很多本可以交付产品的工程师只能坐等轮到他们进行部署。
早期,InVision 微服务主要解决了“人”的问题
自 8 年前成立以来,InVision]一直都是一个单体系统,当时只有 3 个工程师。随着公司的成长和发展,系统的数量几乎没有增加,但是工程团队的规模却在快速扩张。几年的时间,我们有了几十名工程师,包括前端和后端,他们在同一个代码库上开展工作,所有的部署都会到同一个服务队列中。
如前文所述,大量的人在同一个地方工作会产生很大的问题。不仅各种团队会竞争相同的部署资源,而且每当遇到“紧急事件”的时候,多个团队的代码都需要回滚。而且,在处理事件的时候,所有团队都不能进行部署。可以想象,这会在整个组织造成很多摩擦,对工程团队和产品团队均是如此。
同时,“微服务”的出现是解决“人的问题”的。一群选定的工程师围绕他们认为的与团队边界相对应的应用组成部分开始划分边界。这样做的目的是为了让团队能更加独立地工作,独立地部署,从而发布更多产品。早期的 InVision 微服务几乎与解决技术问题毫无关系。
如果你的边界处理得很好的话,那康威定律是很不错的
如果你从事微服务的话,那么肯定听说过“康威定律”,它是由Melvin Conway在 1967 年提出的:
任何设计系统(广义定义)的组织,必然会产生以下设计结果:即其系统的结构就是该组织沟通结构的写照。
该定律通常以“编译器”为样例进行说明:
如果有四个组在从事一个编译器相关的工作,那么这个编译器肯定是要分四个步骤的。
这里的观点在于,解决方案是围绕团队结构(和团队通信开销)进行“优化”的,而不一定是为了解决特定的技术或性能问题。
在微服务出现之前,康威定律一般都是以负面的角度来进行讨论的。比如,康威定律代表着你的应用规划和组织比较糟糕。但是,在后微服务时代,康威定律有了更多的维度。因为事实证明,如果你能将系统分解为一组具有内聚边界的独立服务,那么就能以更少的 bug 发布产品,因为你所创建的团队更专注于一组服务,而这些服务具备更窄小的职责范围。
当然,康威定律的收益在很大程度上依赖于在哪里划分边界以及这些边界随时间的推移该如何变化。这也就是我和我的 Rainbow 团队的职责所在。
多年来,InVision必须要从组织和基础设施方面进行发展。这意味着,在其背后,有一个较老的“遗留”平台和一个不断发展的“现代”平台。随着越来越多的团队迁移至“现代”平台,这些团队之前负责的服务则要移交给剩下的“遗留”团队。
如今,我的团队就是遗留团队。这个团队已经缓慢,但稳定地负责越来越多的服务。这意味着:人数变少了,但是代码仓库变多了,编程语言变多了,数据库变多了,监控仪表盘变多了,错误日志变多了。
简而言之,康威定律为组织带来的所有收益,随着时间的推移,都变成“遗留”团队的负债。所以,我们一直在努力“调整”责任域,让平衡回归康威定律。或者,换句话说,我们在尝试改变服务边界以匹配团队的边界。这意味着,将微服务重新合并为单体架构。
微服务的关键不在于“微”,而在于“合适的大小”
也许,微服务架构最糟糕的事情就是用了“微”这个字。“微”是一个毫无意义,但具有沉重负担的术语,它实际上代表了历史原因和人们的偏见。更合适的术语应该是“合适的大小”。微服务的目的从来不是成为“小的服务”,而是成为“大小合适”的服务。
“微”这个术语毫无意义,不代表任何实际的内容。而“合适的大小”意味着服务经过了恰当地设计,以满足其需求:它负责“合适数量”的功能。而且,至于什么是“合适”并不是一个静态的概念,它取决于团队,即团队的技能集、组织的状态、投资回报率(return-on-investment,ROI)的计算、持有成本以及该服务运行的时间点。
对我的团队来说,“合适的大小”意味着更少的代码仓库,更少的部署队列,更少的语言以及更少的运维仪表盘。对于这个非常小的团队来说,“合适的大小”更多的是关于“人”,而不是关于“技术”。所以,就像 InVision 最初引入微服务是解决“人的问题”一样,我们团队现在摧毁这些微服务也是为了解决“人的问题”。
姿态是一样的,只是表现形式有所不同。
我为团队在遗留平台上所付出的努力而感到自豪。虽然团队规模不大,但是我们利用自己所拥有的东西完成了很多的工作。我把这种成功归功于我们对遗留平台的深入了解,积极的实用主义,以及不断努力设计一个能对应我们能力的系统,而不是试图扩大我们的能力以满足系统需求。这可能听起来很狭隘,但是,这是我们团队及其所拥有的资源在这个时刻唯一可行的方法。
后记 1:大多数技术并不需要“独立扩展”
支撑创建独立服务的论据之一就是这些服务是可以“独立扩展”的。也就是说,可以更有针对性地提供服务器和数据库,以满足服务的需求。因此,与其创建大型的服务却只扩展其中一部分功能,我们能够在独立扩展其他服务的同时,让某些服务保持规模很小的状态。
在所有关于为什么说独立服务是一件“好事”的理由中,这个理由经常被提及,但是在我(有限的经验)看来,这通常是不成立的。除非某项功能是 CPU 密集、IO 密集或内存密集的,否则独立的可扩展性可能并不是你需要担心的“能力”。很多时候,你的服务器都在等着做事情,为一个应用程序添加“更多的 HTTP 路由处理程序”并不会突然耗尽它所有的资源。
如果我能回到过去,重新尝试我们的微服务,我 100%会先关注所有 “CPU 密集”的功能:图像处理和调整大小、缩略图生成、PDF 导出、PDF 导入、使用rdiff的文件版本管理、ZIP 压缩文件生成。
我会沿着这些边界组织团队,让他们创建“纯的”服务,只处理输入和输出(即,没有“集成数据库”,也没有“共享文件系统”),这样其他所有的服务都可以使用它们,同时保持了松耦合。
我并不是说这样就能解决我们所有的问题--毕竟我们的“人的问题”比 “技术的问题”要多。但是,它可以解决一些“合适性”的问题,从长远来看,这可能会让生活变得更轻松一些。
后记 2:微服务也有纯经济方面的成本
服务并不是在抽象地运行:它们要在服务器上运行,与数据库通信,报告度量指标并且还会生成日志条目。所有的这些都需要真正的经济成本。所以,尽管“lambda 函数”在我们不使用它们的时候,不会花掉我们的钱,但是大多数“微服务”肯定还是会花钱的。特别是当我们为了创建一个“高可用”的系统而需要维护冗余的时候。
我的团队将微服务重新合并成单体架构对业务带来了切实的经济影响(这种影响是正面的)。这种影响并不巨大,我们只是在谈论一些小型的服务,但也不是微乎其微。所以,在将系统合并之后,我们不仅得到了“人”的方面的收益,还得到了实际的经济收益。