RabbitMQ生产端可靠性投递(一)

1,079 阅读7分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

往期推荐

生产端可靠性投递

可靠性投递是指消息100%投递成功,在RabbitMQ中会出现各种各样的场景使我们的消息无法正常被投递,比如生产者发送的消息无法到达Broker、消息到达Broker后交换机无法将消息路由到指定队列、消息发送到队列后或者消息到达Broker后RabbitMQ宕机等等,这也是我们使用RabbitMQ存在的一些弊端,因此我们需要有一个可靠性投递方案来解决这类问题。要确保消息的可靠性投递需要满足以下几个条件:

  1. 确保消息到达Broker。解决方案(开启Comfirm消息确认模式,建立消息投递失败补偿机制)

  2. 确保消息被路由到指定的队列。解决方案(开启ReturnCallback失败回调,配置mandatory为true和消息失败回调,当路由不到指定队列时返回消息给生产者而不丢弃消息)或者使用备份的交换机,将路由不到指定队列的消息发送到备份交换机上。

  3. 确保消息在队列正确存储,当RabbitMQ宕机时不至于消息丢失。解决方案(定义队列时将队列设置为持久化且不自动删除

消息落库,对消息状态打标

其主要过程如下图:

image.png

  1. 首先,在消息发送到RabbitMQ之前,将消息的状态设置为“投递中”,并将消息保存到数据库中,同时如果业务中也有数据需要落库,则消息落库和业务数据落库需要事务的支持,确保他们要么全部成功,要么都失败。

  2. 消息落库后就可以将消息投递到RabbitMQ中。

  3. 监听消息回调,根据消息回调返回的ack确定下一步的操作

  4. 如果监听回调中返回的ack为true表示消息成功投递到了RabbitMQ中,则将保存到数据库中的消息的状态修改为"已投递"

  5. 补偿机制,我们需要启用一个定时任务,每10秒执行一次数据库查找任务,从消息数据库中查找出消息状态为“投递中”重试时间小于当前时间的消息,将这些消息重新发送,然后修改这些消息的重试次数+1下一次重试时间更新时间并持久化到数据库。

  6. 如果定时任务从消息数据库中查找的消息状态为“投递中”“重试次数大于3”,则将这些消息状态设置为“投递失败”,不再进行重新投递。

小案例实现

以下案例基于SpringBoot+MyBatis plus搭建

  1. 首先准备一个数据库表,来存放消息相关信息 image.png
  • 对应的POJO类

image.png 2. 准备队列和交换机,这里使用Direct类型的交换机,生产者发送消息时指定routing key将消息投递到指定队列。

image.png

  1. 消息确认回调ComfirmCallback

image.png

  1. 消息失败回调,为了确保消息可靠投递到队列,通过消息失败回调,当消息路由不到指定队列时,触发ReturnCallback回调,RabbitMQ将消息返回给生产者,我们在会回调中根据返回的消息进行消息的重发。这里也可以通过备用交换机的方式,将那些路由不到指定队列的消息投递到备份交换机,由备份交换机将消息投递到队列,一般情况下备份交换机为Fanout类型的,因此与之绑定的所有队列都可以接收到它发出的消息。(下面的消息失败回调是在RabbitConfig配置类中设置的,如果通过自定义的消息失败回调类注入,那么在自定义失败回调类中注入RabbitTemplate就会出现循环依赖,后面看看这种问题怎么解决)

image.png

  1. 配置文件中开启手动确认、消息确认回调以及mandatory为true,当消息路由不到指定队列时返回给生产者(开启手动确认后,如果消费者接收到消息后还没有返回ack就宕机了消息也不会丢失,RabbitMQ只有接收到返回ack后,消息才会从队列中被删除)

image.png

  1. 配置定时任务,每10秒从消息数据库中找出符合重发条件的消息进行重发。 image.png

  2. 消费者监听队列,消费消息

image.png

  1. Controller接收发送消息的请求 image.png

下面使用Postman测试

  • 首先,我们确保在发送消息时指定的交换机和队列都正确的情况下进行测试。在postman中发送一个post请求,指定消息内容为“不喝奶茶的Programmer”,在Controller会先将消息状态设置为0表示“投递中”然后存入到数据库中,

image.png 发送消息后,可以从控制台看到消息已经成被发送到了Broker,且投递到指定的队列,消费者也对该消息进行消费并返回确认ack。

image.png 同时,消息确认回调接收到的ack为true,则会将存入到消息库中的消息状态修改为1表示"已投递"

image.png

  • 如果我们投递消息时指定的交换机不存在,那么会投递失败,下面我们测试在这种情况下消息的投递。
  1. 我们将投递消息时指定的交换机修改为不存在的

image.png 2. 同样在postman中发送一个请求,指定消息内容为"不喝奶茶的Programmer222"

image.png 3. 控制台输出如下,因为不存在对应的交换机,所以消息确认回调返回的ack为false,消息投递失败

image.png

  1. 定时任务会每隔10秒从消息库中找出符合重发要求的消息进行重发,因为我们在定时任务中发送消息时指定的交换机是正确的,所以在重发时消息能够被正确投递到Broker,所以在经过定时任务的重发后,消息状态被修改为1表示"已投递",且重试次数为1,消费者也从对应的队列中获取消息进行消费。

image.png

image.png

  • 同样地如果我们将Controller中发送消息时指定的交换机和定时任务重发消息时指定的交换机都设置为不存在的交换机,那么消息就永远都不会投递成功,定时任务经过3次重发之后,会将消息状态设置为2表示"投递失败",如下

image.png

  • 下面我们将controller中发送消息时指定的routing key设置为一个不存在的路由键,但重发的是指定正确的交换机和路由键,测试当消息路由不到指定队列下的情况。

image.png

  • 从以下运行结果可以看出,第一次发送时由于路由键不存在,交换机无法将消息路由到指定队列,因此会触发消息失败回调returnCallback,然后在回调中进行重新发送,重新发送时指定的路由键和交换机都是正确,因此消息能够被正确投递到指定队列,消费者也能从对列中获取消息进行消费。

image.png

总结

要实现生产端消息的可靠性投递需要做到以下几点:

  1. 开启Comfirm消息确认模式publisher-confirm-type: correlated,生产端可以接收到RabbitMQ的确认应答

  2. 采用持久化的队列和交换机,即使RabbitMQ宕机了,消息也不会丢失。

  3. 生产者建立消息投递失败的补偿机制(落库、打标、定时任务)

  4. 设置mandatory参数为true,当消息无法被路由到指定队列时,将消息返回给生产者,生产者实现回调函数ReturnCallback处理被服务端返回的消息。

🏁以上就是对RabbitMQ生产端可靠性投递方案一的介绍,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞👍吧😋😻😍

默认标题_动态分割线_2021-07-15-0.gif