前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >与我一起学习微服务架构设计模式3—微服务架构中的进程间通信

与我一起学习微服务架构设计模式3—微服务架构中的进程间通信

作者头像
java达人
发布2019-10-23 19:35:49
1.8K0
发布2019-10-23 19:35:49
举报
文章被收录于专栏:java达人java达人

选择合适的进程间通信机制是一个重要的架构决策,它会影响应用的可用性,甚至与事务管理相互影响。

概述

交互方式

首先考虑交互方式有助于你专注于需求,避免陷入细节。

一对一

一对多

同步模式

请求/响应

异步模式

异步请求/响应 单向通知

发布/订阅 发布/异步响应

一对一: 每个客户端请求由一个服务实例处理

一对多: 每个客户端请求由多个服务实例处理

单向通知: 客户端的请求发送到服务端,并不期望服务端做出任何响应

发布/订阅方式: 客户端发布通知消息,被零个或多个感兴趣的服务订阅

发布/异步响应方式: 客户端发布请求消息,等待从感兴趣的服务发回的响应

在微服务中定义API

服务的API是服务与其客户端之间的契约,它由客户端结构可以调用的方法、服务发布的事件组成。

挑战:

没有一个简单的编程语言结构来构造和定义服务的API。若使用不兼容的API部署新版本的服务,虽然在编译阶段不会出现错误,但会出现运行时故障。

首先编写接口定义,然后与客户端开发人员一起查看这些接口的定义。只有在反复迭代几轮API定义后,才可以具体服务实现编程。这种预先设计有助于你构建满足客户端需求的服务。

API的演化

挑战:

1、不能够强行要求客户端和服务端API版本保持一致

2、你一般采用滚动升级的方式更新服务,因此一个服务的旧版本和新版本肯定会共存

语义化版本控制

它是一组规则,用于指定如何使用版本号,并且以正确的方式递增版本号,版本号由三部分组成,必须按如下方式递增版本号:

  • MAJOR:当你对API进行不兼容的更改时
  • MINOR:当你对API进行向后兼容的增强时
  • PATCH:当你进行向后兼容的错误修复时

你可以在实现REST API或消息机制的服务时,包含版本号

进行次要且向后兼容的改变

理性情况下应该只进行向后兼容的更改:

  • 添加可选属性
  • 向响应添加属性
  • 添加新操作

服务应该为缺少的请求属性提供默认值,客户端应忽略任何额外的响应属性,这样老版本的客户端能直接只用更新的服务

进行主要且不向后兼容的改变

此时必须在一段时间内同时支持新旧版本的API

假如使用REST,可以在URL中嵌入主要版本号,或者使用HTTP的内容协商机制,在MIME类型中包含版本号。

实现API的服务适配器将包含在旧版本与新版本之间进行转换的逻辑,如API Gateway几乎会使用版本化的API

消息的格式

考虑到以后会扩展到其他语言,我们不应该使用类似java序列化这样跟语言强相关的消息格式

基于文本的消息格式

如JSON和XML,可读性高,自描述的。但往往过度冗长,解析开销过大。

二进制消息格式

对效率和性能敏感的场景下,比较适用。常见的如Protocol Buffers和Avro,它们提供了强类型定义的IDL,编译器会自动根据其格式生成序列化和反序列化的代码,因此你不得不采用API优先的方法来进行服务设计。

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

客户端的业务逻辑调用由RPI代理适配器类实现的接口,RPI代理类向服务发出请求,RPI服务器适配器类通过调用服务的业务逻辑来处理请求

使用REST

REST是使用HTTP协议的进程间通信机制

其关键概念是资源,它通常表示单个业务对象。REST使用HTTP动词操作资源,使用URL引用这些资源。

REST成熟度模型

  • LEVEL 0:只是向服务端点发起HTTP POST请求,进行服务调用
  • LEVEL 1:引入了资源的概念
  • LEVEL 2:使用HTTP动词执行操作
  • LEVEL 3:基于HATEOAS原则设计,基本思想是由GET请求返回的资源信息中包含链接,这些链接能够执行该资源允许的操作

