前言
在介绍RabbitMQ之前我们首先需要对消息队列中间件和AMQP协议做简单了解。
消息队列中间件
消息队列中间件(Message Queue Middleware,简称MQ),是指利用高效可靠的消息传递机制进行与平台无关的数据交流,它可以在分布式环境下扩展进程间的数据通讯,并基于数据通讯来进行分布式系统的集成。它主要有以下使用场景:
- 项目解藕:不同项目或模块可以使用消息中间件进行数据传递,从而保证模块的相对独立性
- 流量削峰:可以通过将突发流量(如秒杀数据)写入消息中间件,然后由多个消费者进行异步处理
- 弹性伸缩:可以通过对消息中间件进行横向扩展来提高系统的处理能力和吞吐量
- 发布订阅:可以用于任意的发布订阅模式中
- 异步处理:当我们不需要对数据进行立即处理,或者不关心数据的处理结果时,可以使用中间件进行异步处理
- 冗余存储:消息中间件可以对数据进行持久化存储,直到你消费完成后再进行删除
AMQP协议
AMQP(Advanced Message Queuing Protocol)是一个提供统一消息服务的应用层通讯协议,为消息中间件提供统一的开发规范。客户端只需要遵守AMQP协议进行开发,而不需要限制开发语言和技术。AMQP协议可分为以下三层:
- Module Layer:位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以通过这些命令与服务器通讯实现一些自己的业务逻辑。
- Session Layer:位于中间层,主要负责将客户端命令发送给服务器,再将服务器的应答返回给客户端,主要为客户端与服务器之间的通讯提供可靠性同步机制和错误处理。
- Transport Layer:位于最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
简介
RabbitMQ是基于Erlang语言完全实现AMQP协议的消息中间件,主要含有以下特性:
- 支持多种消息传递协议,除了AMQP外,还支持通过插件支持STOMP协议和MQTT3.1协议
- 拥有丰富的交换器类型,可以满足大部分使用需求
- 支持多语言开发、多种部署方式
- 可以通过集群来实现高可用性和高吞吐,还可以通过Federation插件来连接跨机房跨区域的不同版本的服务节点
- 插拔式的身份验证和授权,支持TLS和LDAP
- 能够使用多种方式进行监控和管理,如HTTP API、命令行工具和UI界面
模型架构
通常我们谈到消息队列服务,会想到三个概念:消息发送者、消息队列、消息接受者,RabbitMQ在这个概念之上多做了一层抽象,即在消息发送者和消息队列之间加入了交换器(Exchange),这样消息发送者就和消息队列没有直接联系,转而变成消息发送者把消息发给交换器,交换器根据调度策略再把消息转发给具体的消息队列。
- Publisher(发布者):发布者(或称为生产者)负责生产消息并将其投递到指定的交换器上
- Message(消息):消息由消息头和消息体组成,消息头用于存储与消息相关的元数据,如目标交换器名称(exchange_name)、路由键(RountingKey)和其他可选配置信息。消息体为实际需要传递的数据。
- Exchange(交换器):交换器负责接收来自生产者的消息,并将消息路由到一个或多个队列中,如果路由不到则直接丢弃或返回,这取决于交换器的mandatory属性
- BindingKey(绑定键):交换器与队列通过BindingKey建立绑定关系
- Routingkey(路由键):生产者将消息发给交换器的时候,一般会指定一个RountingKey,用来指定这个消息的路由规则,当Routingkey与BindingKey基于交换器类型的规则相匹配时,消息会被路由到对应的队列中
- Queue(消息队列):用于存储路由过来的消息,多个消费者可以订阅同一个消息队列,此时队列会将收到的消息以轮训的方式分发给所有消费者。即保证每条消息只会发送给一个消费者,不会出现一条消息被多个消费者重复消费的情况
- Consumer(消费者):消费者订阅感兴趣的队列,并负责消费存储在队列中的消息,为了保证消息能够从队列可靠的到达消费者,RabbitMQ提供了消息确认机制(message acknowledgement),并通过autoAck参数进行控制
- Channel(信道):RabbitMQ来用类似于NIO的设计,通过Channel来复用TCP连接,并确保每个Channel的隔离性,就像是拥有独立的Connection连接。当数据流量不是很大时,采用连接复用技术可以避免创建过多的TCP连接而导致昂贵的性能开销。
- Virtual Host(虚拟主机):RabbitMQ通过虚拟主机来实现逻辑分组和资源隔离,一个虚拟主机就是一个小型的RabbitMQ服务器,拥有独立的队列、交换器和绑定关系,用户可以按照不同的业务场景建立不同的虚拟主机,虚拟主机之间是完全独立的,这可以极大的保证业务之间的隔性和数据安全。默认的虚拟主机名为
/
- Broker:一个真实部署运行的RabbitMQ服务
交换器类型
RabbitMQ支持多种交换器类型,常见的有以下四种:
- fanout
这是最简单的一种交换器模型,此时会把消息路由到与该交换器绑定的所有队列中,如下图,任何发送到X交换器上的消息,都会分两分分别发送到Q1、Q2两个队列上
- direct
根据消息的RountingKey将其发送到匹配的BindingKey的队列中,如下图,当消息的RountingKey为orange时,由于队列Q1与交换器X的BindingKey也为orange,则消息将被路由到Q1队列中,其他同理。
- topic
将消息路由到BindingKey和RountingKey相匹配的队列中,匹配规则如下:
BindingKey和RountingKey由多个单词使用逗号.
进行连接
BindingKey支持两个特殊符号:#
和*
。其中*
用于匹配一个单词,#
用于匹配零个或者多个单词。
以下是官方文档中的示例,交换器与队列的绑定情况如图所示,此时的路由情况如下:
路由键为lazy.orange.elephant
的消息会发送给所有队列;
路由键为quick.orange.fox
的消息只会发送给 Q1 队列;
路由键为lazy.brown.fox
的消息只会发送给 Q2 队列;
路由键为lazy.pink.rabbit
的消息只会发送给 Q2 队列;
路由键为quick.brown.fox
的消息与任何绑定都不匹配;
路由键为orange
或quick.orange.male.rabbit
的消息也与任何绑定都不匹配。 - headers
在交换器与队列进行绑定时可以指定一组键值对作为 BindingKey;在发送消息的 headers 中的可以指定一组键值对属性,当这些属性与 BindingKey 相匹配时,则将消息路由到该队列。同时还可以使用 x-match 参数指定匹配模式:
x-match = all :所有的键值对都相同才算匹配成功;
x-match = any:只要有一个键值对相同就算匹配成功。
headers 类型的交换器性能比较差,因此其在实际开发中使用得比较少。
死信队列
“死信”是RabbitMQ的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:
- 消息被否定确认,使用
channel.basicNack
或channel.basicReject
,并且此时requeue属性被设置为false
- 消息在队列的存活时间超过设置的TTL时间
- 消息队列的消息数量已经超过最大队列长度
那么该消息将成为死信,死信消息会被RabbitMQ进行特殊处理,如果配置了私信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
配置一个死信队列大致可以分为以下几个步骤: - 配置业务队列,绑定到业务交换机上
- 为业务队列配置死信交换机和路由key
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机 args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE); // x-dead-letter-routing-key 这里声明当前队列的死信路由key args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
- 为死信交换机配置死信队列