8.2 服务间通信的演化
服务网格主要解决服务间通信治理问题。本节内容,我们从各个历史阶段的通信治理展开,探讨服务网格的起源与演变。
8.2.1 原始的通信时代
先回到计算机的“远古时代”。
大约 50 年前,初代工程师编写涉及网络的应用程序,需要在业务代码里“埋入”各类网络通信的逻辑。比如实现可靠连接、超时重传和拥塞控制等功能,这些功能与业务逻辑毫无关联,但不得不与业务代码混杂在一起实现。
为了解决每个应用程序都要重复实现相似的通信控制逻辑问题,TCP/IP 协议应运而生,它把通信控制逻辑从应用程序中剥离,并将这部分逻辑下沉,成为操作系统网络层的一部分。
图 8-1 业务逻辑与通信逻辑解耦
在原始的通信时代,TCP/IP 协议的出现让我们看到这样的变化:非业务逻辑从应用程序中剥离出来,剥离出来的通信逻辑下沉成为基础设施层。于是,工程师的生产力被解放,各类网络应用开始遍地开花。
8.2.2 第一代微服务
随着 TCP/IP 协议的出现,机器之间的网络通信不再是难题,分布式系统也由此迎来了蓬勃发展。此阶段,分布式系统特有的通信语义又出现了,比如熔断策略、负载均衡、服务发现、认证与授权、灰度发布以及蓝绿部署等。
图 8-2 微服务特有的通信语义又出现了
在这一阶段,工程师实现分布式系统时,不仅需要专注于业务逻辑,还需根据业务需求实现各种分布式系统的通信语义。随着系统规模的扩大,即使是最简单的服务发现功能,逻辑也变得愈发复杂。其次,使用相同开发语言的另一个应用,这些分布式系统功能仍需重复实现一遍。
此刻,你是否想到了计算机远古时代前辈们处理网络通信的情形?
8.2.3 第二代微服务
为了避免每个应用程序都要自己实现一套分布式系统的通信语义,一些面向分布式系统的微服务框架出现了,比如 Twitter 的 Finagle、Facebook 的 Proxygen,还有众所周知的 Spring Cloud。
图 8-3 第二代微服务框架以 Lib 库的形式封装了分布式系统的通信语义
此类微服务框架实现了负载均衡、服务发现、流量治理等分布式通用功能。开发人员无需关注分布式系统底层细节,付出较小的精力就能开发出健壮的分布式应用。
8.2.4 微服务框架的痛点
使用微服务框架解决分布式问题看似完美,但开发人员很快发现它存在三个固有问题:
- 技术门槛高:虽然微服务框架屏蔽了分布式系统通用功能的实现细节,但开发者却要花很多精力去掌握和管理复杂的框架本身。以 SpringCloud 为例,如图 8-5 所示,它的官网用了满满一页介绍各类通信功能的技术组件。实践过程中,工程师们追踪、解决框架出现的问题绝非易事。
图 8-5 SpringCloud 全家桶
框架无法跨语言:微服务框架通常只支持一种或几种特定的编程语言,而微服务的关键特性是和编程语言无关。如果你使用的编程语言框架不支持,则很难融入这类微服务的架构体系。因此,微服务架构所提倡的:“因地制宜用多种编程语言实现不同模块”,也就成了空谈。
框架升级困难:微服务框架以 Lib 库的形式和服务联编,当项目非常复杂时,处理依赖库版本、版本兼容问题将非常棘手。同时,微服务框架的升级也无法对服务透明。服务稳定的情况下,工程师们普遍不愿意升级微服务框架。大部分的情况是,微服务框架某个版本出现 Bug 时,才被迫升级。
站在企业组织的角度思考,技术重要还是业务重要?每个工程师都是分布式专家固然好,但又不现实。因此,当企业实施微服务架构时,你会看到业务团队每天处理大量的非业务逻辑,相似的技术问题总在不停上演!
8.2.5 思考服务间通信的本质
实施微服务架构时,需要解决问题(服务注册、服务发现、负载均衡、熔断、限流等)的本质是保证服务间请求的可靠传递。站在业务的角度来看,无论上述逻辑设计的多么复杂,都不会影响业务请求本身的业务语义与业务内容发生任何变化,实施微服务架构的技术挑战和业务逻辑没有任何关系。
回顾前面提到的 TCP/IP 协议案例,我们思考是否服务间的通信是否也能像 TCP 协议栈那样:“人们基于 HTTP 协议开发复杂的应用,无需关心底层 TCP 协议如何控制数据包”。如果能把服务间通信剥离、并下沉到微服务基础层,工程师将不再浪费时间编写基础设施层的代码,而是将充沛的精力聚焦在业务逻辑上。
图 8-6 将服务间通信逻辑剥离,并下沉成为微服务基础设施层
8.2.6 代理模式的探索
最开始,探路者们尝试过使用“代理”(Proxy)的方案,比如使用 Nginx 代理配置上游、负载均衡的方式处理部分通信逻辑。
虽然这种方式和微服务关系不大,功能也简陋,但它们提供了一个新颖的思路:“在服务器端和客户端之间插入一个中间层,避免两者直接通讯,所有的流量经过中间层的代理,代理实现服务间通信的某些特性。”。
受限于传统代理软件功能不足,在参考代理模式的基础上,市场上开始陆陆续续出现“边车代理”(Sidecar)模式的产品,比如 Airbnb 的 Nerve & Synaps、Netflix 的 Prana。这些产品的功能对齐原侵入式框架的各类功能,实现上也大量重用了它们的代码、逻辑。
图 8-7 初代 Sidecar 模式的探索
但是此类边车代理存在局限性:它们往往被设计成与特定的基础设施组件配合使用。比如 Airbnb 的 Nerve & Synapse,要求服务发现必须使用 Zookeeper,Prana 则限定使用 Netflix 自家的服务发现框架 Eureka。
因此,该阶段的边车代理局限在某些特定架构体系中,谈不上通用性。
8.2.7 第一代服务网格
2016 年 1 月,William Morgan 和 Oliver Gould 离开 twitter,开启了他们的创业项目 Linkerd。早期的 Linkerd 借鉴了 Twtter 开源的 Finagle 项目,并重用了大量的 Finagle 代码:
- 设计思路上:Linkerd 将分布式服务的通信逻辑抽象为单独一层,在这一层中实现负载均衡、服务发现、认证授权、监控追踪、流量控制等必要功能。
- 具体实现上:Linkerd 作为和服务对等的代理服务(Sidecar)和服务部署在一起,接管服务的流量。
Linkerd 开创先河的不绑定任何基础架构或某类技术体系,实现了通用性,成为业界第一个服务网格项目。同期的服务网格代表产品还有 Lyft(和 Uber 类似的打车软件)公司的 Envoy(Envoy 是 CNCF 内继 Kubernetes、Prometheus 第三个孵化成熟的项目)。
图 8-8 第一代服务网格产品 Linkerd 和 Envoy
8.2.8 第二代服务网格
第一代服务网格由一系列独立运行的代理型服务(Sidecar)构成,但并没有思考如何系统化管理这些代理服务。为了提供统一的运维入口,服务网格继续演化出了集中式的控制面板(Control Plane)。
典型的第二代服务网格以 Google、IBM 和 Lyft 联合开发的 Istio 为代表。根据 Istio 的总体架构(见图 8-8),第二代服务网格由两大核心组成部分:一系列与微服务共同部署的边车代理(称为数据平面),以及用于管理这些代理的控制器(称为控制平面)。控制器向代理下发路由、熔断策略、服务发现等策略信息,代理根据这些策略处理服务间的请求。
图 8-8 增加了控制平面(Control Plane)的第二代服务网格
只看代理组件(下方浅蓝色的方块)和控制面板(顶部深蓝色的长方形),它们之间的关系形成如图 8-9 所示的网格形象状,这也是服务网格命名的由来。
图 8-9 服务网格的控制平面通信与数据平面之间的通信
至此,我们见证了 5 个时代的变迁。大家一定清楚了服务网格技术到底是什么,以及是如何一步步演化成今天这样的形态。现在,我们回过头重新看 William Morgan 对服务网格的定义。
服务网格的定义
服务网格是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但对应用程序透明。
再来理解定义中的 4 个关键词:
- 基础设施层+请求在这些拓扑中可靠穿梭:这两个词加起来描述了服务网格的定位和功能,是否似曾相识?没错,你一定想到了 TCP 协议。
- 网络代理:描述了服务网格的实现形态。
- 对应用透明:描述了服务网格的关键特点,正是由于这个特点,服务网格能够解决以 Spring Cloud 为代表的第二代微服务框架所面临的三个本质问题。