2.2 服务通信系统
微服务架构在互联网服务中已经是一个被广泛接受和实践的理念。如图2-9 所示,相对于一体化服务架构,微服务架构可以将不同的功能拆解到多个目标明晰、相互独立、协同互联的自治服务当中。通过对功能的明确划分和解耦,这些独立且协作的微服务可以按照最适合的方式进行演进和迭代升级。概括地讲,微服务架构有如下一些优点。
● 独立性:研发团队可以按需为微服务选择最合适的编程语言和技术栈,不同微服务之间的耦合程度很低,它们只需要按照接口规范对外提供服务即可。此外,独立性也极大地提高了项目迭代的并发度和代码开发的效率。
● 隔离性:微服务是自治的服务实体,某个微服务发生故障通常只会降低系统整体的功能完备性和系统可用性,但不会导致大面积的系统瘫痪。当然,隔离性的完善也需要依赖比较成熟的集群运维经验和服务治理手段。
● 可运维性:运维人员可以按需对微服务进行独立的持续集成、压力测试、服务部署、服务扩缩容甚至服务回滚操作,高度的自治性大大简化了运维难度并提高了运维效率。
图2-9 一体化服务架构和微服务架构
凡事有利必有弊,微服务架构也并不是万能的,特别是随着微服务数量的膨胀,服务管理、性能保障和故障定位也变得愈加复杂。为了应对这些技术挑战,本节将从跨进程通信框架、服务注册与发现、服务治理这几个核心的技术方面来进行探讨。在后续章节中,我们也将对监控报警系统和链路跟踪系统进行更深入的介绍。
2.2.1 跨进程通信框架
跨进程调用机制可以追溯到 1984 年 Birrell 和 Nelson 的设计思路,在他们的设计当中,进程 A 在发起请求调用进程 B 上的过程之后会被挂起,只有当进程 B 执行完被请求的过程并返回响应之后,进程 A 才能恢复到正常的运行状态。这种跨越进程边界的过程调用机制被称为远程过程调用(Remote Procedure Call,RPC)。跨进程通信框架是基于这种原理来实现微服务间通信的核心工具,它通常需要提供数据序列化、网络消息传输、跨编程语言以及跨工作平台这些重要功能。
基于XML序列化格式的SOAP通信框架,以及基于JSON序列化格式的RESTful通信框架,都有着极为广泛的应用,它们比较适合作为用户端程序与数据中心服务器之间的通信框架。在数据中心内部的机器集群中,gRPC和Thrift则是更为高效的开源RPC通信框架方案,前者通常配合Protobuf序列化协议,而后者内置了序列化实现。gRPC是Google在内部Stubby框架的长期实践基础上打造的开源版本RPC框架,它是面向移动应用并基于HTTP/2协议进行设计的。gRPC支持双向流控、头部压缩和多路复用等高级功能,因此它具有较为突出的功能完备性和通信性能优势。Thrift也是一种功能完备、跨语言和跨平台的RPC框架,它同时集成了数据序列化、网络消息传输和服务端框架等功能,Thrift被Facebook和新浪微博等公司所使用。gRPC和Thrift都是经过长期实践检验的优秀RPC框架,研发团队可以根据自身情况进行技术选型。
图2-10 展示了跨进程通信框架的基本原理和运行机制。
图2-10 跨进程通信框架的基本原理和运行机制
首先开发人员需要利用框架的接口定义语言(Interface Definition Language,IDL)来确定客户端与服务端之间的通信接口和消息格式,然后利用框架自带的编译器来生产客户端桩函数、服务端桩函数、服务端框架骨干,最后开发人员需要完善两端的过程调用接口。由此可见,服务端框架骨干、数据格式转化、消息序列化以及网络消息通信等核心功能都由跨进程通信框架来负责实现,框架利用两端的桩函数屏蔽了底层实现,并提供了类似于本地调用的函数接口。基于这个基本原理,跨进程通信框架的具体运行流程如下。
(1)客户端调用桩函数发起远程过程调用,这个过程和调用本地函数的过程一致。
(2)客户端桩函数将网络消息发送到远程系统中。桩函数自身是一个本地过程,但是它在内部将数据进行序列化后,可利用网络层协议与远程服务端进行通信。
(3)客户端的操作系统内核通过套接字(Socket)接口将网络消息传输到远程进程当中。
(4)服务端的操作系统内核通过套接字接口接收网络消息并通知桩函数解析客户端请求。
(5)服务端桩函数将解析后的客户端请求发送到指定的服务端过程实现接口中。
(6)远程过程在完成执行之后,会将响应消息返回给服务端桩函数。
(7)服务端桩函数对响应消息进行数据序列化之后,利用网络通信层协议返回给客户端。
(8)服务端的操作系统内核通过套接字接口发送响应消息给客户端。
(9)客户端桩函数通过套接字接口接收网络消息并通知桩函数解析响应消息。
(10)客户端桩函数将响应结果返回给客户端函数。
2.2.2 服务注册与发现
跨进程通信框架解决了数据序列化以及远程过程调用的问题,而服务寻址则是它能够在微服务架构中得以工作的前提条件。不仅如此,在服务寻址的过程中,我们也需要结合服务状态、动态配置、负载均衡、故障转移、请求限流、服务降级和请求熔断等一系列服务治理手段进行综合考虑。通常来说,服务寻址的方式可以概括为图 2-11 所示的四类。
● 硬编码:这是最简单的一种方式,一般来说,它依赖静态的配置文件或者命令行参数来实现服务寻址。
● 代理服务:它利用诸如 LVS 或者 OpenResty 等中间代理服务来进行数据转发。这种服务寻址方式自带简单的负载均衡机制。当然,客户端也需要基于硬编码的方式来寻址中间代理服务,并且代理服务在高负载下可能会成为性能和可用性的单点瓶颈。
● DNS 查询:客户端通过查询 DNS 服务器来获得所有的服务端地址,并基于自己的负载均衡策略选择恰当的服务器进行通信。这种方式虽然不会带来单点瓶颈的问题,但是由于 DNS 只能返回较为单一的地址信息,而非更为丰富的状态信息和配置参数,因此客户端难以进行精细的访问路由控制。
● 服务注册与发现:这种方式和 DNS 查询颇为相似,但是它提供了更为丰富的状态和配置信息,因此客户端能够依据这些信息来实现更为精细和实时的访问路由控制。
图2-11 服务寻址的方式
在小规模的服务架构中,硬编码、代理服务或者 DNS 查询都能基本满足使用需求。但是在大型的微服务架构中,服务节点的规模、状态和网络地址是动态变化的,而传统的寻址方式往往难以应对这些新的挑战,这便是服务注册与发现技术诞生的背景。我们可以通过图 2-12 来粗略地比较不同服务寻址方式的特性。综合来看,服务注册与发现方式无论是在功能完备性、运行可靠性还是运维便利性上都有着突出的优势,因此它也成为各大互联网公司的标准服务寻址方式。
图2-12 不同服务寻址方式的特性对比
图 2-13 展示了一个典型的服务注册与发现系统的整体架构,这是一个典型的分层设计。
图2-13 典型的服务注册与发现系统的整体架构
● 数据持久化层:该组件主要负责对路由数据进行可靠的存储,其中使用较为广泛的开源解决方案是 Consul、etcd 和 ZooKeeper。通常来说,这个组件需要存储服务地址、服务状态、服务配置、路由策略、数据分片记录、动态负载数据等关键信息。
● 策略实现层:该组件需要依据数据持久化层的信息来执行具体的服务注册和服务寻址操作。除了这些基本功能,它通常也可以按需来实现负载均衡、连接池管理、健康检查和机房路由策略等高级功能。该组件既可以被嵌入应用程序当中,也可以被部署为机器节点上的守护进程。
● 远程通信框架层:该组件的主要作用是在服务寻址结束后发起真正的远程调用操作,其中最为常见的通信框架是 Thrift、gRPC 和 HTTP。
2.2.3 服务治理
服务注册与发现系统以及跨进程通信框架较为完美地解决了微服务架构下的服务寻址和服务通信问题,但是随着微服务数量的膨胀和服务间依赖关系的交织,微服务管理的复杂性也变得更为明显,服务治理的理念便是在这种背景下应运而生的。服务治理包含了方方面面的工程实践方案,ServiceMesh 是其中非常具有代表性的一个,其系统架构如图 2-14 所示。
图2-14 ServiceMesh 系统架构
ServiceMesh 系统架构的主要特点是,依赖部署在服务节点上的 Proxy 守护进程组成了网格化的服务通信基础设施,它也利用了中心化的控制台来完成对这些 Proxy 守护进程的统一管理。集群中所有微服务的通信流量和通信策略均依赖 ServiceMesh 所构建的通信网络基础设施来完成,因此我们可以把该运行模型的特性概括为以下几点。
● 非侵入式:Proxy 守护进程作为节点上的透明网管,统一负责流量的路由和收发。应用程序本身无须嵌入重量级的通信框架,它们只需要与本地 Proxy 进行交互即可。这种方式可以适配用多种语言编写的应用程序,研发人员无须重复实现与语言相关的框架逻辑代码。
● 易于运维:通信框架完全集成在 Proxy 守护进程中,因此通信框架和 Proxy 守护进程的具体实现与升级迭代对于应用程序来说是透明的。
● 使用简单:应用程序只需要利用简单的规则和接口与本地 Proxy 进行交互即可,因此开发者无须掌握较为复杂的框架逻辑和框架配置。
正如任何架构一样,ServiceMesh 系统架构也带来了如下一些副作用。
● 基于 Proxy 守护进程的数据转发模式在一定程度上降低了通信性能。
● ServiceMesh 本身的开发和运维具有较大的复杂性。
● 该基础设施的稳定性和可用性会波及几乎所有的应用程序。
Istio 是目前 ServiceMesh领域中较为知名的开源解决方案,它也得到了 Google 和 IBM 的大力支持。总的来看,Istio 是一个具有高度可扩展性和可运维性的工程实现,当然,它也满足了背后公司对于公有云平台的商业利益诉求。在笔者看来,虽然ServiceMesh 系统架构本身确实具有先进的服务治理范式,但是业务方依然需要按照自身情况来选择最适合当前业务发展的实施方案。