在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来,由于该主题中产生的消息会被所有实例监听和消费, 所以称它为消息总线。
在总线上的各个实例都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息,例如配置信息的变更或者其他一些管理操作等。由于消息总线在微服务架构系统中被广泛使用,所以它同配置中心一样,几乎是微服务架构中的必备组件。Spring Cloud 作为微服务架构综合性的解决方案,对此自然也有自己的实现,即 Spring Cloud Bus。 通过使用 Spring Cloud Bus,可以非常容易地搭建起消息总线,同时实现一些消息总线中的常用功能, 比如配合 Spring Cloud Config 实现微服务应用配置信息的动态更新等。
消息代理
消息代理 (Message Broker) 是一种消息验证、传输、路由的架构模式。它在应用程序之间起到通信调度并最小化应用之间的依赖的作用,使得应用程序可以高效地解耦通信过程。
在整个生产消费过程中,生产和消费是一个异步操作,这也是在分布式系统中要使用消息代理的重要原因,所以可以使用通信来解耦业务逻辑。通过生产消费模式的异步操作,系统间调用就没有同步调用需要那么高的实时性要求,同时也更容易控制处理的吞吐量以保证系统的正常运行等。
消息代理是一个中间件产品,它的核心是一个消息的路由程序,用来实现接收和分发消息,并根据设定好的消息处理流来转发给正确的应用。它包括独立的通信和消息传递协议,能够实现组织内部和组织间的网络通信。 设计代理的目的就是为了能够从应用程序中传入消息,并执行一些特别的操作,下面这些是在企业应用中,经常需要使用消息代理的场景:
-
将消息路由到一个或多个目的地
-
消息转化为其他的表现方式。
-
执行消息的聚集、消息的分解,并将结果发送到它们的目的地,然后重新组合响应返回给消息用户。
-
调用 Web 服务来检索数据。
-
响应事件或错误。
-
使用发布 - 订阅模式来提供内容或基于主题的消息路由。
目前已经有非常多的开源产品可供使用, 如:ActiveMQ、Kafka、RabbitMQ、RocketMQ。
AMQP
AMQP, Advanced Message Queuing Protocol,它是一个面向消息中间件的开放式标准应用层协议。它定义了以下这些特性:
-
消息方向
-
消息队列
-
消息路由(包括点到点和发布-订阅模式)
-
可靠性
-
安全性
AMQP 要求消息的提供者和客户端接收者的行为要实现对不同供应商可以用相同的方式(比如SMTP、HTTP、FTP等)进行互相操作。在以往的中间件标准中,主要还是建立在 API 级别,如 JMS,集中于通过不同的中间件实现来建立标准化的程序间的互操作性,而不是在多个中间件产品间实现互操作性。
AMQP 与 JMS 不同,JMS 定义了一个 API 和一组消息收发必须实现的行为,而 AMQP 是一个线路级协议。 线路级协议描述的是通过网络发送的数据传输格式。因此任何符合该数据格式的消息发送和接收工具都能互相兼容和进行操作,这样就能轻易实现跨技术平台的架构方案。
RabbitMQ 实现消息总线
RabbitMQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件,也称为面向消息的中间件。RabbitMQ 服务器是用高性能、可伸缩而闻名的 Erlang 语言编写而成的,其集群和故障转移是构建在开放电信平台框架上的。
RabbitMQ 实现了 AMQP 协议,所以它可以支持多种操作系统、多种编程语言,几乎可以覆盖所有主流的企业级技术平台。在微服务架构消息中间件的选型中,它是一个非常适合且优秀的选择。因此在 Spring Cloud Bus 中默认包含了对 Rabbit 的自动化配置。
RabbitMQ 基本概念
-
Broker: 可以理解为消息队列服务器的实体,它是一个中间件应用,负责接收消息生产者的消息,然后将消息发送至消息接收者或者其他的 Broker。
-
Exchange: 消息交换机,是消息第一个到达的地方,消息通过它指定的路由规则,分发到不同的消息队列中去。
-
Queue: 消息队列,消息通过发送和路由之后最终到达的地方,到达 Queue 的消息即进入逻辑上等待消费的状态。每个消息都会被发送到一个或多个队列。
-
Binding: 绑定,它的作用就是把 Exchange 和 Queue 按照路由规则绑定起来,也就是 Exchange 和 Queue 之间的虚拟连接。
-
Routing Key: 路由关键字,Exchange 根据这个关键字进行消息投递。
-
Virtual host: 虚拟主机,它是对 Broker 的虚拟划分,将消费者、生产者和它们依赖的 AMQP 相关结构进行隔离,一般都是为了安全考虑。比如,可以在一个 Broker 中设置多个虚拟主机,对不同用户进行权限的分离。
-
Connection: 连接,代表生产者、消费者、Broker 之间进行通信的物理网络。
-
Channel: 消息通道,用于连接生产者和消费者的逻辑结构。在客户端的每个连接里,可建立多个 Channel,每个 Channel 代表一个会话任务,通过 Channel 可以隔离同一 连接中的不同交互内容。
-
Producer: 消息生产者,制造消息并发送消息的程序。
-
Consumer: 消息消费者,接收消息并处理消息的程序。
消息投递到队列的整个过程大致如下:
-
客户端连接到消息队列服务器,打开一个 Channel。
-
客户端声明一个 Exchange,并设置相关属性。
-
客户端声明一个 Queue, 并设置相关属性。
-
客户端使用 Routing Key, 在 Exchange 和 Queue 之间建立好绑定关系。
-
客户端投递消息到 Exchange。
-
Exchange 接收到消息后,根据消息的 Key 和已经设置的 Binding,进行消息路由,将消息投递到一个或多个Queue 里。
Exchange 有以下类型:
- Direct 交换机:完全根据 Key 进行投递。比如,绑定时设置了 Routing Key 为 abc,那么客户端提交的消息,只有设置了 Key 为 abc 的才会被投递到队列。
- Topic 交换机:对 Key 进行模式匹配后进行投递,可以使用符号#匹配一个或多个词,符号*匹配正好一个词。如 abc.# 匹配 abc.def.ghi,abc.* 只匹配 abc.def。
- Fanout 交换机:不需要任何 Key,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。
RabbitMQ 支持消息的待久化,也就是将数据写在磁盘上。为了数据安全考虑,大多数情况下都会选择持久化。消息队列持久化包括3个部分:
- Exchange 持久化,在声明时指定 durable => 1。
- Queue 持久化,在声明时指定 durable => 1。
- 消息持久化,在投递时指定 delivery_mode => 2 (1是非持久化)。
如果 Exchange 和 Queue 都是持久化的,那么它们之间的 Binding 也是持久化的。
如果 Exchange 和 Queue 两者之间有一个是持久化的,一个是非持久化的,就不允许建立绑定。
参考:
《Spring Cloud 微服务实战》翟永超 著