最流行的REST IDL是Open API的规范,他是从Swagger开源项目发展而来的。

一个请求中获取多个资源的挑战

REST资源通常以业务对象为导向,设计REST API时常见问题是如何使客户端能够在单个请求中检索多个相关对象。纯REST API要求客户端发出多个请求,更复杂的情况时需要更多往返并遭受过多延迟,其中一个解决方案是API允许客户端在获取资源时检索相关其他资源,如果情况更复杂耗时,则使用GraphQL和Falcor等替代技术。

把操作映射为HTTP动词的挑战

如何将在业务对象上执行的操作映射到HTTP动词。但很难将多个更新操作映射到HTTP动词,且更新可能不是幂等的,但这却是使用PUT的要求。

一种解决方案是定义用于更新资源的特定方面的子资源,还有就是将动词指定为URL的查询参数。但这不是很符合RESTful的要求。

REST的好处和弊端

好处:

  • 简单熟悉
  • 可使用浏览器扩展或curl来测试API
  • 直接支持请求/响应方式通信
  • HTTP对防火墙友好
  • 不需要中间代理,简化系统架构

弊端:

  • 只支持请求/响应方式通信
  • 没有代理缓冲消息,可能导致可用性降低
  • 客户端必须知道服务实例的位置
  • 在单个请求中获取多个资源具有挑战性
  • 有时很难将多个更新操作映射到HTTP动词
使用gRPC

由于HTTP仅提供有限数量的动词,设计支持多个更新操作的REST API不总是很容易,gRPC可以避免此问题。它是一种跨语言客户端和服务端的框架,基于二进制消息,你可以基于Protocol Buffer的IDL定义gRPC API,能够保持在向后兼容的同时进行变更。

gRPC除简单的请求/响应RPC外,还支持流式RPC。

好处:

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

弊端:

  • 需要更多工作
  • 旧式防火墙也许不支持HTTP/2
  • 也是一种同步通信机制,存在局部故障的问题
使用断路器模式处理局部故障

服务端可能因为故障等无法在有限时间内对客户端请求做出响应,客户端等待响应被阻塞,这可能会在其他客户端甚至使用服务的第三方应用之间传导,导致服务中断。

解决方案:

1、开发可靠的远程过程调用代理,包括:

  • 网络超时机制
  • 限制客户端向服务器发出的请求数量
  • 断路器模式:在连续失败次数超过指定阀值后一段时间内,这个代理会立即拒绝其他调用,稍后重试,若成功则解除断路器

2、从服务失效故障中恢复

  • 服务只是向其客户端返回错误
  • 返回备用值
使用服务发现

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

什么是服务发现

服务发现的关键组件是服务注册表

两种方式实现服务发现:

  • 服务及其客户直接与服务注册表交互
  • 通过部署基础设施来处理服务发现

应用层服务发现模式

它是两种模式的组合

  • 自注册模式:服务实例向服务注册表注册自己
  • 客户端发现模式:客户端从服务注册表检索可用服务实例列表,并在它们之间进行负载均衡

例子:如Euraka,高可用的服务注册表;Euraka java客户端;Ribbon,支持Eureka客户端的复杂Http客户端

好处:可以处理多平台部署的问题

弊端:需要为使用的每种编程语言提供服务发现库。开发者需要负责设置、管理服务注册表,分散一定精力。

平台层服务发现模式

它是两种模式的组合:

  • 第三方注册模式:由第三方负责处理注册,而不是服务本身向服务注册表注册自己
  • 服务端发现模式:客户端不需要查询服务注册表,而是向DNS名称发出请求,请求被解析到路由器,路由器查询服务注册表对请求进行负载均衡。

例子:Docker和Kubernetes

好处:服务发现的所有方面完全由部署平台处理

弊端:仅限于支持使用该平台部署的服务

基于异步消息模式的通信

客户端使用异步消息调用服务

消息传递

