第2章 微服务治理技术概述
2.1 微服务架构
在微服务应用中,服务实例的运行环境和实例的网络地址都是动态变化的。因此,客户端为了访问服务必须使用服务发现机制。
服务发现的核心部分是服务注册表,也就是可用服务实例的“数据库”。服务注册表提供一种注册管理API和服务发现请求API。服务实例使用注册管理API来完成注册和注销,通过心跳检测来实现对服务实例有效性的监控,及时将失效服务实例从服务注册表中移除。
服务发现请求API用于发现可用的服务实例。根据进行发现操作的主体来区分,有三种服务发现模式:
● 服务端发现(代理模式);
● 客户端发现(直连模式);
● 服务网格发现(边车模式)。
2.1.1 代理模式
代理模式又称服务端发现模式(server-side discovery pattern),采用Façade门面架构模式。服务提供方和服务调用方的交互流程,即代理模式的服务框架,如图2.1所示。
图2.1 代理模式的服务框架
1)启动时,服务提供方将自己注册到服务注册中心,并通过心跳保持和服务注册中心的连接。
2)代理网关通过服务注册中心获取服务的可用节点列表。
3)服务调用方(客户端)通过指定的域名或者IP向代理网关请求指定的服务。
4)代理网关接到请求后,从相应服务的可用节点列表中,根据指定的路由及负载均衡策略,选择一个最终访问节点,这个服务节点建立连接并发起请求,获得结果。
5)代理网关将结果返回给服务调用方,完成请求。
从图2.1中可以看出,代理网关实际上起到了请求转发(分发)的作用。这种架构具有如下优点。
● 服务路由及负载均衡的策略均在代理网关处实现,服务调用方(客户端)无须关注发现的细节,只需要简单地向负载均衡器发送请求,因此其上的SDK可以做得比较“轻”。
● 代理网关可以根据实际需要,选择效率更高的连接通道,也就是说,同一个请求可以在“调用方→代理网关”“代理网关→提供方”之间采用不同的网络协议。
● 相关服务监控、安全控制及其他治理策略可以集中在代理网关进行,有效地降低监控及治理的复杂度。
● 服务调用方从代理网关上看到的就像一个统一的完整服务,代理网关屏蔽了后台服务的复杂性,同时屏蔽了后台服务的升级和变化,可以提供较好的隔离。
同时,这种模式也具有如下缺点。
● 由于代理网关的存在,部署上比较复杂。网关需要开发、部署和管理,有额外的部署和管理成本。
● 由于代理网关的存在,网络请求上多了一“跳”,比直连模式效率要差一些。
● 代理网关本身就是一个单点隐患,如果代理网关出现故障,这个服务网络将不可用。
最早采用微服务模式的Amazon内部使用的就是代理网关模式,其在AWS上提供的ELB服务是典型的代理网关产品。基于代理网关模式最著名的开源微服务框架非Spring Cloud莫属。准确地说,Spring Cloud利用Netflix开源的Zuul组件来实现代理网关,特色体现在动态可热部署的过滤器(filter)机制上。Spring Cloud是一系列产品和框架的集合,它以Spring Boot的风格将目前市面上各家公司开发得比较成熟、经得起实际考验的服务框架进行了深度整合,屏蔽掉复杂的配置和实现原理,最终给开发者构建了一套简单易懂、易部署且易维护的分布式系统开发工具包。
其他开源的,如我们熟悉的HAproxy、Nginx等负载均衡器产品都可以扩展作为代理模式的网关组件。
2.1.2 直连模式
直连模式又称客户端发现模式,其具体服务框架如图2.2所示。
1)服务提供方在启动时,通过服务框架提供的SDK到服务注册中心注册,并通过心跳或者长连接保持和服务注册中心的联系。
图2.2 直连模式的服务框架
2)服务调用方(即客户端)首先通过服务框架提供的SDK到服务注册中心获取要调用服务的所有已注册的可用节点列表,并对服务注册中心进行监听,一旦服务注册中心有服务的新节点注册,服务调用方会及时更新节点列表。
3)服务调用方根据一定的路由及负载均衡策略计算后,选定一个最终的服务提供节点,并直接发起远程服务调用。
4)服务提供方接到调用请求后,进行业务逻辑处理,并把最终结果返回给服务调用方。
直连模式的服务提供方和调用方之间没有中间代理层,与代理模式相比,它的优点如下。
● 由于不存在代理网关,部署架构更加简化。
● 由于直接在客户端进行路由及负载均衡操作,客户端可以根据自身特点采用更适合的路由及负载均衡算法。
● 由于去掉代理网关组件,网络上少了一“跳”,服务调用方和提供方之间可以采用效率更高的直连协议,甚至采用长连接模式,服务调用更高效。
● 不存在单点隐患。
直连模式的缺点如下。
● 在直连模式下,服务的提供方和调用方均需集成服务框架提供的SDK,也就是说它们都要接受服务框架较强的约束。尤其是对于服务调用方,由于服务的路由及负载均衡均要在其上进行,运算逻辑会比较“重”。
● 服务调用方和服务提供方之间存在较强的耦合关系,客户端要维护所有调用服务的地址,一旦节点变动,路由及负载均衡再平衡算法会相对比较复杂。
● 日志收集及服务治理不仅要在服务提供方处做,也需要覆盖到所有的服务调用方,比代理模式更复杂,而且这个复杂度会随着服务集群规模的扩大而上升。
● 客户端的自由度较低,在客户端需要为每种语言开发不同的服务发现逻辑。
由于直连模式有更高的调用效率,而且初期部署成本较低,很多一线互联网大厂都采用这种模式,包括Google、阿里等。在这些大厂的示范效应下,绝大部分企业在做服务化选型时会采用直连模式,包括现在很火的ServiceMesh,本质上也是一种直连模式。
2.1.3 边车模式
上一节介绍了经典的直连模式微服务框架,其网络部署架构可以用图2.3描述。从图中可以看到,SDK也是应用的一部分,应用逻辑和SDK之间的调用属于微服务的内部调用,不涉及网络传输,微服务和微服务之间的调用才通过SDK进行网络间的传输。
由于微服务和SDK的强耦合导致微服务的开发受到一定的限制,包括技术选型和开发语言等都要考虑与微服务框架的SDK兼容。另外,服务和SDK之间在出现异常时也会相互影响。为了解决这些问题,最近几年出现了一种新的弱耦合SDK的微服务框架,业界统称为ServiceMesh。
ServiceMesh可以简单理解为将直连模式中的SDK拆分出来,以独立进程的模式和微服务应用部署在同一个操作系统中,如图2.4所示。ServiceMesh的服务在部署时会通过修改操作系统的底层网络配置(例如,在Linux中通过iptables的相关代理配置)的方式,对本地网络的调用链路进行调整。
图2.3 传统直连模式的微服务网络部署架构
图2.4 ServiceMesh的微服务网络部署架构
服务调用方将微服务应用发出的所有网络调用通过本地环回接口或地址(Loopback,亦称回送地址,处于网络层),直接发送到本机的ServiceMesh(可以理解为ServiceMesh成了微服务应用的本地代理程序),ServiceMesh再根据截获的URL的路由、负载均衡等相关参数及配置,最终决定向哪个远程网络节点发起真正的网络请求。
服务提供方的网络请求也首先被底层的网络代理配置(通过类似iptables的配置)导向ServiceMesh代理应用,进行一系列诸如限流、降级和安全控制等预处理后,再重新构造一个请求,最后通过本地网络的本地环回接口或地址来调用最终的微服务应用。
可见,在ServiceMesh模式中,每个服务的本地都配备了一个独立代理应用,用于服务之间的通信和服务治理。这些代理应用通常与应用程序代码一起部署,并且不会被应用程序所感知。ServiceMesh将这些代理组织起来形成了一个轻量级网络代理矩阵,即服务网格。如此一来,这些代理就不再是孤立的组件,它们共同构成了一个有价值的网络,如图2.5所示。
图2.5 ServiceMesh中的服务网格
每个服务配置一个代理应用,这种模式很像我们在电视剧中看到的由双轮摩托车挂载一个跨斗形成的跨斗摩托车,跨斗英文名称为SideCar(边车),所以这种模式又称为“边车模式”。边车模式是目前发展非常迅速的一种微服务架构模式,它本质上还是直连模式。如果不特别说明,本书中的相关内容默认采用直连模式。
下一节将通过经典的直连模式来详细介绍微服务框架的架构特点。
2.1.4 直连模式的架构特点
直连模式的微服务框架为服务的提供方和调用方都提供了相应的SDK,分别负责服务的封装、协议暴露、注册、查找、路由、负载均衡及治理等框架级的能力。图2.6是直连模式微服务框架的整体架构。
图2.6 直连模式微服务框架的整体架构
1.服务提供方
在服务提供方,微服务框架的SDK都做了哪些工作?
服务提供方提供服务的真实业务逻辑,这个业务逻辑可能只是程序代码中的某一个类,甚至只是某一个方法,其本身并不具备被远程客户端直接调用的能力。微服务框架要做的就是通过特定协议把这个业务逻辑封装成一个远程服务,并暴露出去。封装和暴露的手段很多,不同语言的实现也不尽相同,有的会使用Hook技术,有的会使用字节码或者动态代理机制等。以Java为例,普遍采用Instrumentation字节码替换技术或InvocationHandler动态代理,为原始业务逻辑生成一个代理类,让这个代理类来负责远程请求的解析匹配和本地真实服务的调用,再把结果返回给远程的调用方。图2.7描述的就是一种典型的服务封装及暴露的架构模式。
可以看到,为了提供更好的性能和更强的隔离性及保护,本地服务一旦被定义为以具体的网络协议(RMI、Hessian、HTTP、gRPC等)对外提供服务,微服务框架通常会采用独立的线程池来暴露它的封装服务。远程请求被接入后首先进行反序列化、请求封装和参数转换的操作,然后被封装成一个Invoker对象,再把这个Invoker对象进行一系列的链式过滤器处理,最终调用真实服务的代理类。这个过程就像包装精美的圣诞苹果,把外面的包装纸一层层撕开,才能看到最终那个“本尊”。
图2.7 服务的封装及暴露
在请求接入的过程中,链式过滤器是很关键的请求处理组件。在各微服务框架里,通常会把链式过滤器设计成动态的,可以根据用户的定义灵活地往里面增减过滤器组件。由于每个请求都会被链式过滤器组件处理,所以针对请求的限流、降级、熔断、日志采集、监控和Mock等操作都可以在这里进行。
2.服务调用方
服务调用方一般只有一个远程服务的接口或标识,如表2.1的第一列所示。为了实现对远程服务的调用,微服务框架在服务调用方启动后,根据这个接口或标识来生成一个代理对象,通过这个代理对象来实现对本地请求的接入,如表2.1的第二列所示。由于动态生成代理类是微服务框架SDK的运行态行为,并且SDK会将代理类通过hook或者IoC的方式动态注入(替换掉)程序的调用逻辑,所以这个过程服务调用方的开发人员是无感的,开发人员只需要简单地调用对应服务接口即可。
表2.1 服务接口及在调用方动态生成的代理类(伪代码)
表2.1中第二列伪代码的第09行,一个名为“handler”的SDK提供的操作类负责处理具体的请求。它的典型操作如下。
1)将请求参数统一封装成标准请求对象,例如一个叫RpcInvocation的类对象。
2)从服务注册中心获取提供此服务的所有服务节点地址列表,例如ClusterList。
3)根据预定义的路由策略RouterConfig,从所有服务节点的地址列表ClusterList中,筛选出一个最终可用的服务节点子集ChildClusterList。
4)根据预定义的负载均衡策略LoadBalanceConfig,从服务节点子集ChildClusterList中筛选出最终要调用的服务节点targetNode。
5)调用一系列的过滤器对请求进行逐项处理。
6)序列化参数RpcInvocation,并向目标节点targetNode发起远程调用(可能会采取异步调用方式以提高调用效率,并控制调用超时),然后返回调用结果。如果调用失败,根据预先定义的容错机制进行相应的容错处理(重试、忽略、失败转移等)。
以上就是微服务框架在服务调用方的典型处理逻辑,各微服务框架在具体处理逻辑上会有差异,但大体架构是一致的。
2.1.5 微服务全生命周期整体架构
微服务的整个生命周期包含了产品设计(微服务需求定义)、微服务应用开发、微服务应用构建、微服务应用部署、监控与运维五大线下、线上的环节,如图2.8所示。
可以看到,整个过程和DevOps工具链基本上是重叠的,微服务治理也围绕上述过程来进行。首先采集各个过程的核心度量数据,包括服务属性、人员属性、协同效率指标、性能指标等;然后通过数据的聚合分析,对过程进行客观度量,找出质量、效率及性能缺失的地方;再通过人工或自动化的手段进行过程优化,这些过程优化措施既包括线下优化,也包括线上优化,如表2.2所示。
表2.2 服务治理的过程优化
简单来说,微服务的治理包含了微服务的度量及管控/管理两大方面的能力。度量为治理决策提供必要依据并制定出相应的治理决策及管控指令,管控/管理负责将治理决策和管控指令落地。本章后续将围绕微服务的整个生命周期,详细讨论针对各个阶段的度量策略和管控/管理手段。
图2.8 微服务生命周期