内容总结自《微服务架构设计模式》
微服务架构中的进程间通信
- 一、通信概述
- 通信方式
- API定义
- 消息格式
- 二、同步通信
- REST
- gRPC
- 断路器
- 服务发现
- 三、异步通信
- 消息
- 消息通道
- 消息代理
- 消息问题
)
一、通信概述
通信方式
有很多进程间通信技术可供开发者选择。服务可以使用基于同步请求/响应的通信机制,例如HTTP REST或gRPC。另外,也可以使用异步的基于消息的通信机制,比如AMQP或STOMP。消息的格式也不尽相同。服务可以使用具备可读性的格式,比如基于文本的JSON或XML。也可以使用更加高效的、基于二进制的Avro或Protocol Buffers 格式。
客户端和服务端的交互方式可以分为两个不同的维度
维度一:一对一、一对多
维度二:同步模式、异步模式
一对一 | 一对多 | |
---|---|---|
同步模式 | 请求/响应 | 无 |
异步模式 | 异步请求/响应 单向通知 | 发布/订阅 发布/异步响应 |
API定义
相比于单体架构,我们面临的挑战在于:并没有一个简单的编程语言结构可以用来构造和定义服务的API。根据定义,服务和它的客户端并不会一起变一。如果使用不兼容的API部署新版本的服务,虽然在编译阶段不会出现错误,但是会在运行时故障。
API需要优先设计
即使在那些最简单的项目中,组件和API之间也经常发生冲突。例如,负责后端的Java程序员和负责前端的AngularJS程序员都声称完成了开发,然而这个应用程序却无法工作。前端的REST和WebSocket API无法与后端的API一起工作。最终导致这个应用程序的前端和后端无法正常通信!
在现代应用程序中,对API有着极高的可用性要求,一般会采用滚动升级的方式来更新服务,因此一个服务的旧版本和新版本肯定会共存。所以在处理API的时候存在相关的措施:
- 语义化的版本控制:规范化如何使用版本号,正确的方式递增版本号
- 进行次要并且向后兼容的改变:添加可选属性、向响应添加属性、添加新操作
- 进行主要并且不向后兼容的改变:服务强制客户端升级,必须同时支持新老版本API
消息格式
消息的格式可以分为两大类:文本(JSON、XML等)和二进制(Protocol Buffers、Avro等)
二、同步通信
REST
REST是一种(总是)使用HTTP协议的进程间通信机制。REST提供了一系列架构约束,当座位整体使用时,它强调组件交互的可扩展性、接口的通用性、组件的独立部署,以及那些能减少交互延迟的中间价,它强化了安全性,也能封装遗留系统。
REST中的一个关键概念是资源,它通常表示单个业务对象,例如客户或产品,或业务对象的集合。REST使用HTTP动词来操作资源,使用URL饮用这些资源。
好处:
- 它非常简单,并且大家都很熟悉
- 可以使用浏览器扩展(比如Postman插件)或者curl之类的命令行(假设使用的是JSON或其他文本格式)来测试HTTP API
- 直接支持请求/响应方式的通信
- HTTP对防火墙友好
- 不需要中间代理,简化系统架构
弊端:
- 它只支持请求/响应方式的通信
- 可能导致可用性降低。由于客户端和服务直接通信而没有代理来缓冲消息,因此他们必须在REST API调用期间都保持在线
- 客户端必须知道实例的位置(URL),这是现在应用程序中的一个重要问题。客户端必须使用所谓的服务发现机制来定位服务实例
- 在单个请求中获取多个资源具有挑战性
- 有时很难将多个更新操作映射到HTTP动词
gRPC
gRPC是REST的另一种替代方案,这是一个用于编写跨语言客户端和服务端的框架。gRPC使用Protocol Buffers是一种高效且紧凑的二进制格式。他是一种标记格式。
好处:
- 设计具有复杂更新操作的API非常简单。
- 它具有高效、紧凑的进程间通信机制,尤其是在交换大量消息时。支持在远程过程调用和消息传递过程中使用双向流式消息方式。
- 它实现了客户端和用各种语言编写的服务端之间的互操作性。
弊端:
- 与基于REST/JSON的API机制相比,JavaScript客户端使用基于gRPC的API需要 做更多的工作。
- 旧式防火墙可能不支持HTTP/2。
gRPC是REST的一个引人注目的替代品,但与REST一样,它是一种同步通信机制,因此它也存在局部故障的问题。
断路器
本质就是微服务中提到的服务熔断和降级
当一个服务同步调用另一个服务时,它应该使用Netflix描述的方法来保护自己,这种方法包括以下机制的组合
- 网络超时
- 限制客户端向服务器发送请求的数量:限流
- 断路器模式:控制客户端发出请求的成功和失败数量
服务发现
实现服务发现有以下两种主要方式:服务及其客户直接与服务注册表交互、通过部署基础设置来处理服务发现
1、服务及其客户直接与服务注册表交互
服务实例使用服务注册表注册其网络位置
- 自注册:服务实例向服务注册表注册自己
- 客户端发现:客户端从服务表检索可用服务实例的列表,并在他们之间进行负载平衡
二、通过部署基础设置来处理服务发现
部署平台(Docker、K8S)为每个服务提供DNS名称、虚拟IP(VIP)地址和解析为VIP地址的DNS名称。
- 第三方注册:服务实例由第三方自动注册到服务注册表
- 服务端发现:客户端向路由器发出请求,路由器负责服务发现
三、异步通信
使用消息机制时,服务之间的通信采用异步交换消息的方式完成。
消息模型的定义:消息通过消息通道进行交换,发送方(应用程序或服务)将消息写入通道,接收方(应用程序或服务)从通道读取消息
消息
消息由消息头部和主体组成。标题是名称和值对集合,描述正在发送的数据和元数据。除了消息发送提供的名称和值对外,消息头部还包含其他信息,例如发件人或消息传递基础设施生成的唯一消息ID,以及可选的返回地址,该地址制定发送回复的消息通道。消息正文是以文本或二进制格式发送的数据。
消息有以下几种类型:
- 文档:仅包含数据的通信消息
- 命令:一条等同于RPC请求的消息
- 事件:表示发送方这一端发生了重要的时间,事件通常是领域事件,表示领域对象的状态发生的更改
消息通道
有两种消息通道:
- 点对点通道(一对一)
- 发布-订阅通道(一对多)
消息代理
类比理解为具体的MQ产品(MetaQ、Kafuka等)
消息问题
处理并发和消息顺序
常见的解决方案是使用分片(分区)通道:
- 分片通道由两个或多个分片组成,每个分片的行为类似于一个通道
- 发送方在消息头部指定分片键,通常是任意字符串或字节序列。消息代理使用分片键将消息分配给特定的分片
- 消息代理将接受方的多个实例组合在一起,并将它们视为相同的逻辑接受方
处理重复消息
由于保证有且仅有一次的消息传递通常成本很高,所以大多数消息代理承诺至少成功传递一次消息
解决重复消息常见做法:
- 编写幂等消息处理程序
- 跟踪消息并丢弃重复项
处理事务消息
常见做法是使用数据库表作为消息队列(借助本地的ACID事务),将需要发送的消息,存储到数据库中。
将消息从数据库移动到消息代理并对外发布有两种不同方法:
- 通过轮询模式发布事件:定时轮询select表的数据
- 使用事物日志拖尾模式发布事件:监听数据库的日志文件