自微服务这个概念诞生以来,就伴随着诸多热议。人们要么爱它,要么恨它,似乎没有什么中间地带。

在微服务如日中天的几年中,很多公司都尝试进行了微服务转型。彼时,微服务架构提供了一种新颖的重构现有系统的方法,并以提供模块化、可扩展性、可用性的能力成为软件开发行业的新宠。

但任何一种架构都不会是适配所有问题的万能钥匙,微服务也不例外。近年来,一些公司放弃微服务实践、选择退回单体架构的消息也不时出现在大众视野。不久前,GitHub前CTO Jason Warner更是直接在推特上表示,“我确信过去十年中,最大的架构错误之一就是全面使用微服务。”一时掀起涟漪无数。

我们知道,微服务不会适用于所有公司,但无论是选择它还是放弃它,其背后的根由依旧值得深思。

1、为什么我们选择微服务

2003年左右,诸如DDD和EIP之类的软件设计开始流行,一些团队尝试将应用程序开发为模块化服务,但传统的基础结构对模块化部署并不友好。而随着云计算的发展,云托管的普及,像Heroku这样的平台又为模块化部署提供了便利。这也为微服务打造细粒度和可重用的功能提供了可能性。

传统的单体架构优缺点都很鲜明,优势在于早期开发简单,易于对程序做重大更改,但随着业务发展也会逐渐暴露出开发效率低下,伸缩性差,缺乏故障隔离等问题,沦为“单体地狱”。

微服务的出现则提供了一种架构思维上的新解:将其中服务按照业务域分为多个块,相应地,代码可以被分解成更小的部分,可以独立地开发、测试、部署和更新。它提供了复杂应用程序的持续交付,使应用程序更易于理解,开发,测试,并且对架构侵蚀更具弹性。尤其在开发大型应用程序时,这种优势会愈发突出。

在微服务相关的推荐文章中,对微服务的褒奖大体可归因为以下几种:

  • 保证服务的可伸缩性:由于每个服务都是独立的组件,因此可以使用更多容器部署扩展,从而实现更有效的容量规划。
  • 降低耦合简化了团队协作:单一用途设计意味着它们可以由较小的团队构建和维护。每个团队可以是跨职能的,同时也可以专注于解决方案中的一个微服务子集。
  • 改善了故障隔离:在微服务架构中,如果单个模块受到影响,则可以轻松拆卸或解决,而不会影响应用程序的其他部分。
  • 易于修改技术堆栈:通过微服务,软件开发公司可以在特定组件上尝试新的堆栈或最新技术。由于没有依赖性问题,软件开发人员可以避免使用特定的技术堆栈。大数据世界中的各种技术,包括开源社区,在微服务架构中都能很好地工作。
  • 提升开发效率提高生产力:不同的团队同时处理不同的组件,而无需等待一个团队完成任务。从而提升工作效率,加快产品发布速度。

可以肯定的是,以微服务架构搭建的系统提供了大量的好处,比如独立部署、强大的子系统边界、保障了技术多样性等等,这也是微服务架构一度为众多公司所青睐的原因。不过,凡事一体两面,微服务架构同样有其弊端。

3、我们需要的不是微服务,而是模块

微服务在解决了很多单体架构的痼疾后也带来了其他挑战。我们可以列举其中几个:

首先,判断是不是适合微服务化,也要看具体的业务场景。如果只是为了拆分而拆分,那无疑没有意义。而且微服务在设计上也更为复杂,比如,拆成几个微服务?新需求来了,是新建一个微服务还是在老系统上改造?都需要综合考量。可以说,一个设计糟糕的微服务架构比一个设计糟糕的单体架构要麻烦得多。

其次,提高了开发的技术门槛。鉴于微服务是一个分布式系统,它也带来了开发分布式应用程序相关的复杂性的代价,比如独立的数据模型、微服务之间的弹性通信、最终一致性和操作复杂性等。开发和运行大规模的分布式服务并非易事。随着企业发展而拥有的分布式系统,引入数十个微服务进行推理已经很难了,更不用说数百个各有风险的微服务。

再者,增加了运维的复杂性。某种程度上,微服务所提供的敏捷性和开发效率是以增加操作复杂性为代价的。服务被拆成了多个微服务,每个微服务又会部署很多套,人肉运维显然就不合适了。另外,所有服务可能都需要群集以实现故障转移和弹性。你的系统可能具有数十个单独的组件,并且在添加新功能时,它将变得越来越复杂。

在稍稍权衡了微服务的褒贬两面时,我们需要重新审视的是,当谈起微服务时,我们看重的到底是什么;当我们选择微服务时,我们关注的核心到底是什么。

