RabbitMQ基础

1. 简介

RabbitMQ是由Erlang语言编写,实现了AMQP(Advanced Message Queuing Protocol)的消息中间件,常用于分布式系统之间的信息传递。
有以下特点:

  • 可靠性:持久化、传输确认及发布确认
  • 灵活路由:一些内置交换器提供典型的路由功能,针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器
  • 扩展性:多个RabbitMQ节点可以组成一个集群,可以动态扩展集群中节点
  • 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列依然可用
  • 多种协议:除了原生支持AMQP协议,还支持STOMP、MQTT等多种消息中间件协议
  • 多语言客户端:支持常用语言,如Java、Python、Ruby、PHP、C#、JavaScript等
  • 管理界面:提供简易用户界面( 默认http://localhost:15672 ),可以监控和管理消息、集群中的节点等
  • 插件机制:提供了许多插件,以实现从多方面进行扩展,也可以编写自己的插件

2. 架构分析

2.1 AMQP协议

AMQP协议包括3层:

  • Module Layer:位于协议最高层,主要定义一些客户端调用命令,客户端可以利用这些命令实现自己的业务逻辑。如客户端可以使用Queue.Declare命令声明队列。
  • Session Layer:位于中间层,主要负责将客户端命令发送给服务器,在将服务端应答返回给客户端,主要为客户端和服务器之间的通信提供可靠同步机制和错误处理。
  • Transport Layer:位于最底层,主要传输二进制数据流,提供帧的处理,信道复用,错误检测和数据表示等。

AMQP是通过协议命令进行交互的,可以看做是一系列结构化命令的集合,如Queue.Declare,Basic.Consume等。
AMQP模型架构:消息发布到交换器,交换器通过一些绑定规则分发消息到相应队列中,消费者通过订阅或者直接拉取,获取队列中的消息。

2.2 RabbitMQ结构

RabbitMQ基础_第1张图片

Producer:生产者,负责投递消息给Exchange。消息一般由EXCHANGE、ROUTING_KEY以及payload(业务方JSON串)组成。
Consumer:消费者,接收消息的一方。消费者从队列中消费消息时,只是消费payload,也就不知道生产者方的信息,因为消息存入队列后会丢掉EXCHANGE、ROUTING_KEY等其他标签。
Broker:消息中间件的服务节点。一个Broker可以看做一个RabbitMQ实例。
Queue:队列。RabbitMQ中消息都只能存储在队列中,支持持久化。Kafka将消息存储在topic这个逻辑层面,而相应的队列逻辑只是topic实际存储文件中的位移标识。
多个消费者可以订阅同一队列,这时队列中的消息会被平均分摊(Round-Robin 轮询)给多个消费者进行处理,而不是每个消费者都可以收到所有消息。不支持队列层面的广播消费。
Exchange:交换器,将消息路由到一个或多个队列中,如果路由不到会返回给生产者或直接丢弃。
生产者发送消息先发送到交换器,生产者绑定交换器时会给交换器传递一个ROUTING_KEY,队列自身有一个BINDING_KEY,交换机通过BINDING_KEY与ROUTING_KEY的匹配路由到相应队列并发送消息给队列。
交换器有 fanout, direct, topic, header 四种。

  • fanout: 把发送到该交换器的消息路由到所有与交换器绑定的队列
  • direct: 把发送到该交换器的消息路由到BINDING_KEY完全匹配交换器ROUTING_KEY的队列
  • topic: 把发送到该交换器的消息路由到BINDING_KEY模糊匹配交换器ROUTING_KEY的队列,常用于广播消息到多个指定队列
    匹配规则:
  • ROUTING_KEY和BINDING_KEY是由“ . ”号分隔的字符串(如com.rabbit.client,每个分隔开的字符串为一个独立的单词)
  • BINDING_KEY用于模糊匹配,可以存在特殊字符“ * ”和“ # ”,“ # ”用于匹配单个单词,“ * ”号用于匹配零个或多个单词。
  • header: 把发送到该交换器的消息路由到完全匹配交换器消息头属性的队列。header交换器没有用BINDING_KEY作为匹配方式,与direct类似,但在direct的基础上,header还支持更加丰富的BINDING_KEY数据类型,可以是整型或者哈希。

3. 连接原理

RabbitMQ基础_第2张图片

无论是生产者还是消费者,都需要和rabbitMQ Broker建立一条TCP连接(Connection),客户端可以在Connection上创建一个AMQP信道(Channel),每个信道都会被指派一个唯一的ID。信道是建立在Connection之上的虚拟连接,RabbitMQ的每条指令都是基于信道的。
引入信道是为了避免多个客户端连接Broker时建立多个TCP连接,造成性能瓶颈。
每个线程一个信道,信道复用了Connection的TCP连接,同时RabbitMQ可以确保每个线程的独立性。当每个信道流量不大时,多个信道可以复用一个Connection,但当信道中的流量很大时,一个Connection就会出现性能瓶颈,这时就需要将流量分摊到多个Connection了。

4. Channel主要方法清单

以下只列出参数最全的方法,其他重载方法不做说明

basicPublish

生产者发布一条消息。如果交换器不存在,会产生通道协议异常,并且关闭当前通道。

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, AMQP.BasicProperties props, byte[] body) throws IOException

