微服务架构中的进程间通信概述:
进程之间的通信技术有很多。服务可以使用基于同步请求/响应的通信机制,例如HTTP REST或gRPC。另外,也可以使用异步的基于消息的通信机制,比如AMQP或STOMP。消息的格式也不尽相同。服务可以使用具备可读性的格式,比如json,XML。也可以使用更加高效的,基于二进制的Avro或Protocol Buffers格式
交互方式:
有多种客户端与服务端的交互方式,它们可以分为两个维度。第一个维度关注的是一对一和一对多。
- 一对一:每个客户端请求由一个服务实例来处理
- 一对多:每个客户端请求由多个服务实例来处理
交互方式的第二个维度关注的是同步和异步
- 同步模式:客户端请求需要服务端实时响应,客户端等待响应时可能或导致堵塞
- 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非实时的
一对一的交互方式有以下几种类型:
- 同步请求/响应:一个客户端向服务端发起请求,等待响应;客户端期望服务端很快就会发送响应。在一个基于线程的应用中,等待过程可能造成线程阻塞。这样的方式会导致服务的紧耦合
- 异步请求/响应:客户端发送到服务端,服务端异步响应请求。客户端在等待响应时不会阻塞线程,因为服务端的响应不会马上返回
- 单向通知:客户端的请求发送到服务端,但是并不期望服务端做出任何响应
但是,同步请求/响应的交互方式并不会因为具体的进程间通信技术而发生改变!
一对多的交互方式有以下几种类型:
- 发布/订阅方式:客户端发布通知消息,被零个或者多个感兴趣的服务订阅
- 发布/异步响应方式:客户端发布请求消息,然后等待从感兴趣的服务发回的响应
每个服务通常使用的都是以上这些交互方式的组合
在微服务架构中定义API:
API接口在微服务架构中同样重要。服务的API是服务与其客户端之间的约定
消息的格式:
进程间通信的本质是交互消息。消息通常包括数据,因此一个重要的设计决策就是这些数据的格式。消息格式的选择会对进程间通信的效率、API的可用性和可演化性产生影响
消息的格式可以分为两大类:文本和二进制
- 基于文本的消息格式:
JSON和XML这样的基于文本的格式。优点是它们的可读性很高,同时也是自描述的。缺点是消息过渡冗长,特别是XML。消息每传递一次都必须反复包含除了值以外的属性名称,这样会造成额外的开销,还有就是解析文本引入的额外开销,尤其是在消息体较大的时候。因此,在对效率和性能敏感的场景下,可能需要考虑二进制格式的消息
- 二进制消息格式:
常用的二进制格式有Protocol Buffers(protobuf)和Avro。这两种格式都提供了一个强类型的IDL(接口描述文件),用于定义消息的格式。例如Protocol Buffers的.proto文件就是protobuf的接口描述文件
基于同步远程过程调用模式的通信:
使用基于远程过程调用(RPI)的进程间通信机制时,客户端向服务发送请求,服务处理请求并发回响应。有些客户端可能处于堵塞状态并等待响应,而其他客户端可能会有一个响应式的非阻塞架构
使用REST:
REST是一种使用HTTP协议的进程通信机制,REST提供了一系列架构约束,当作为整体使用时,它强调组件交互的可扩展性、接口的通用性、组件的独立部署,以及那些能减少交互延迟的中间件,它强化了安全性,也能封装遗留系统
REST中的一个关键概念是资源,它通常表示单个业务对象,例如客户或产品,或业务对象的集合。REST使用HTTP动词来操作资源,使用URL引用这些资源
REST的成熟度模型有四个层次:
- Level 0:Level 0层级的客户端只是向服务端点发起HTTP POST请求,进行服务调用。每个请求都指明了需要执行的操作、这个操作针对的目标和必要的参数
- Level 1:Level 1层级的服务引入了资源的概念。要执行对资源的操作,客户端需要发出指定要执行的操作和包含任何参数的POST请求
- Level 2:Level 2层级的服务使用HTTP动词来执行操作,如GET表示获取资源、POST表示创建资源、PUT表示更新资源、DELETE表示删除资源。请求查询参数和主体指定操作的参数。这让服务能够借助WEB基础设施服务
- Level 3:Level 3层级的服务基于HATEOAS(Hypertext As The Engine Of Application State),基本思想是在由GET请求返回的资源信息中包含链接,客户端可以根据链接来发现可以执行的动作
REST的优点:
- 简单、熟悉
- 可以使用浏览器或者Postman等插件、CURL命令来测试
- 直接支持请求/响应方式的通信
- HTTP对防火墙友好
- 不需要中间代理,简化了系统架构
REST的弊端:
- 只支持请求/响应方式的通信
- 可能导致可用性降低。由于客户端和服务直接通信而没有代理缓冲信息,因此它们必须在REST API调用期间都保持在线
- 客户必须知道服务实例的URL。客户端必须使用服务发现机制来定位服务实例
- 在单个请求中获取多个资源具有挑战性
- 有时很难将多个更新操作映射到HTTP动词
使用GRPC:
GRPC是一种基于二进制消息的协议,这就意味着,我们不得不采用API优先的方法来进行服务设计。通常使用Potocol Buffer的IDL定义grpc api,这是谷歌用于序列化结构化数据的一套语言中立机制。可以使用Potocol Buffer编译器生成客户端的存根和服务端骨架。grpc api由一个或多个服务和请求/响应消息组成。服务定义是强类型方法的集合。除了支持简单请求/响应RPC之外,grpc还支持流式RPC。服务器可以使用消息流响应客户端。客户端也可以向服务端发送消息流
grpc的好处:
- 设计具有复杂更新操作的API非常简单
- 它具有高效、紧凑的进程间通信机制,尤其是在交换大量消息时
- 支持在远程过程调用和消息传递过程中使用双向流式消息方式
- 实现了客户端和用各种语言编写的服务端之间的互操作性
grpc的弊端:
- 与基于REST/JSON的API机制相比,JavaScript客户使用基于grpc的api需要做更多的工作
- 旧式防火墙可能不支持HTTP2
grpc与REST一样,是一种同步通信机制,因此也存在局部故障的问题
使用断路器模式处理局部故障:
分布式系统中,当服务试图向另一个服务发送同步请求时,有永远都面临着局部故障的风险。因为客户端和服务端是独立的进程,服务端很有可能无法在有限的时间内对客户端请求做出响应
模式:断路器
这是一个远程过程调用的代理,在连续失败次数超过指定的次数后的一段时间内,这个代理会立即拒绝其他调用。
要通过合理地设计服务来防止在整个应用程序中故障的传导和扩散,这是至关重要的。解决这个问题分为两部分:
- 必须让远程过程调用代理有正确处理无响应服务的能力
- 需要决定如何从失败的远程服务中恢复
如何编写健壮的远程过程调用代理:
- 网络超时:在等待请求的响应时,一定不要做成无限阻塞,而是设定一个超时时间
- 现在客户端向服务器发出请求的数据量
- 断路器模式:监控客户端发出请求的成功和失败数量,如果失败的比例超过一定次数,就启动断路器,让后续的调用立刻失效
调用代理
客户端发出请求后,会先请求一个代理服务器上,比如使用php作为代理 接收PC、IOS、Android传递的参数,然后再请求服务
使用服务发现:
在现代基于云的微服务应用程序中,具有动态性。服务实例具有动态分配的网络位置。由于自动扩展、故障、升级,服务实例集会动态更改。因此客户端需要使用服务发现
服务发现的概念:
其关键组件是服务注册表,它是包含服务实例网络位置信息的一个数据库。服务启动或停止时,服务发现机制会更新服务注册表。当客户端调用服务时,服务发现机制会查询服务注册表以获取服务实例的列表,并将请求路由到其中一个服务实例
实现服务有以下两种方式:
- 服务端与客户端直接与服务注册表交互(应用层服务发现模式) 例如:consul
实现服务发现的一种方法是应用程序的服务及其客户端与服务注册表进行交互。服务实例使用服务注册表注册其网络位置。客户端先通过查询服务注册表获取服务实例列表来调用服务,然后它向其中一个实例发送请求
这种服务发现方式是以下两种模式的组合而成:
1:自注册模式
服务实例调用服务发现的注册API来注册其网络位置。它还可以提供运行状况检查URL。服务实例向服务注册表注册自己。
2:客户端发现模式:
当客户想调用服务时,会先查询服务注册表获取服务实例的网络位置
例如:consul的使用方式 ,服务实例通过调用consul的注册API注册其网络位置,客户端通过服务名称查询该服务的网络位置
- 通过平台基础设施来处理服务发现(平台层服务发现模式) 例如:kubernetes中的service
Kubernetes都具有内置的服务注册表和服务发现机制。部署平台为每个服务提供DNS名称,虚拟IP和解析VIP地址的DNS名称。客户端像DNS名称和VIP发出请求,部署平台自动将请求路由到其中一个可用服务实例。因此服务注册、服务发现和请求路由完全由部署平台处理。图3-6显示它的工作原理
这种方法是以下两种模式的组合:
1:第三方注册模式
由第三方服务(被称为注册服务器,通常是部署平台的一部分,例如 kubernets中etcd)处理注册,而不是服务本身向服务注册表注册自己
2:服务端发现模式
客户端不再需要查询服务注册表,而是向DNS名称发出请求,对该DNS名称的请求解析到路由器,路由器查询服务注册表并对请求进行负载均衡。例如 kubernets中的svc