其实,在上文中列举的关于微服务的优点中,稍加留心就可以发现,其中大半论点都折射出了一个共同的主题:创建和维护小的、独立的代码和数据“块”的想法,它们彼此分开,使用公共的输入和输出来实现更大的系统集成。所有都指向了微服务的本质关键词——模块。

太阳底下无新事。

自20世纪70年代以来,“模块”这个概念一直是大多数编程语言的核心。CLR (c#, f#, Visual Basic…)上的“程序集”,JVM (Java, Kotlin, Clojure, Scala, Groovy…)上的“jar”或“包”,或者操作系统的动态链接库(Windows上的dll, sos或*nixes上的dll,当然macOS有隐藏在/Library目录中的“框架”)。不管形式如何,在概念层面上,它们都是模块。它们都有不同的内部格式,但都有相同的基本目的:可以重用的,独立被构建、被管理、被部署的代码单元。

来自David Parnas的论文《关于将系统分解为模块的标准》写于1971年,其中定义良好的“独立的、不同的程序模块”涵盖了微服务本质上的多数优点。可以说,对于系统“模块化”的追求已经有半个世纪的悠久历史。那么微服务又为何会引来诸多追捧呢?因为微服务与其说是解决了技术的问题,更多的是解决了人的问题,其关键词不是服务,也不是分布式系统,而是组织清晰度。

3、是架构问题,更是“人”的问题

如果你做过微服务相关工作的话,那么可能听说过“康威定律”。这是Melvin Conway在1967年提出的一个观点,大致意思是一个组织的系统通常被设计成这个组织沟通结构的副本。

organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations.

— M. Conway

简言之,你想要什么样的系统设计,就架构什么样的团队。解决方案是是围绕团队结构(和团队通信开销)进行“优化”的,而不一定是为了解决特定的技术或性能问题。最好通过业务来划分团队,如此一来,能让团队自然的自治自洽,明确业务边界会减少跨界的沟通成本,每个小团队都对自己的模块的整个生命周期负责,权责明确,没有无效的扯皮推诿踢皮球。

微服务可以说会在一定程度上倒逼组织结构。通常IT公司的岗位会划分为产品、开发、测试、运维等等,有些公司甚至会划分成不同部门。一个需求从开发到上线,会在不同部门间不断流转,而微服务化往往是为了加速业务响应,减少开发团队所面临的依赖关系。

在微服务架构下,其组织架构也必然要做出相应调整,这个团队要么必须由各种具备不同技能的人员组合而成,要么每个团队成员具备完整的技能(所谓的 "全栈式开发")。同时这个团队也要对一个或多个微服务的迭代和运维负责。当然,康威定律的收益在很大程度上依赖于在哪里划分边界以及这些边界随时间的推移该如何变化。

所以说在尝试微服务之前,首先要考虑清楚的是,是否真的需要减少开发团队所面临的依赖关系,团队对他们正在尝试构建的东西是否有清晰的愿景,以及是否愿意承担可能因此导致的风险。

软件服务公司InVision的技术架构经历了从微服务合并回单体架构的过程,其技术人员Ben Nadel曾如此描述这种抉择的原因:

“多年来,InVision必须要从组织和基础设施方面进行发展。这意味着,在其背后,有一个较老的‘遗留’平台和一个不断发展的‘现代’平台。随着越来越多的团队迁移至‘现代’平台,这些团队之前负责的服务则要移交给剩下的‘遗留’团队。”

遗憾的是,Ben Nadel的团队就是“遗留”团队。“这个团队已经缓慢,但稳定地负责越来越多的服务。这意味着:人数变少了,但是代码仓库变多了,编程语言变多了,数据库变多了,监控仪表盘变多了,错误日志变多了。”

简而言之,康威定律为组织带来的所有收益,随着时间的推移,都变成“遗留”团队的负债。“所以,我们一直在努力‘调整’责任域,让平衡回归康威定律。或者,换句话说,我们在尝试改变服务边界以匹配团队的边界。这意味着,将微服务重新合并为单体架构。”

正因为微服务不只是涉及到技术架构的问题,当它牵涉到“人”的问题时,往往会导致“水能载舟亦能覆舟”的效果。

4、结语

很多公司尝试过微服务转型,有的转型成功,有的中途放弃。原因多种多样,毕竟采用微服务,实际是在转移复杂性,而不是消解复杂性。

如果你的系统不够大,团队成员不同多,现有的技术架构完全可以满足业务需求,那就根本没必要选择微服务。微服务的重点从来不是“微”,而是成为“大小合适”的服务,负责“合适数量”的功能。这里的合适也并非静态标准,它往往取决于团队的技能集、组织状态、投资回报率等多重因素。更重要的是,这也并非是单纯的技术领域的选择,同样是关于“人”和“组织”结构之间的取舍。