默认情况下,消息发送端发送消息给 RabbitMQ 后,RabbitMQ 是不会返回任何信息的。 那么我们怎么知道消息是中途丢失了还是到达了 broker 呢?
RabbitMQ 提供了两种确认消息是否投递成功的方法
- 设置 channel 为 transaction 模式,通过 AMQP 事务机制实现,这也是 AMQP 协议层面提供的解决方案
- 设置 channel 为 confirm 模式,这是 RabbitMQ 提供的解决方案
两种模式不能共存
channel 的 transaction 模式
RabbitMQ 中与事务机制有关的方法有三个
- txSelect:用于将当前 channel 设置成 transaction 模式
- txCommit:用于提交事务
- txRollback:用于回滚事务
在通过 txSelect 开启事务之后,我们便可以发布消息给broker代理服务器了,如果txCommit提交成功了,则消息一定到达了broker了,如果在 txCommit 执行之前 broker 异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过 txRollback 回滚事务了
事务确实能够解决 producer 与 broker 之间消息确认的问题,只有消息成功被 broker 接收,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作,同时进行消息重发,但是使用事务机制的话会降低RabbitMQ的性能。
channel 的 confirm 模式
生产者通过调用 channel 的 confirmSelect 方法将 channel 设置为 confirm 模式. 该模式下,所有在该信道上发布的消息都会被分派一个唯一的ID(从1开始),当消息被投递到所有匹配的队列后,broker 就会发送一个(包含消息的唯一 ID 的)确认给发送端, 如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条nack消息,发送端的 Confirm Listener 会去监听应答
broker回传给发送端的确认消息中 deliver-tag 域包含了确认消息的ID,此外 broker 也可以设置 basic.ack 的 multiple 域,表示到这个ID之前的所有消息都已经得到了处理
confirm模式最大的好处在于他是异步的,生产者可以在等信道返回的同时继续发送下一条消息。
延伸
上面说到“当消息被投递到所有匹配的队列后,broker 就会发送一个(包含消息的唯一 ID 的)确认给发送端”,万一发送确认后, rabbitMq 崩溃了,消息队列中的消息就都没了,这时候发送端还以为消息还在队列中。
为了防止这种情况的发送,rabbitMq 需要对队列和消息进行持久化。
当消息和队列开启持久化之后,确认信息会等到消息写入磁盘之后再发出
超时
RabbitMQ 的响应可能会超时,超时可能是消息没有到达 mq ,也有可能是网络延迟导致的。对于响应超时的消息,通常被认定为投递失败。
对于投递失败的消息,需要进行消息补偿
消息补偿
消息补偿由发送端自己设计,常见的设计方案为
消息落库,消息状态打标
如图
- step1: 发送业务消息前,先将业务数据和消息状态入库,并将消息状态初始化为发送中
- step2: 发送业务消息,设置超时时间,同时异步监听 RabbitMQ 响应
- step3: RabbitMQ 返回响应
- step4: 根据响应结果,更新消息状态,投递成功则将消息状态设置成成功
- step5: 定时任务找出状态为发送中,且时间超时的
- stem6: 重新投递
- stem7: 经过上述步骤多次(通常是3次)循环后,依然失败的,设置消息状态为失败
- stem8: 人工去解决状态为失败的消息
重复投递
消息补偿机制可能会导致重复投递,重复投递可能导致消费端重复消费。但重复投递又无法完全避免,因此消费端需要防止重复消费。
总结
要保证 RabbitMQ 的消息可靠性投递,需要做到以下几点
- 消息发送端开启 channel 的 confirm 模式
- 消息发送端异步接收 RabbitMQ 响应
- RabbitMQ 对队列和消息进行持久化
- 消息发送端建立消息投递失败的补偿机制