消息由消息头部和消息主体组成 类型:

  • 文档 仅包含数据的通用消息
  • 命令 一条等同于RPC请求的消息
  • 事件 表示发送方这一端发生了重要事件

关于消息通道

发送方中的业务逻辑调用发送端接口,该接口由消息发送方适配器实现。消息发送方通过消息通道向接收方发送消息。消息通道是消息传递基础设施的抽象。调用接收方的消息处理程序适配器来处理消息。它调用接收方业务逻辑实现的接收端端口。

类型:

  • 点对点通道:向正在从通道读取的一个消费者传递消息
  • 发布-订阅通道:将一条消息发给所有订阅的接收方
使用消息机制实现交互方式

足够灵活,支持上面描述的所有交互方式

实现请求/响应和异步请求/响应

消息机制本质上是异步的,因此只提供异步请求/响应,但客户端可能会阻塞,直到收到回复。

通过在请求消息中包含回复通道和消息标识符来实现异步请求/响应。接收方处理消息将回复发送到指定的回复通道,回复消息包含与消息标志符具有相同值的相关性ID,用以匹配验证。

实现单向通知

实现发布/订阅

客户端将消息发布到由多个接收方读取的发布/订阅通道,对特定领域对象的事件感兴趣的服务只需订阅相应的通道。

实现发布/异步响应

它把发布/订阅和请求/响应两种方式的元素组合在一起

客户端发布一条消息,在头部指定回复通道,该通道也是发布-订阅通道。消费者将包含相关性ID的回复消息写入回复通道,客户端通过相关性ID来收集响应

为基于消息机制的服务API创建API规范

不像REST,没有广泛采用的标准来记录通道和类型,需要自己定义。服务的异步API一般由消息通道和命令、回复和事件消息类型组成

记录异步操作

  • 请求/异步响应式API
  • 单向通知式API

记录事件发布

服务可使用发布/订阅的方式对外发布事件

使用消息代理

无代理消息

无代理架构中,服务可以直接交换消息,如ZeroMQ

好处:

  • 允许更轻的网络流量和更低的延迟
  • 消除了消息代理可能会成为性能瓶颈或单点故障的可能性
  • 具有较低的操作复杂性

弊端:

  • 服务需要了解彼此的位置
  • 导致可用性降低,发送方和接收方必须同时在线
  • 实现例如确保消息能够成功投递这些复杂功能时挑战性更大

基于代理的消息

如ActiveMQ,Kafka 好处:

  • 发送方不需要知道接收方的网络位置
  • 消息代理缓冲消息,直到接收方能够处理它们

选择消息代理考虑因素:

  • 支持的编程语言
  • 支持的消息标准
  • 消息排序
  • 投递保证
  • 持久性:保存到磁盘且能在代理崩溃时恢复
  • 耐久性:若接收方重新连接到消息代理,是否会收到断开连接时发送的消息
  • 可扩展性
  • 延迟
  • 竞争性接收方:在多线程多实例同时处理消息的情况下,确保消息仅被处理一次,且按照应有的顺序来处理

使用消息代理实现消息通道:

每个消息代理都用自己与众不同的概念来实现消息通道,如Kafka使用主题实现点对点通道和发布-订阅通道,RabbitMQ使用交换+队列实现点对点通道,使用组播式交换和每客户端队列实现发布-订阅通道

好处:

  • 松耦合:客户端不需要感知服务实例的位置
  • 消息缓存:发送方和接受方不要求一定同时在线
  • 灵活的通信:支持前面所述的所有交互方式
  • 明确的进程间通信:与RPC相比,程序员不会陷入类似“本地调用”的那种“太平盛世”的感觉

弊端:

  • 潜在的性能瓶颈,不过可以横向扩展
  • 潜在的单点故障,不过现代消息代理大部分是高可用的
  • 额外的操作复杂性
处理并发和消息顺序

如何在保留消息顺序的同时,横向扩展多个接收方的实例

