发布日期

服务架构演进史

作者
  • avatar
    Name
    BroZhong
    Twitter

作为后端程序员,架构知识是一点一点在工作中捡的:数据一致性踩坑了,知道了事务的重要性;服务改用 K8s 部署,真切体会到了声明式 API 给运维带来的便利性;性能遇到瓶颈了,我们加上缓存系统。

每块都懂一点,但是这些知识之间是什么关系,为什么要这么设计,在我的心中一直没有形成体系化的思考,直到我读到周志明老师的《凤凰架构》这本书。

这本书完整地梳理了服务端的知识地图,将 why 和 what 的问题讲得非常清楚,给了我非常清晰的宏观视角。读完最大的感受是:每一代架构风格的诞生,都是因为上一代遇到了它解决不了的具体问题。这些问题的性质各不相同,但贯穿始终的暗线是复杂度 —— 复杂度不会消失,只会转移。这是本书读后感的第一篇,我试着沿这条线把故事串起来。

原始的分布式

通常我们会认为,服务架构是从单体开始的,为了逃离单体大泥球架构的地狱,才搞分布式。

但历史上恰恰相反。对分布式架构的探索,从 20 世纪 70 年代就开始了。那时单机算力极其有限,16 位处理器、不到 5MHz 的主频,单机直接卡住了软件能做到的规模上限。人们不得不寻找多台计算机协作支持一套软件系统的方案。

这些早期研究带着浓厚的 UNIX 设计风格:「保持接口与实现的简单性,比系统的任何其他属性,包括准确性、一致性和完整性,都来得更加重要」。理想很美 —— 远程调用应该尽可能透明,开发者无需关心自己调的是本地方法还是远程服务。

但一旦触碰到”远程”二字,网络的不确定性便会带来相当的复杂度。远程的服务在哪里(服务发现),有多少个(负载均衡),网络出现分区、超时或者服务出错了怎么办(熔断、隔离、降级),方法的参数与返回结果如何表示(序列化协议),信息如何传输(传输协议),服务权限如何管理(认证、授权),如何保证通信安全(网络安全层),如何令调用不同机器的服务返回相同的结果(分布式数据一致性)—— 每一个都需要设计者耗费大量精力。

这些探索催生了 RPC、DFS 等概念,人们也得到了一个价值千金的教训:「某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果」。

原始分布式时代的故事,是一次发现复杂度的过程。探索者试图用"透明调用"把分布式的复杂度屏蔽掉,让开发者像写本地程序一样写分布式程序。但现实证明,网络带来的不确定性是无法假装不存在的 —— 服务发现、一致性、容错,这些问题不会因为你不看它就消失。这次失败的意义不在于产出了什么可用的系统,而在于让整个行业认清了分布式复杂度的真实面貌。

于是当硬件性能随摩尔定律起飞后,人们做了一个务实的选择:既然分布式的复杂度屏蔽不了,那就别分布式了。

单体系统时代

软件退回到单体 —— 所有代码跑在同一个进程里,不用想网络,不用想一致性。

大型的单体系统,经常是微服务书籍批判的对象。但一定要注意这里的定语 —— “大型的”。小型单体系统,不仅易于开发、测试、部署,且由于系统中各个功能、模块、方法的调用过程都是进程内调用,没有进程间通信,运行效率也很高。三个人的团队、一台机器撑得住的系统,搞微服务纯粹是给自己找麻烦。

单体系统的缺陷在于,缺乏自治和隔离能力。进程内调用虽然简单高效,但故障也难以隔离,某个模块的 bug 能导致整个系统崩溃。而在大型系统中,出错几乎是必然的 —— 大型系统意味着多人协作、频繁变更,缺乏隔离就意味着一个模块的内存泄漏能拖垮整个进程,一次局部的代码升级需要整体停机重启。

单体系统真正的致命伤在于:它潜在的设计哲学是「让每一部分都尽量不出错」,靠高质量来保证高可靠。但系统越大,出错越是必然。正如书中所说,从「追求不出错」到正视「出错是必然」的观念转变,才是微服务架构得以挑战单体的底气所在。

