服务间通信

# 服务间通信

# 一、服务发现

服务实例具有动态分配的网络位置. 此外, 由于自动扩展, 故障和升级, 实例集合会动态更改, 因此客户端代码必须使用服务发现

# 应用层发现模式

常用的应用层服务发现组件如eureka,nacos

  • 服务机器客户端直接与服务注册表交互
  • 通过部署基础设施来时间服务发现

# 平台服务发现模式

现代部署平台中内置服务注册表和服务发现机制. 如docker + k8s

部署平台中的注册器观察服务实例, 为其分配DNS名称, 虚拟IP(VIP)和解析为VIP的DNS名称, 客户端想dns名称和vip发起请求, 部署平台自动将请求路由到其中一个可用的服务实例.

优点是服务发现的所有方面完全由部署平台处理, 服务和客户端都不包含任何服务发现代码. 对语言和框架没有限制

# 二、基于同步的远程过程调用模式的通信

# REST

REST是一种(总是)使用http协议的进程间通讯机制, 关键概念是资源, 采用http动词操作资源.

# 成熟度模型(从低到高)

  1. 客户端只是向服务端发起POST请求, 进行服务调用, 每个请求都指明了需要执行的操作和这个操作针对的目标
  2. 要执行对资源的操作, 客户端需要发出指定要执行的操作和包含任何参数的POST的请求
  3. 使用HTTP动词进行操作
  4. 基于HATEOAS(hypertext as the engine of application state) 设计原则, 基本思想是在由GET请求返回的资源中包含链接, 这些连接能够执行该资源允许的操作

# 定义

将操作映射为HTTP动词

    POST: /user  	新建用户
    GET: /user/id 	获取某个用户信息
    DELETE: /user/id 	删除某个用户
    PUT:/user    	修改某个用户信息
1
2
3
4

# 弊端

  • 只支持请求/响应的通信
  • 可能导致可用性降低. 由于客户端和服务端直接通信没有代理缓冲消息, 因此必须保证双方在调用期间都在线
  • 客户端必须知道服务实例的位置
  • 单个请求中获取多个资源具有挑战性
  • 将多个更新操作映射到HTTP动词困难

# gRPC

gRPC可以是REST的替代品

gRPC是一种基于二进制消息的协议.

gRPC API 由一个或多个服务和请求/响应消息定义组成, 服务定义类似于JAVA接口, 是强类型方法的集合

gRPC使用Protocol Buffers作为消息格式, 这是一种高效且紧凑的二进制格式, 是一种标记格式

Protocol Buffers消息的每个字段都有编号, 并且有一个类型代码

消息接收方可以提起所需字段, 并跳过他无法识别的字段, 因此gRPC能够保持向后兼容的同时进行变更

# 好处

  • 设计具有复杂更新操作的API非常简单
  • 具有高效, 紧凑的进程间通信机制, 尤其是在交换大量消息时
  • 支持在远程过程调用和消息传递过程中使用双向流式消息方式
  • 实现了客户端和各种语言编写的服务端之间的互操作性

# 弊端

  • 与基于REST/JSON的API机制相比, js客户端使用基于gRPC的API需要更多的操作
  • 旧式防火墙可能不支持HTTP/2

# 处理服务端无响应故障

  • 断路器模式

监控客户端发出请求的成功和失败数量, 失败比例超过一定阈值后启动断路器, 让后续调用立刻失效, 进过一定时间后客户端应该继续尝试, 如果调用成功, 解除断路器. 常用的断路器: Spring Cloud Hystrix

  • 网络超时

不要做成无限阻塞, 要设置timeout

  • 限制客户端请求数量

把客户端能够向特定服务发起的请求设置一个上线, 若请求达到上限, 请求立刻失败. 常见的手段: 令牌桶

  • 从服务失效故障中恢复

服务只需向其他客户端返回错误

返回备用值(默认值或者缓存响应). 如果查询操作中存在调用多个服务的场景, 每个服务的数据对客户来说重要性不同, 重要数据应返回其数据的缓存版本或错误; 非重要数据可以返回缓存版本或直接忽略.

# 三、基于异步消息模式的通信

# 消息类型

  • 文档: 仅包含数据的通用信息
  • 命令: 等同于RPC请求的消息, 指定要调用的操作及其参数
  • 事件: 标示发生方发生了重要的事件, 事件通常是领域事件, 标示领域对象的状态更改

# 有代理型消息架构

消息代理是所有消息的中间节点, 发送放写入消息到消息代理, 消息代理将消息发送到接收方

优点

  • 松耦合
  • 消息可以缓存
  • 通信灵活
  • 支持请求/响应模式
  • 支持请求/异步响应模式
  • 支持发布/订阅
  • 支持单向通知
  • 支持发布/异步响应模式