Parameters:
exchange - 消息要发布到的交换器,如果设为空字符串则会发布到默认交换器
routingKey - 路由键
mandatory - 如果设置了mandatory标签则为true。true:交换器无法路由到任何符合条件的队列,则会通过basic.return返还给生产者。false:出现以上情形直接丢弃该消息
immediate - 如果设置了immediate标签则为true。true: 如果交换器路由消息的队列上没有消费者,这条消息不会放入队列;如果交换器路由到的所有队列都没有消费者,则会通过basic.return返还给生产者。 注意 RabbitMQ 3.0 版本开始去掉了此参数的支持.
props - 消息的基本属性集。成员有:contentType,contentEncoding,headers(Map),deliveryMode(消息投递模式),priority(消息优先级),correlationId(关联请求和其调用RPC之后的回复),replyTo(回调队列),expiration,messageId,timestamp,type,userId,appId,clusterId。
body - 消息体

exchangeDeclare

声明交换器

AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String,Object> arguments) throws IOException`

Parameters:
exchange - 交换器名字
type - 交换器类型(direct, fanout, headers, topic)
durable - 是否持久化 (存储在磁盘,服务器重启时依旧存在)
autoDelete - 自动删除的前提是至少有一个交换器或队列与之绑定,自动删除操作是当交换器所有绑定都解除了,就会被自动删除
internal - 是否属于内部交换器(对于内部使用的交换器,不能由客户端直接发布)
arguments - 可以传一些其他参数
exchangeDeclareNoWait
与exchangeDeclare一样,但是没有返回AMQP.Exchange.DeclareOk,表示该方法无需等待服务器返回创建结果
exchangeDeclarePassive
声明一个已经存在指定名字的交换器,如果交换器不存在会引发404通道异常

AMQP.Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException

exchangeDelete

删除交换器

AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException

Parameters:
exchange - 交换器名字
ifUnused - 是否删除无客户端使用的交换器
exchangeDeleteNoWait
与exchangeDelete一样,但是没有返回AMQP.Exchange.DeleteOk,表示该方法无需等待服务器返回删除结果

exchangeBind

绑定一个交换器到另一个交换器上

AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map<String,Object> arguments) throws IOException

Parameters:
destination - 目的交换器
source - 来源交换器
routingKey - 交换器绑定的路由键
arguments - 一些其他的绑定参数
exchangeBindNoWait
与exchangeBind一样,但是没有返回AMQP.Exchange.BindOk,表示该方法无需等待服务器返回绑定结果

exchangeUnbind

解绑交换器与交换器的绑定

AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map<String,Object> arguments) throws IOException

Parameters: 参数与exchangeBind一致
exchangeUnbindNoWait
与exchangeUnbind一样,但是没有返回AMQP.Exchange.UnbindOk,表示该方法无需等待服务器返回解绑结果

queueDeclare

声明一个队列

AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String,Object> arguments) throws IOException

Parameters:
queue - 队列名
durable - 是否持久化 (服务器重启时依旧存在)
exclusive - 是否为排他队列 (仅限于在声明它的当前连接的所有通道中使用);排他队列即使声明为持久化的,一旦连接关闭或者所有客户端断开连接,也会被自动删除
autoDelete - 自动删除的前提是至少有一个客户端与之绑定,自动删除操作是当队列所有绑定都解除了,就会被自动删除
arguments - 一些其他的参数。有 x-massage-ttl(队列中的消息过期时间),x-max-priority(指定了就代表队列里的消息是有优先级的,指定的数值代表最大优先级数,若队列中消息的优先级设置的值大于最大优先级数,一律按最大优先级数处理;若指定了队列的最大优先级数,则此后发送消息时都需要设置消息当前的优先级) 等。
queueDeclareNoWait
与queueDeclare一样,但是没有返回AMQP.Queue.DeclareOk,表示该方法无需等待服务器返回队列创建结果
queueDeclarePassive
声明一个已经存在指定名字的队列,如果队列不存在,或在另一个Connection中为排他队列,会引发IO异常。

queueDelete

删除一个队列

AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException

Parameters:
queue - 队列名
ifUnused - 是否删除无客户端使用的队列
ifEmpty - 是否删除空的队列
queueDeleteNoWait
与queueDelete一样,但是没有返回AMQP.Queue.DeleteOk,表示该方法无需等待服务器返回队列删除结果

queueBind

绑定一个队列到交换器

AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String,Object> arguments) throws IOException

Parameters:
queue - 队列名
exchange - 交换器的名字
routingKey - 当前绑定的路由键
arguments - 传递一些其他参数
queueBindNoWait
与queueBind一样,但是没有返回AMQP.Queue.BindOk,表示该方法无需等待服务器返回队列绑定结果

queueUnbind

解绑交换器的一个队列

AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map<String,Object> arguments) throws IOException

Parameters: 参数与queueUnbind一致

queuePurge

清空给定队列内容

AMQP.Queue.PurgeOk queuePurge(String queue) throws IOException

basicGet

获取队列中的一条消息

GetResponse basicGet(String queue, boolean autoAck) throws IOException

Parameters:
queue - 队列名
autoAck - 是否需要自动应答

basicAck

应答一条或多条消息。 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 的方法里提供了传递标签的值(deliveryTag),并且该方法中包含已被应答的消息。

void basicAck(long deliveryTag, boolean multiple) throws IOException

Parameters:
deliveryTag - 来自接收消息的AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 的标签(tag)
multiple - true: 应答所有消息,匹配给定传递标签的消息,false: 只应答匹配给定传递标签的消息。

basicNack

拒绝一条或多条消息

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException

Parameters:
deliveryTag - 来自接收消息的AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 的标签(tag)
multiple - true: 拒绝所有未被确认的消息,也包括匹配给定传递标签的消息,false: 只拒绝匹配给定传递标签的消息。
requeue - true: 被拒绝的消息会重新进入队列,而不是被丢弃或进入死信队列。如果队列只有一个消费者,消息会被尽可能放入原来的位置;如果队列有多个消费者,消息会被尽可能放入队头的位置。

basicReject

拒绝一条消息

void basicReject(long deliveryTag, boolean requeue) throws IOException

Parameters:
deliveryTag - 来自接收消息的AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 的标签(tag)
requeue - true: 被拒绝的消息会重新进入队列,而不是被丢弃或进入死信队列

basicConsume

启动一个消费者

String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String,Object> arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException

Parameters:
queue - 队列名
autoAck - 消息是否会自动应答
consumerTag - 消费者客户端自动生成,用于建立上下文的标签,区分多个消费者
noLocal - true: 同一个Connection中的生产者和消费者不会互相传递消息。注意RabbitMQ 5.0 版本已不支持这个参数。
exclusive - 是否为排他客户端(消费队列中只允许有当前一个消费者)
arguments - 消费者传递的一些其他参数
deliverCallback - 消费消息的推模式下,消费者接收消息投递的回调函数
cancelCallback - 消费者取消的回调函数(消费者主动调用basicCancel或queueDelete)
shutdownSignalCallback - 通道或连接中断时的回调函数

basicCancel

通过消费者标签,取消一个消费者

void basicCancel(String consumerTag) throws IOException

basicRecover

请求Broker重新发送未应答的消息。

AMQP.Basic.RecoverOk basicRecover(boolean requeue) throws IOException

Parameters:
requeue - true:消息可能传递给不同的消费者

basicQos

限制客户端数据流量

void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException

Parameters:
prefetchSize - 消息最大长度,0为无限制
prefetchCount - 消息最大数量,0为无限制
global - true: 设置的参数应用到整个channel的consumer中,false: 应用到单个consumer

5. 要点记录

5.1 日志

日志文件默认在RABBIT_HOME/var/log目录下,如:

$ ls var/log/rabbitmq/
rabbit@bijiadeMacBook-Pro-sasl.log	rabbit@localhost-sasl.log
rabbit@bijiadeMacBook-Pro.log		rabbit@localhost.log

sasl(system application support library系统应用程序支持库)结尾的Log:sasl作为Erlang-OTP发行版的一部分库的集合,记录Erlang运行的相关日志信息,有助于调试无法启动的RabbitMQ节点。
非sasl结尾的Log:RabbitMQ应用服务日志,记录RabbitMQ运行的相关日志。

5.2 消费模式

RabbitMQ有两种消费消息的模式:
推模式
通过持续订阅的方式来消费消息,spring-amqp@RabbitListener消费消息用的就是这种方式。

使用channel.basicConsume

拉模式
单条地获取消息

使用channel.basicGet

5.3 保证消息不丢失方案

  • 消息生产者开启事务机制或者publisher confirm机制,以确保消息可以可靠传输到RabbitMQ中

  • 消息和队列都要持久化处理,以确保RabbitMQ服务器故障时不会丢失消息

  • 消费者要将autoAck设置为false,然后通过手动确认的方式去确认已经正确消费的消息,以避免在消费端引起不必要的消息丢失

  • 备份交换器

如果没有设置mandatory参数,消息在未路由成功的情况下将会丢失;如果设置了mandatory参数,那么需要添加ReturnListener逻辑,生产者的代码将变复杂。
备份交换器可以在不设置mandatory参数的情况下,将未路由的消息存储在RabbitMQ中(未路由成功的消息通过备份交换器,路由到其绑定队列),再在需要的时候去处理这些消息。

通过声明交换器(channel.exchangeDeclare)时添加 alternate-exchange参数 来实现,也可通过Policy(策略)的方式实现,如果两者同时使用,则前者的优先级更高;
如果备份交换器和mandatory参数一同使用,则前者的优先级更高;

  • 死信队列

消息变死信条件:

  1. 消息被拒绝(reject/nack),并且requeue参数为false。
  2. 消息过期。当消息过期时间设为0时,消息路由到的队列如果没有消费者,会立即变成死信,可以解决immediate参数为false时消息丢失的情况。
  3. 队列达到最大长度。
    当队列中存在死信时,RabbitMQ会自动将这个消息发布到设置好的DLX(死信交换器),进而路由到其绑定队列,即死信队列,可以监听此队列的消息进行相应处理。

在channel.queueDeclare中设置 x-dead-letter-exchange参数 为队列添加DLX,设置 x-dead-letter-routing-key参数 指定路由键(如果没有指定则使用原队列的路由键)

5.4 实现延迟队列

rabbitMQ并没有直接提供延迟队列,但我们可以通过设置过期时间和死信队列来模拟实现延迟队列。

  • 一个经典的例子,订单系统中,用户下单后通常有30分钟的时间进行支付,如果30分钟内没有支付成功,那么这个订单将进行异常处理,可以用延迟队列来处理这些订单。
    用户下的订单通过exchange.normal这个交换器将消息发送存储在queue.normal队列中,且将消息的过期时间设为30分钟,消息过期后进入设置好的死信队列queue.dead。消费者会通过订阅对应的死信队列,每消费到一条消息会判断该订单是否支付,没有支付则做关闭订单处理。

  • 用户希望设定家里的智能设备在各自指定的时间工作,可以将指令发送发送到对应过期时间的延迟队列上,到了过期时间,再将指令推送到对应的智能设备。
    可以通过设定多个延迟队列,过期时间分别为5分钟,10分钟,30分钟,1小时,对应时间的指令发送到对应的队列,消息过期即可实现消费者接收到指定延迟时间的消息。

5.5 Spring-amqp的 Confirm And Return

配置启用生产者的发布确认和发布退回机制
spring.rabbitmq.publisher-confirms = true
spring.rabbitmq.publisher-returns = true

分别实现RabbitTemplate中两个接口

/**
 * 确认消息到达Broker时返回ack=true,否则返回ack=true
 */
public interface ConfirmCallback {
    void confirm(CorrelationData correlationData, boolean ack, String cause);

}
/**
 * 需要把mandatory也设为true才会启用此回调,在未路由到任何队列时会调用
 */
public interface ReturnCallback {
    void returnedMessage(Message message, int replyCode, String replyText,
            String exchange, String routingKey);
}

将以上两个实现类注入RabbitTemplate实例中
rabbitTemplate.setReturnCallback(messageReturnCallback);
rabbitTemplate.setConfirmCallback(messageConfirmCallback);

6. 常用命令

rabbitmq-server
rabbitmq-server -detached 以守护进程方式启动RabbitMQ服务
rabbitmqctl
rabbitmqctl list_queues 列出所有队列
rabbitmqctl purge_queue my_queue 清空某一队列
rabbitmqctl status 查看当前rabbitmq服务的运行信息

你可能感兴趣的:(mq)