起初,我们使用一个单体应用来提供服务,经常采用的是MVC三层架构。在单体应用中操作都是进程内的函数调用,不涉及诸如RPC等服务与服务的跨进程通信。但随着时间的增加,我们发现单体架构越来越不能满足我们的需求,比如用户访问暴增,业务逻辑愈加复杂,一个单体的服务已不能满足功能及性能的要求。我们需要将其按业务领域拆分为几个独立的服务来对外提供服务,这就是微服务架构。
引入微服务架构,一般会引入一个RPC框架,来完成整个RPC的调用过程。
如上图粉色部分所示,RPC分为:
RPC-client,它嵌在调用方进程里
RPC-server,是服务进程的基础
框架只是第一步,越来越多和RPC,和微服务相关的功能,会被加入进来。
如果要扩展多种负载均衡方案,例如:
- 轮询
- 随机
- 取模
- 一致性哈希
RPC-client需要进行升级。
如果要对RPC接口处理时间进行收集,来实施统一监控与告警,也需要对RPC-client进行升级。
服务新增一个实例,通知配置中心,配置中心通知已注册的RPC-client,将流量打到新启动的服务实例上去,迅速完成扩容。
然而我们要知道,由于RPC-client,它嵌在调用方进程里,RPC-server,是服务进程,如果我们想要对RPC-Client及RPC-Server的负载均衡、数据收集、服务发现、调用链跟踪这些基础功能进行新增或者升级,往往会面临以下一些问题:
- 业务技术团队,仍需要花时间去学习、使用基础框架与各类工具,而不是全心全意将精力花在业务和产品上
- client要维护m个版本, server要维护n个版本,兼容性要测试m*n个版本
- 如果要支持不同语言,往往要开发C-client,Python-client,go-client,Java-client多语言版本
- 每次基础功能的升级,都需要推动上下游进行升级,这个周期往往是以季度、半年、又甚至更久,整体效率极低
这些耦合,这些通用的痛点,有没有办法解决呢?
一个思路是,将服务拆分成两个进程,解耦。
- 一个进程实现业务逻辑(不管是调用方,还是服务提供方),biz,即上图白色方块
- 一个进程实现底层技术体系,proxy,即上图蓝色方块
- biz和proxy共同诞生,共同消亡,互为本地部署,即上图虚线方框
- biz和proxy之间,为本地通讯,即上图黑色箭头
- 所有biz之间的通讯,都通过proxy之间完成,proxy之间才存在远端连接,即上图红色箭头
这样就实现了“业务的归业务,技术的归技术”,实现了充分解耦,如果所有节点都实现了解耦,整个架构会演变为:
- 绿色为biz
- 蓝色为proxy
整个服务集群变成了网格状,这就是Service Mesh服务网格的由来。
将本将属于应用程序的功能抽出来形成单独的进程,这个进程可以被理解为Sidecar。在微服务体系内,将集成在应用内的微服务功能剥离到了sidecar内,sidecar提供了微服务发现、注册,服务调用,应用认证,限速等功能。异构服务本身不会和注册中心有请求调用,而是通过sidecar代理注册接入注册中心,获得服务注册、发现等功能。
ServiceMesh,就不得不提Istio,它是ServiceMesh目前最流行的产品化落地。
Istio官网强调了它提供的五项关键特性:
流控(traffic management)
断路器(circuit breakers)、超时、重试、高可用、多路由规则、AB测试、灰度发布、按照百分比分配流量等。
安全(security)
加密、身份认证、服务到服务的权限控制、K8S里容器到容器的权限控制等。
可观察(observability)
追踪、监控、数据收集,通过控制后台全面了解上行下行流量,服务链路情况,服务运行情况,系统性能情况。
平台无关系(platform support)
K8s,物理机,自己的虚拟机都没问题。
集成与定制(integration and customization)
可定制化扩展功能。
逻辑上,Istio分为数据平面(data plane)和控制平面(control plane)。
数据平面,有一个核心组件:
Envoy (proxy)
Envoy的核心职责是高效转发,更具体的,它具备这样一些能力:
(1)服务发现
(2)负载均衡
(3)安全传输
(4)多协议支持,例如HTTP/2,gRPC
(5)断路器(Circuit breakers)
(6)健康检查
(7)百分比分流路由
(8)故障注入(Fault injection)
(9)系统度量
大部分能力是RPC框架都具备,或者比较好理解的,这里面重点介绍下断路器和故障注入。
- 断路器设计
它是软件架构设计中,一个服务自我保护,或者说降级的设计思路。
举个例子:当系统检测出某个接口有大量超时时,断路器策略可以终止对这个接口的调用(断路器打开),经过一段时间后,再次尝试调用,如果接口不再超时,则慢慢恢复调用(断路器关闭)。
- 故障注入设计
它是软件架构设计中,一种故意引入故障,以扩大测试覆盖范围,保障系统健壮性的方法,主要用于测试。国内大部分互联网公司,架构设计中不太会考虑故障注入,在操作系统内核开发与调试,路由器开发与调试中经常使用,可以用来模拟内存分配失败、磁盘IO错误等一些非常难出现的异常,以确保测试覆盖度。
控制平面,有四个核心组件:
- Mixer
Mixer的一些核心能力是:
(1)跨平台,作为其他组件的adapter,实现Istio跨平台的能力;
(2)和Envoy通讯,实时各种策略
(3)和Envoy通讯,收集各种数据
Mixer的设计核心在于“插件化”,这种模型使得Istio能够适配各种复杂的主机环境,以及后端基础设施。
- Pilot
Pilot作为非常重要的控制平面组件,其核心能力是:
(1)为Envoy提供服务发现能力;
(2)为Envoy提供各种智能路由管理能力,例如A/B测试,灰度发布;
(3)为Envoy提供各种弹性管理能力,例如超时,重试,断路策略;
Pilot的设计核心在于“标准化”,它会将各种流控的控制命令转化为Envoy能够识别的配置,并在运行时,将这些指令扩散到所有的Envoy。Pilot将这些能力抽象成通用配置的好处是,所有符合这种标准的Envoy都能够接入到Pilot来。
潜台词是,任何第三方可以实现自己的proxy,只要符合相关的API标准,都可以和Pilot集成。
- Citadel
Citadel组件,它提供终端用户身份认证,以及服务到服务的访问控制。总之,这是一个和安全相关的组件。
- Galley
Gally组件,它是一个配置获取、校验、处理、分发的组件,它的设计核心在于“解耦”,它将“从底层平台(例如:K8S)获取用户配置”与Istio解耦开来。
实施与控制分离,经典的架构设计方法。
用一个例子,理解istio流控策略实施通用流程
就如同ServiceMesh的设计初衷,是技术体系与业务服务解耦一样,Istio流控模型的本质,是流量控制与服务实例扩展的解耦,更具体的:
-
用户只需要通过控制平面中的Pilot设定期望流量要以什么规则进行路由
-
不需要规定服务实例(service pods)如何接收
-
数据平面Envoy将从Pilot中获取规则和命令,然后落地各类分流策略
如上图所示,最开始时,ServiceA访问旧版的ServiceB。
(1)灰色圆形为业务Svc服务;
(2)紫色六边形为Envoy代理;
(3)服务与代理之间都是本地访问;
(4)跨网段之间都是Envoy代理交互(蓝色箭头);
如何进行灰度发布呢?
如上图所示,服务A调用服务B,服务B要发布一个灰度版本,需要5%的流量打到服务B的新版本,只需要:
(1)部署服务B的新版本;
(2)控制平面Pilot上进行策略配置,策略同步到Envoy;
(3)数据平面Envoy接收到策略配置,实时分流策略;
这个过程业务服务与流量控制策略完全解耦。
除了基于按流量比例分流的灰度发布,基于应用层的灰度发布通过Istio也非常容易实现。
如上图所示,服务B要发布一个灰度版本,需要把iPhone的流量打到B的新版本,操作流程完全一样(部署服务,Pilot控制,Envoy实施),非常方便。
如果Envoy原来只支持按照流量比例分流,不支持基于应用层协议分流,此时只需要:
(1)升级Envoy的分流策略,以及策略控制端Pilot;
(2)调用方服务A不需要升级;
(3)服务方服务B也不需要升级;
如果是用传统微服务框架的方式,需要框架升级,调用方与服务方均需要配合升级与重启。