采用分片通道方案,如将orderId作为分片键,特定订单的每个事件都发布到同一个分片,该消息也由同一个接收方实例读取

1、分片通道由两个或多个分片组成,分片的行为类似于通道

2、发送方在消息头部指定分片键,消息代理使用分片键将消息分配给分片

3、消息代理将接收方的多个实例组合在一起。并将它们视为相同的逻辑接收方,如kafka中的消费者组。消息代理将每个分片分配给单个接收器。

处理重复消息

正常情况下,保证传递的消息代理只会传递一次消息。但故障可能导致消息被多次传递。

两种方法处理重复消息:

编写幂等消息处理器:

幂等指这个应用被相同输入参数多次重复调用时,也不会产生额外的效果,但要保证消息代理在重新传递消息时保持相同顺序。

跟踪消息并丢弃重复消息:

简单的解决方案是消息接收方使用message id跟踪它已处理的消息并丢弃任何重复项

事务性消息

数据库更新和消息发送都必须在事务中进行,否则系统可能处于不一致状态。

使用数据库表作为消息队列

通过事务性发件箱模式,即将事件或消息保存在数据库的OUTBOX表中,将其作为数据库事务的一部分发布。

将消息从数据库移动到消息代理的两种方法:

通过轮询模式发布事件 轮询数据库中的发件箱,将消息发送给消息代理,它在小规模下运行良好,但经常轮询数据库可能会导致数据库性能下降

使用事务日志拖尾模式发布事件 应用提交到数据库的更新对应着数据库事务日志中的一个条目。事务日志挖掘器可以读取事务日志,将跟消息有关的记录发送给消息代理。

其挑战在于需要一些开发努力,现有框架有Debezium,Eventuate Tram等。

消息相关的类库和框架

直接使用消息代理客户端库的弊端:

  • 客户端库将发布消息的业务逻辑耦合到消息代理API
  • 客户端库是非常底层的,需要常编写重复类似的代码
  • 不支持更高级别的交互

更好的方法是使用更高级别的库或框架,如Eventuate Tram

使用异步消息提高可用性

同步消息会降低可用性

如REST,当服务必须从另一个服务获取信息后才能返回它客户端的调用,就会导致可用性问题。每增加一个额外的服务,会更进一步降低可用性。

要最大化一个系统的可用性,就应该最小化系统的同步操作量

消除同步交互

方法:

使用异步交互模式:

客户端和服务端使用消息通道发送消息来实现异步通信。这种架构很有弹性,消息代理会一直缓存消息,直到服务端接收并处理消息。但服务很多情况采用同步通信协议的外部API,需要对请求立即作出响应。

复制数据:

服务维护一个数据副本,这些数据是服务在处理请求时需要使用的,数据的源头会在数据发生变化时发出消息,服务订阅这些消息来确保数据副本的实时更新。

弊端:

  • 数据量巨大时效率低下
  • 没有从根本上解决服务如何更新其他服务所拥有的数据这个问题

先响应,后处理

如Order Service,它在不调用任何其他服务的情况下创建订单,然后通过与其他服务交换信息来异步验证新创建的Order

优点:即使其他服务中断, Order Service仍然会创建订单响应客户

弊端:为了使客户端知道订单是否已成功创建,需要定期轮询或者向客户端发送通知。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
    • 交互方式
      • 在微服务中定义API
        • API的演化
          • 消息的格式
          • 基于同步远程过程调用模式的通信
            • 使用REST
              • 使用gRPC
                • 使用断路器模式处理局部故障
                  • 使用服务发现
                  • 基于异步消息模式的通信
                    • 消息传递
                      • 使用消息机制实现交互方式
                        • 为基于消息机制的服务API创建API规范
                          • 使用消息代理
                            • 处理并发和消息顺序
                              • 处理重复消息
                                • 事务性消息
                                  • 消息相关的类库和框架
                                  • 使用异步消息提高可用性
                                    • 同步消息会降低可用性
                                      • 消除同步交互
                                      相关产品与服务
                                      数据库
                                      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档