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

708 阅读4分钟

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

关于RabbitMQ可靠性投递现在主要有两种方案,对于下面介绍的“消息的延迟投递,做二次确认,回调检查”这种方案在网上的实际例子比较少,只是简单描述一下过程,给的流程图也大都一样,比较难以理解。下面是我结合网上资料给出自己的理解,如果有错误的地方,还请留言指出👼

在上一篇文章中RabbitMQ生产端可靠性投递(一),我们采用定时任务+消息落库、打标的方式保证生产端可靠性投递,其中需要执行两次数据入库,在高并发场景下会存在性能瓶颈。因此,在这篇文章中将介绍另外一种可靠性投递方案:消息的延迟投递,做二次确认,回调检查,实现过程如下图:

image.png

  • 过程描述:
  1. 首先生产者先完成业务数据的入库,然后进行第一次消息的发送。🚨一定要等业务数据入库成功后再发送消息。

  2. 完成第一次消息发送后,生产者再一次发送一条消息到指定过期时间的队列中,比如设置5分钟后消息过期,过期后消息会被重新路由到死信队列,监听者2会监听该死信队列,来获取第二次投递的消息。

  3. 消费者会监听指定的队列即队列1,消费者接收第一次发送的消息

  4. 如果消费者接收到消息并完成消费后,回送响应,这里的回送响应不是一般的返回确认ack,而是消费者会发送一条Comfirm消息到指定的队列,即队列2。

  5. 监听者1会监听队列2,获取消费者返回的Confirm。

  6. 监听者1接收到Comfrim后,会将消息保存到MsgDB消息数据库中,表示该消息已经被正常投递且被消费者消费了。

  7. 第一条消息发送5分钟后,第二次发送的消息会被重新路由到死信队列,即队列3中,监听者2监听队列3,并获取第二次发送的消息,然后根据该消息从消息数据库MsgDB中判断该消息是否在数据库中已经存在。不存在,则说明之前的消息没有被消费成功,则需要通知生产者重新发送消息。如果数据库中存在该消息,则说明第一次发送的消息已经被消费者成功消费,则无需做任何操作,消息投递成功。

Demo

方案中的延迟投递我们以TTL+DXL来实现。

image.png

  1. 声明队列、交换机、死信队列,死信交换机,并将他们按上图的路由键进行绑定。
@Bean
public DirectExchange directExchange(){
    return new DirectExchange("direct-Ex",true,false);
}

@Bean
public Queue queue1(){
    return new Queue("queue1",true,false,false);
}

@Bean
public Queue queue2(){
    return new Queue("queue2",true,false,false);
}

@Bean
public Binding bindQueue1(){
    return BindingBuilder.bind(queue1()).to(directExchange()).with("m1");
}

@Bean
public Binding bindQueue2(){
    return BindingBuilder.bind(queue2()).to(directExchange()).with("Lis2");
}

//定义一个死信交换机
@Bean
public DirectExchange deadLetterExchange(){
    return new DirectExchange("deadLetterExchange",true,false);
}

//定义指定消息过期时间的ttl队列
@Bean
public Queue ttlQueue(){
    HashMap<String, Object> args = new HashMap<>();
    args.put("x-message-ttl",60000);
    args.put("x-dead-letter-exchange","deadLetterExchange");
    args.put("x-dead-letter-routing-key","dead");
    return new Queue("ttlQueue",true,false,false,args);
}

//将ttl队列绑定到交换机direct-Ex上面,binding key 为ttl
@Bean
public Binding bindTtlQueue(){
    return BindingBuilder.bind(ttlQueue()).to(directExchange()).with("ttl");
}

//定义队列3,即死信队列
@Bean
public Queue queue3(){
    return new Queue("queue3",true,false,false);
}

//将队列3绑定到死信交换机上,binding key为dead
@Bean
public Binding bindDeadLetterQueue(){
    return BindingBuilder.bind(queue3()).to(deadLetterExchange()).with("dead");
}
  1. 定义监听者1和监听者2,分别监听队列queue2、queue3

image.png

image.png

  1. 定义消费者,监听队列1,如果成功接收到消息并消费,则发送一条Comfirm消息到MQ中。

image.png

4.Controller接收请求,进行业务处理和消息发送

image.png

  1. Postman测试

image.png

  1. 运行结果

image.png

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

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