单体时代面对的不是分布式复杂度 —— 它根本没有分布式。它面对的是规模带来的复杂度,而这种复杂度体现在多个维度上:可维护性(一次局部改动需要整体停机重启)、团队协作(多人改同一个代码库,互相踩脚)、可靠性(单点故障拖垮全局)。系统小的时候,这些问题都不存在;但规模一旦上去,它们会同时爆发,而且在单体架构下无解 —— 因为缺乏隔离和自治能力。

为了获得这种能力,人们再次走向分布式。

SOA 时代

但在微服务之前,业界走过一段弯路 —— SOA。

SOA 的野心极大,它不仅要解决技术问题,还想建立一套自上而下的软件研发方法论:如何挖掘需求、如何分解业务、如何编排服务,一揽子全包。它有 IBM、Oracle 等巨头撑腰,有 SOAP 协议族做底座,有企业服务总线(ESB)做通信管道,从技术可行性上看确实解决了分布式的主要问题。

但问题恰恰出在「太完美」上。过于精密的规范带来过度的复杂性,SOAP 之上层层叠加的 ESB、BPM、SCA、SDO,让整个技术栈变成了只有少数专业人员才能驾驭的奢侈品。书中有一句话让我印象深刻:SOA 与 EJB 的失败如出一辙 —— 一旦脱离人民群众,终究会淹没在群众的海洋之中。

SOA 面对的问题其实和原始分布式一样 —— 分布式环境下的服务通信、发现、编排。不同的是,它试图用一套大一统的方案把所有问题一揽子解决。但解决方案本身成了新的复杂度来源:协议过重,对开发者强加了大量不需要的东西。 你可能只想让两个服务通个信,却要先搞懂 WSDL、UDDI、SOAP Envelope 这一整套仪式。技术上可行,实践中过载。

回过头看,经历了从原始分布式到 SOA 这漫长的三十年,应用受架构复杂度的牵绊越来越大,距离当年「透明调用」的初心也越来越远。微服务带着对这段历史的自省而来。

微服务时代

Martin Fowler 与 James Lewis 给出了微服务的现代定义,其中的关键词是强终端弱管道 —— 从这里开始,微服务逐渐与 SOA 划清界限,反对 SOAP 和 ESB 这样复杂的通信机制。服务的通信应该回到 UNIX 风格,简单直接,如果需要额外的通信能力,应该在 Endpoint 上自己解决。

同时,微服务还强调容错性设计,这是凤凰特性的关键。不再幻想服务永远稳定,而是接受出错的现实,用熔断、隔离、降级来应对。可靠系统完全可以由会出错的服务组成,这才是微服务最大的价值,也是「凤凰架构」这个名字的含义 —— 每个部分都能死掉并重生,系统整体却永远在线。

微服务丢掉了 SOA 沉重的包袱,但分布式环境下的复杂度并没有消失。SOA 试图用统一规范解决的那些问题 —— 服务发现、负载均衡、通信协议、容错 —— 一下子全回来了,而且因为"自由选择",每个问题都有一长串备选方案:远程调用可选 Dubbo、gRPC、Thrift;服务发现可选 Eureka、Consul、Nacos、ZooKeeper……

微服务面对的复杂度,本质上是分布式基础设施的选型与集成复杂度。Spring Cloud 全家桶帮普通开发者屏蔽了大部分细节,但把选型决策集中到了架构师的桌上。自由是双刃剑:一刃指向 SOA 的复杂规范,夺回了选择权;另一刃朝向自己,选择太多本身就是负担。如果有下一个时代,能不能既有微服务的自由,又不必在应用层自行解决这些基础设施问题?

后微服务时代

这个愿望还真有人接住了。

核心思路是一个换位思考:分布式的那些老问题 —— 服务发现、负载均衡、传输安全 —— 一定要在软件层面解决吗?这些问题其实早就有对应的硬件方案:负载均衡器、DNS、TLS 链路。之所以微服务选择在应用层用代码解决,是因为硬件基础设施跟不上软件的灵活性 —— 你能用键盘命令拆出服务、拷贝启动就能扩容,硬件凭什么不行?