通过把发布/订阅和请求/响应两种方式组合实现

客户端发布消息, 小系统中指定回复通道, 这个回复通道也是一个发布/订阅通道; 消费者将包含相关性ID的回复消息写入回复通道, 客户端通过使用相关性ID收集响应

  • 明确的进程间通信

缺点

  • 消息代理潜在的性能瓶颈(可以通过集群解决)
  • 潜在的单点故障(大多数高可用, 无须担心)
  • 额外的操作复杂性
  • 并发与保证顺序
  1. 通道进行分片, 每个分片的行为类似一个通道
  2. 发送方在消息头部指定分片key, 消息代理通过key分配到指定分片
  3. 消息代理将多个接收方实例组合, 并视为相同的逻辑接收方. 如kafka的group

以上三步保证特定消息分配到同一个分片, 并且该分片中的消息始终有同一个接收方实例读取, 由此来保证顺序处理

  • 处理复杂操作

消息中进行幂等处理 跟踪消息并丢弃重复项, 如messageId 唯一键

消息代理技术选型影响因素

  • 支持的编程语言
  • 支持的消息标准. 是否支持多种, 还是仅支持专用
  • 消息是否有序
  • 提供什么样的投递保证
  • 持久性. 是否可以持久化到磁盘并在崩溃后恢复
  • 耐久性. 接收方重连后能否收到断开连接时发送的消息
  • 可扩展性
  • 延迟
  • 竞争性接收方. 如kafka中一个group智能一个订阅者接收消息

基于消息机制的API规范

必须指定消息通道的名称, 通过通道交换的信息类型及其格式.

记录异步操作的API规范

  • 请求/异步响应式API.

包括服务的命令消息通道, 服务接收的命令式消息的具体类型和格式

  • 单向通知式API

包括服务的命令消息通道, 以及服务接收的命令式消息的具体类型和格式

记录事件发布, 通过发布/订阅的方式对外发布事件, 包括事件通道和发布到通道的事件式消息的类型和格式

# 无代理型消息架构

服务可以直接交换消息, 典型代表是ZeroMQ, 它及时规范, 也是适用于不同编程语言的库, 支持各种传输协议, 包括TCP, UNIX风格的套接字和多播

好处是更轻的网络流量与更低的延迟, 没有消息代理成为性能瓶颈和单点故障的可能性

缺点是发送放和接收方必须同时在线, 并且服务需要了解彼此的位置

# 事务性消息

服务通常需要在更新数据库的事务中发布消息, 如发布领域事件时. 要保证数据库更新和消息发送在事务中进行可以采取事务表的方式, 将数据库作为消息队列

  1. 发送的消息插入到消息表中, 作为增删改业务对象的数据库事务的一部分, 因为是本地事务, 可以保证原子性
  2. 将消息表中的消息发布到消息队列中.

发布事务消息的方式

  • 轮询事务表发布消息, 但这会降低性能, 并且在NOSQL中可能无法查询指定消息
  • 切面发布, doAround后发布消息表中的消息(最优)
  • 事务日志拖尾模式发布

每次应用程序更新数据库都对应着数据库事务日志的一个条目, 事务日志挖掘起可以读取事务日志, 把每条跟消息有关的记录发送给消息代理

# 使用异步消息提高可用性

例如创建订单的创建, 在操作order服务的同时会涉及customer和restaurant服务

    public void createOrder(OrderCreateCommand command){
    
        boolean customerCheckResult = customerService.check(command.getCustomer());
        boolean restaurantCheckResult = restaurantService.check(command.getRestaurant());
        
        if(customerCheckResult && restaurantCheckResult){
            Order order = orderFactory.create(command);
            orderRepository.save(order);
        }
    }
1
2
3
4
5
6
7
8
9
10

实现方式

  • 所有交互全部异步

    1. 客户端发起create请求
    2. orderService收到请求, 分别异步请求customerService和restaurantService验证信息
    3. orderService收到验证信息, 完成orderCreate操作, 返回响应
  • 复制数据(消除上帝类的另一可用之处)

orderService自包含customer和restaurant中需压迫验证的相关数据, 直接在本服务中验证.

customer中数据变更时发出消息, orderService订阅更新自包含数据

优点是不需要服务交互即可完成订单创建, 缺点是复制数据量过大会导致性能降低, 并且数据复制不能保证同步

  • 先返回响应, 再完成处理

    1. order直接创建返回响应
    2. 异步请求其他服务验证信息
    3. 其他服务返回错误状态, orderService收到消息, 将order改为对应的错误状态
    4. orderService通知客户端刚创建的order验证失败

但这种做法会使客户端更复杂, 客户端知道order创建成功, 但不久后返回失败

上次更新: 2022/1/6 15:29:54