RabbitMq确认消费,与重复消费避免使用冥等
1 | 生产者 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | package com.wangbiao.consumer.config; import org.apache.logging.log4j.message.SimpleMessageFactory; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; /** * TODO * * @author wangbiao * @Title TODO * @module TODO * @description direct 发布订阅 这个模式多了个路由KEY 这里单独配置了RabbitTemplate * @date 2021/3/24 22:32 */ @Configuration public class DirectRabbitConfig { @Bean public RabbitTemplate rabbitTemplate(CachingConnectionFactory factory) { // log.info("caching factory: {}", factory.getChannelCacheSize()); RabbitTemplate rabbitTemplate = new RabbitTemplate(factory); /** * 当mandatory标志位设置为true时 * 如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息 * 那么broker会调用basic.return方法将消息返还给生产者 * 当mandatory设置为false时,出现上述情况broker会直接将消息丢弃 */ rabbitTemplate.setMandatory( true ); // rabbitTemplate.setReturnsCallback(rabbitReturnCallback); //使用单独的发送连接,避免生产者由于各种原因阻塞而导致消费者同样阻塞 rabbitTemplate.setUsePublisherConnection( true ); //设置消息为json // rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter()); // 如果消息没有到exchange,则confirm回调,ack=false; 如果消息到达exchange,则confirm回调,ack=true rabbitTemplate.setConfirmCallback( new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if (ack){ System.out.println( "消息发送成功:correlationData({}),ack({}),cause({})" +correlationData+ ":" +ack+ ":" +cause); } else { System.out.println( "消息发送成功:correlationData({}),ack({}),cause({})" +correlationData+ ":" +ack+ ":" +cause); } } }); //如果exchange到queue成功,则不回调return;如果exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了) rabbitTemplate.setReturnsCallback( new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println( "消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}" +returnedMessage); } }); return rabbitTemplate; } //序列化方式 // @Bean // public MessageConverter jackson2JsonMessageConverter(){ // Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); // return jackson2JsonMessageConverter; // } //交换机注册与声明 这里介绍Direct模式交换机 @Bean public DirectExchange DirectExchange() { return new DirectExchange( "direct_order_exchange" , true , false ); } //声明队列 sms.Direct.queue, email.Direct.queue, duanxin.Direct.queue, //完成绑定(队列与短信绑定关系) @Bean public Queue directemail() { return new Queue( "email.direct.queue" , true ); } @Bean public Binding emailBind() { return BindingBuilder.bind(directemail()).to(DirectExchange()).with( "email" ); } @Bean public FanoutExchange deadExchange() { return new FanoutExchange( "dead_direct_exchange" , true , false ); } @Bean public Queue dead() { return new Queue( "dead.direct.queue" , true ); } @Bean public Binding deadBinding() { return BindingBuilder.bind(dead()).to(deadExchange()); } } |
生产者业务代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package com.wangbiao.consumer.service; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.*; import java.util.Calendar; import java.util.Date; import java.util.UUID; /** * TODO * * @author wangbiao * @Title TODO * @module TODO * @description TODO * @date 2021/3/24 22:29 */ @Service public class OrderService { @Autowired private RabbitTemplate rabbitTemplate; public void makeOrder() { //1根据商品id查询库存 //2保存订单 String orderId= UUID.randomUUID().toString(); System.out.println( "订单ok" ); String exchangeName= "direct_order_exchange" ; //direct_order_exchange String tpoicKey= "email" ; rabbitTemplate.convertAndSend(exchangeName,tpoicKey,orderId); //确认下消费的信号 // rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { // @Override // public void confirm(CorrelationData correlationData, boolean ack, String cause) { // if(!ack){ // System.out.println("发送消息失败:"+cause); // throw new RuntimeException("发送异常:"+cause); // } // } // }); } } |
1 | 消费者: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | package com.wangbiao.mq; import com.alibaba.fastjson.JSON; import com.rabbitmq.client.Channel; import com.wangbiao.Kung; import com.wangbiao.entity.Wmxg; import com.wangbiao.service.impl.DispatcherService; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.io.IOException; /** * *@死信队列的消费 接收那些重复发送的消息还没有被消费的消息 (业务上一般安排人工客服) * */ @Component public class DeadMqConsumer { /** * 服务对象 */ private int count= 1 ; @Autowired private DispatcherService dispatcherService; @RabbitHandler // @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "order.fanout.exchange", // durable = "true",autoDelete = "false"), // exchange = @Exchange(value = "order_fanout_exchange",type = ExchangeTypes.FANOUT))) @RabbitListener (queues = "dead.direct.queue" ) public void messageconsumer(String mesg, Channel channel, CorrelationData correlationData, @Header (AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { try { //1收到消息是 System.out.println( "死信队列收到的消息是:" + mesg + ",count=" + count); //2获取订单的信息 Kung kung = JSON.parseObject(mesg, Kung. class ); //3获取ID String orderId = kung.getOrderId(); String userId = kung.getUserId(); //4保存运单 Wmxg wmxg = new Wmxg(); wmxg.setOrderId(orderId); wmxg.setUserId(userId); // dispatcherService.insert(wmxg); //对当前消息进行应答 //用catch进行捕捉 channel.basicAck(tag, false ); //只对当前收到的消息进行确认 true对消息进行批量确认 } catch (Exception e) { //如果出现异常的情况,根据实际情况去进行重发 //重发一次后,丢失还是日记,库存根据自己的业务场景去定 //参数1:消息的tag // 参数2:false 多条处理 // 参数3:requeue重发 fasle 不会重发,会把消息打入死信队列 true会进入死循环的重发,建议true的情况下,不使用try catch 否则造成循环 System.out.println( "死信队列 发短信,然后移除" ); System.out.println( "死信队列仍然没有解决就人工客服" ); channel.basicNack(tag, false , false ); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | package com.wangbiao.mq; import com.alibaba.fastjson.JSON; import com.rabbitmq.client.Channel; import com.rabbitmq.client.impl.AMQImpl; import com.wangbiao.Kung; import com.wangbiao.entity.Wmxg; import com.wangbiao.service.impl.DispatcherService; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.amqp.RabbitProperties; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.io.IOException; /** * *@description 订单的消费者 *坚挺的队列名称: order.fanout.exchange * */ @Component public class OrderMqConsumer { /** * 服务对象 */ private int count= 1 ; @Autowired private DispatcherService dispatcherService; @RabbitHandler // @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "order.fanout.exchange", // durable = "true",autoDelete = "false"), // exchange = @Exchange(value = "order_fanout_exchange",type = ExchangeTypes.FANOUT))) @RabbitListener (queues = "email.direct.queue" ) public void messageconsumer(String mesg, Channel channel, CorrelationData correlationData, @Header (AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { try { //1收到消息是 System.out.println( "收到的消息是:" + mesg + ",count=" + count); //2获取订单的信息 Kung kung = JSON.parseObject(mesg, Kung. class ); //3获取ID String orderId = kung.getOrderId(); String userId = kung.getUserId(); //4保存运单 Wmxg wmxg = new Wmxg(); wmxg.setOrderId(orderId); wmxg.setUserId(userId); //幂等性的问题,存在则更新,不存在则插入 使用分布式锁也可以解决 // dispatcherService.insert(wmxg); // System.out.println(1 / 0); //对当前消息进行应答 //用catch进行捕捉 channel.basicAck(tag, false ); //只对当前收到的消息进行确认 true对消息进行批量确认 } catch (Exception e) { //如果出现异常的情况,根据实际情况去进行重发 //重发一次后,丢失还是日记,库存根据自己的业务场景去定 //参数1:消息的tag // 参数2:false 多条处理 // 参数3:requeue重发 fasle 不会重发,会把消息打入死信队列 true会进入死循环的重发,建议true的情况下,不使用try catch 否则造成循环 channel.basicNack(tag, false , false ); } } } |
1 | 查看下管理面板 |
队列
交换机
交换机通道的解释
路由的通道与解释
可以看出有一条消息未被消费
1 | 下面演示程序调用示例: |
此时对应的面板关系是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 幂等性问题: 消费者在消费完一条消息后,向 RabbitMQ 发送一个 ack 确认, 此时由于网络断开或者其他原因导致 RabbitMQ 并没有收到这个 ack, 那么此时 RabbitMQ 并不会将该条消息删除, 当重新建立起连接后,消费者还是会再次收到该条消息,这就造成了消息的重复消费。 同时,由于类似的原因,消息在发送的时候, 同一条消息也可能会发送两次 采用 Redis,在消费者消费消息之前,现将消息的 id 放到 Redis 中,存储方式如下: id- 0 (正在执行业务) id- 1 (执行业务成功) 如果 ack 失败,在 RabbitMQ 将消息交给其他的消费者时,先执行 setnx,如果 key 已经存在(说明之前有人消费过该消息),<br>获取他的值,如果是 0 ,当前消费者就什么都不做,如果是 1 ,直接 ack。 极端情况:第一个消费者在执行业务时,出现了死锁,在 setnx 的基础上,再给 key 设置一个生存时间。<br>生产者,发送消息时,指定 messageId。 |
本文来自博客园,作者:余生请多指教ANT,转载请注明原文链接:https://www.cnblogs.com/wangbiaohistory/p/15259484.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了