容器化和虚拟化让硬件也能「敲键盘变出来」了。当 Kubernetes 出现,虚拟化的基础设施终于能像软件一样灵活调度时,容器技术就不仅仅是解决程序的分发部署问题了,还开始解决分布式系统的难题。

2017 年 Kubernetes 赢得容器战争,标志着后微服务时代的开端(也称云原生时代,因为 K8s 和微服务解决的是同一类问题)。以前在 Spring Cloud 里用代码实现的服务发现、配置中心、负载均衡,现在可以下沉到基础设施层面,由 CoreDNS、ConfigMap、Ingress Controller 来承担。

但 Kubernetes 也不完美。基础设施是容器粒度的,对单个远程服务的精细管控力不足 —— 微服务 A 调用 B 的两个接口,B1 正常 B2 持续 500,Kubernetes 切不切网络?切了影响 B1,不切继续被 B2 拖垮。为了解决这个问题,服务网格(Service Mesh)应运而生 —— 通过 Sidecar 代理,在应用毫无感知的情况下接管所有通信,实现熔断、认证、监控等功能,由此完成了业务代码和技术关注点的分离。

后微服务时代解决的问题很明确:把分布式基础设施的复杂度从应用代码中剥离出去,下沉到平台层。 微服务时代架构师头疼的那些选型问题 —— 用哪个注册中心、哪个负载均衡器、怎么做熔断 —— 现在由基础设施统一提供,应用代码不再需要感知。复杂度从应用代码搬到了控制平面和 YAML 文件里,由专职的平台团队操持。原始分布式时代追求的「透明的分布式调用」,在三十多年后以另一种形态重新成为可能。

无服务时代

如果说微服务是分布式这条路的极致,那无服务就是另一条路的起点 —— 不分布式的云端系统。

无服务的愿景很纯粹:开发者只写业务逻辑(FaaS),后端组件直接取用(BaaS),部署、算力、运维全交给云。UC Berkeley 的论文将其类比为从汇编到高级语言的跨越 —— 不用关心寄存器,只关心业务表达。

与此同时,它的局限性也很明显:按使用量计费决定了函数不会常驻,冷启动延迟不可避免;无状态的特性也限制了它只适合短链接、事件驱动类的应用。对于业务逻辑复杂、需要长连接的系统,目前并不适用。

无服务面对的复杂度和前面几个时代都不太一样。它不再纠结于分布式通信怎么做、服务怎么发现这些问题 —— 这些在它的视角里根本不存在,因为开发者连"服务器"这个概念都不用想了。它面对的是运维和资源管理的复杂度:部署、扩缩容、容量规划、运维监控,这些全部交给云厂商。代价是你交出了控制权 —— 冷启动延迟你管不了,底层运行时你选不了,成本模型你只能接受。

让我印象最深的是书中最后的判断:未来不会只有一种最先进的架构风格,多种架构并存、融合互补才是软件产业更有生命力的形态。微服务和无服务不是替代关系,而是可以组合使用 —— 将无服务作为技术层面的架构,将微服务作为应用层面的架构。

总结

把整条线拉出来看,每一代架构面对的核心问题其实各不相同:

  • 原始分布式:发现了分布式复杂度 —— 试图用"透明调用"屏蔽它,失败了
  • 单体:没有分布式复杂度,但规模带来了可维护性、团队协作、可靠性的多重困境
  • SOA:正面回答分布式问题,但解决方案本身太重,协议过载压垮了普通开发者
  • 微服务:轻装上阵获得了自由,但分布式基础设施的选型与集成成了新的负担
  • 后微服务:把基础设施复杂度从应用代码下沉到平台层,开发者终于可以不感知
  • 无服务:连运维和资源管理的复杂度都交了出去,代价是交出控制权

每一代架构都在做同一件事:把当前最痛的那种复杂度,想办法从开发者的日常视野中移走。有时候是下沉到基础设施,有时候是交给云厂商。复杂度没有被消灭,只是不断地转移。