RabbitMQ SpringBoot整合
SrpingBoot -RabbitMQ 整合
注解整合,五种常用模式,producer生产者消息发送确认,customer消费者收到消息确认
RabbitMQ 五种模式初体验
一.Producer 生产者搭建
1.pom引入依赖
springboot项目为底层框架添加rabbitmq需要的依赖
1 <!--引入rabbitmq集成的依赖--> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-amqp</artifactId> 5 <version>2.1.7.RELEASE</version> 6 </dependency> 7 <!--JSON依赖--> 8 <dependency> 9 <groupId>com.alibaba</groupId> 10 <artifactId>fastjson</artifactId> 11 <version>1.2.78</version> 12 </dependency>
2.配置application.yml 配置文件
配置producer 开启消息确认,回退,重试策略
spring: application: name: rabbitmq-springboot rabbitmq: host: 192.168.31.177 port: 5672 username: admin password: admin virtual-host: / # 开启回退模式 消息从 exchange 到 queue 投递失败有一个 returnCallback 退回模式。 publisher-returns: true # 开启发布确认 消息从 producer 到 rabbitmq broker有一个 confirmCallback 确认模式。 publisher-confirm-type: correlated listener: simple: acknowledge-mode: auto retry: #开启重试 enabled: true #最大重试次数 max-attempts: 3 #重试间隔 max-interval: 1000ms server: port: 8888
二.Customer消费者搭建
1.pom引入依赖
springboot项目为底层框架添加rabbitmq需要的依赖
<!--引入rabbitmq集成的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!--引入阿里mq依赖--> <dependency> <groupId>com.alibaba.mq-amqp</groupId> <artifactId>mq-amqp-client</artifactId> <version>1.0.3</version> </dependency> <!--引入JSON--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency>
2.配置application.yml 配置文件
spring: #rabbitmq rabbitmq: # host: 192.168.31.189 host: 192.168.31.177 port: 5672 virtual-host: / username: admin password: admin listener: simple:
#手动确认消息 ack acknowledge-mode: manual retry: enabled: true max-attempts: 3 max-interval: 1000ms
三.Producer生产者消息发送确认
发送消息确认:用来确认生产者 producer
将消息发送到 broker
,broker
上的交换机 exchange
再投递给队列 queue
的过程中,消息是否成功投递。
消息从 producer
到 rabbitmq broker
有一个 confirmCallback
确认模式。
消息从 exchange
到 queue
投递失败有一个 returnCallback
退回模式。
我们可以利用这两个Callback
来确保消的100%送达。
import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; /** * @Author: GG * @Date: 2022/2/28 16:21 */ @Component @Slf4j public class MqProducerCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback { @Override public void confirm(CorrelationData correlationData,boolean ack, String cause) { /** * correlationData:对象内部只有一个 id 属性,用来表示当前消息的唯一性。 * ack:消息投递到broker 的状态,true表示成功。 * cause:表示投递失败的原因。 */ if (!ack) { log.error("消息发送异常!"); } else { log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause); } } @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.err.println("ReturnedMessage: " + returnedMessage); System.out.println("退回的消息是:"+new String(returnedMessage.getMessage().getBody())); System.out.println("退回的replyCode是:"+returnedMessage.getReplyCode()); System.out.println("退回的replyText是:"+returnedMessage.getReplyText()); System.out.println("退回的exchange是:"+returnedMessage.getExchange()); System.out.println("退回的routingKey是:"+returnedMessage.getRoutingKey()); } }
四.work模型
work-Producer 生产者
1.在hello world模型上的升级,不需要交换机绑定,可以有多个消费者消费消息默认情况下,消息的分发平均分配到每个不同的消费者上* 可以进行“能者多劳”的设置,即:那个消费者线程处理的快,就可在获得同一队列更多的消息进行处理
配置work模式 config类,项目运行自动创建队列queue
import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * @Author: GG * @Date: 2022/3/7 11:23 */ @Component public class WorkExchangeQueueConfig { public final static String TEST_WORK_QUEUE="TEST_WORK_QUEUE"; /** * work queue 模型 */ @Bean Queue workQueue(){ return new Queue(TEST_WORK_QUEUE); } };
2.world模型消息发送
rabbitTemplate:调用消息确认
rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
rabbitTemplate.convertAndSend(队列,msg消息,MessagePostProcessor,CorrelationData:唯一ID)
/** * @Author: GG * @Date: 2022/2/28 15:52 */ @RestController @AllArgsConstructor public class TestController { private final RabbitTemplate rabbitTemplate; private final MqFanoutCallBack mqFanoutCallBack; @GetMapping("/Work") public String test3(){ for (int i = 0; i < 5; i++) { User user = User.builder() .name("Work" + i) .build(); String msg = JSONObject.toJSONString(user); CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + ""); rabbitTemplate.setConfirmCallback(mqFanoutCallBack); rabbitTemplate.setReturnsCallback(mqFanoutCallBack); rabbitTemplate.convertAndSend(WorkExchangeQueueConfig.TEST_WORK_QUEUE, msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; },correlationData); } return "———————————Work—————————Work—————————————"; } }
work-Producer Customer 消费者
配置另外一个springboot Customer 消费者项目 获取RabbitMq消息
/** * @Author: GG * @Date: 2022/2/22 14:04 */ public class ExchangeQueueConstant { /** * QUEUE_xxx_xxx * Queue 队列 */ public static final String TEST_WORK_QUEUE="TEST_WORK_QUEUE";
}
2. 消息监听
注解监听消息
@RabbitListener(queues = "queue_hf_send_message")
/** * @Author: GG * @Date: 2022/2/16 14:06 */ @Component @Slf4j public class TestRabbitMqReceiver { @RabbitListener(queuesToDeclare = @Queue(value = "TEST_WORK_QUEUE")) @RabbitHandler public void testWork1(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); /** * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。 * void basicAck(long deliveryTag, boolean multiple) * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。 * */ channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入try____________" + user); System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 /** * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。 * deliveryTag:表示消息投递序号。 * requeue:值为 true 消息将重新入队列。 */ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 /** * deliveryTag:表示消息投递序号。 * multiple:是否批量确认。 * requeue:值为 true 消息将重新入队列。 */ channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }
END
WORK 模型运行结果
1.Producer 执行
发送五条消息
ID:correlationData=id_1646727964029
ack状态:ack=true 发送成功
cause:表示投递失败的原因
2.RabbitMQ 消息状态
消息详细属性————点此了解属性含义
5条消息已进入RabbitMQ中
3.Customer消息接收
启动 customer消息消费者项目接收消息
接收到5条消息
此时RabbitMQ中的消息已经被消费
五.Fanout发布订阅模式
Fanout Producer 生产者
1.发送消息
FanoutConfig
声明交换机 声明队列 队列绑定交换机,一个交换机可以有多个队列queue共同消费同一批消息,分享不共有
/** * @Author: GG * @Date: 2022/2/28 17:18 */ @Configuration public class FanoutExchangeQueueConfig { //fanoutQueue public final static String TEST_FANOUT_SEND_MESSAGES_QUEUE = "TEST_FANOUT_SEND_MESSAGES_QUEUE"; //fanoutExchange public final static String TEST_FANOUT_SEND_MESSAGES_EXCHANGE = "TEST_FANOUT_SEND_MESSAGES_EXCHANGE"; /** * 声明fanout 队列 */ @Bean public Queue testQueue() { return new Queue(TEST_FANOUT_SEND_MESSAGES_QUEUE); } /** * 声明fanout交换机 */ @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange(TEST_FANOUT_SEND_MESSAGES_EXCHANGE); } @Bean Binding bindingFanoutExchangeOrderDicQueue(){ return BindingBuilder.bind(testQueue()).to(fanoutExchange()); } }
发送消息
@GetMapping("/fanout") public String test1(){ for (int i = 0; i < 5; i++) { User user = User.builder() .name("王五" + i) .build(); String msg = JSONObject.toJSONString(user); CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + ""); rabbitTemplate.setConfirmCallback(mqFanoutCallBack); rabbitTemplate.setReturnsCallback(mqFanoutCallBack); rabbitTemplate.convertAndSend(FanoutExchangeQueueConfig.TEST_FANOUT_SEND_MESSAGES_EXCHANGE, FanoutExchangeQueueConfig.TEST_FANOUT_SEND_MESSAGES_QUEUE, msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; },correlationData); } return "———————————fanout—————————fanout—————————————"; }
Fanout Customer 消费者 消费消息
发布订阅模型-接收消息
@RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_FANOUT_SEND_MESSAGES_QUEUE"), exchange = @Exchange(value = "TEST_FANOUT_SEND_MESSAGES_EXCHANGE", type = "fanout")//绑定交换机 ) }) public void testFanout(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); /** * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。 * void basicAck(long deliveryTag, boolean multiple) * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。 * */ channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入try____________" + user); System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 /** * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。 * deliveryTag:表示消息投递序号。 * requeue:值为 true 消息将重新入队列。 */ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 /** * deliveryTag:表示消息投递序号。 * multiple:是否批量确认。 * requeue:值为 true 消息将重新入队列。 */ channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }
六.Direct 直连交换机
Direct Producer 生产者
创建交换机,队列。队列绑定到交换机
@Bean("directExchange")
@Qualifier("directExchange") DirectExchange exchange
防止Bean加载时重复
/** * @Author: GG * @Date: 2022/3/7 10:34 */ @Component public class DirectExchangeQueueConfig { public final static String TEST_DIRECT_QUEUE="TEST_DIRECT_QUEUE"; public final static String TEST_DIRECT_EXCHANGE="TEST_DIRECT_EXCHANGE"; @Bean public Queue testDirectQueue(){ return new Queue(TEST_DIRECT_QUEUE); } @Bean("directExchange") DirectExchange directExchange(){ return new DirectExchange(TEST_DIRECT_EXCHANGE); } @Bean Binding bingDirectExchange(Queue testDirectQueue, @Qualifier("directExchange") DirectExchange exchange){ return BindingBuilder.bind(testDirectQueue).to(exchange).with(TEST_DIRECT_QUEUE); } }
发送消息
@GetMapping("/Direct") public String test2(){ for (int i = 0; i < 5; i++) { User user = User.builder() .name("Direct" + i) .build(); String msg = JSONObject.toJSONString(user); CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + ""); rabbitTemplate.setConfirmCallback(mqFanoutCallBack); rabbitTemplate.setReturnsCallback(mqFanoutCallBack); rabbitTemplate.convertAndSend(DirectExchangeQueueConfig.TEST_DIRECT_EXCHANGE,DirectExchangeQueueConfig.TEST_DIRECT_QUEUE, msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; },correlationData); } return "———————————Direct—————————Direct—————————————"; }
Direct Customer 消费者 消费消息
注解
@RabbitListener ---exchange 不用指定type类型 默认为 direct类型
@RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_DIRECT_QUEUE"), exchange = @Exchange(value = "TEST_DIRECT_EXCHANGE")//绑定交换机 ) }) public void testDirect(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); /** * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。 * void basicAck(long deliveryTag, boolean multiple) * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。 * */ channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入try____________" + user); System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 /** * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。 * deliveryTag:表示消息投递序号。 * requeue:值为 true 消息将重新入队列。 */ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 /** * deliveryTag:表示消息投递序号。 * multiple:是否批量确认。 * requeue:值为 true 消息将重新入队列。 */ channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }
七.Routing路由模型
Routing Producer 生产者
路由模式: 一个生产者,多个消费者。需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
RoutingExchangeQueueConfig
创建两个队列 一个交换机,两个Routing key 路由: info error
/** * @Author: GG * @Date: 2022/3/7 13:19 */ @Component public class RoutingExchangeQueueConfig { public final static String TEST_ROUTING_QUEUE_A = "TEST_ROUTING_QUEUE"; public final static String TEST_ROUTING_QUEUE_B = "TEST_ROUTING_QUEUE_B"; public final static String TEST_ROUTING_EXCHANGE = "TEST_ROUTING_EXCHANGE"; /** * 路由模式: 一个生产者,多个消费者。需要设置类型为direct的交换机, * 交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后, * 交换机会根据routing key将消息发送到对应的队列 */ @Bean public Queue routingQueueA() { return new Queue(TEST_ROUTING_QUEUE_A); } @Bean public Queue routingQueueB() { return new Queue(TEST_ROUTING_QUEUE_B); } @Bean("routingExchange") public DirectExchange routingExchange() { // 创建direct类型交换机,表示与此交换机会将消息发送给 routing_key 完全相同的队列 return new DirectExchange(TEST_ROUTING_EXCHANGE); } @Bean public Binding bindExchangeQueueA(Queue routingQueueA,@Qualifier("routingExchange") DirectExchange routingExchange) { // 队列二绑定direct交换机,并设置 routing_key 为 routing_second_queue_routing_key return BindingBuilder.bind(routingQueueA).to(routingExchange).with("info"); } @Bean public Binding bindExchangeQueueB(Queue routingQueueB,@Qualifier("routingExchange") DirectExchange routingExchange) { // 队列二绑定direct交换机,并设置 routing_key 为 routing_second_queue_routing_key return BindingBuilder.bind(routingQueueB).to(routingExchange).with("info,error"); } }
发送消息
@GetMapping("/Routing") public String test4(){ for (int i = 0; i < 2; i++) { User user = User.builder() .name("Routing" + i) .build(); String msg = JSONObject.toJSONString(user); CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + ""); rabbitTemplate.setConfirmCallback(mqFanoutCallBack); rabbitTemplate.setReturnsCallback(mqFanoutCallBack); rabbitTemplate.convertAndSend(RoutingExchangeQueueConfig.TEST_ROUTING_EXCHANGE,"info",msg,message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; },correlationData); } return "———————————Routing—————————Routing—————————————"; }
Routing Customer 消费者 消费消息
同一个交换机下 三个监听者,ABC
路由Ruouting Key 不同消费消息不同。但是同一路由下则为分配共同的消息
/** * rouing 路由模式 同一路由下 接收相同的数据。 */ @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_ROUTING_QUEUE"), exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct key = {"info"} ) }) public void testRouting1(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入BBB____________" + user); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_ROUTING_QUEUE_B"), exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct key = {"info","error"} ) }) public void testWorkRouting2(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入AAAA____________" + user); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_ROUTING_QUEUE_B"), exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct key = {"info","error"} ) }) public void testWorkRouting3(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入CCC____________" + user); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } /** * 路由模式 end * 一个交换机多个队列 监听不同key 下的数据 * 同一个队列多个消费者:数据分配消费 */
END
Routing Producer 生产者消息发送成功
Routing Customer 消费消息
三个消费者 B消费者独自消费两条消息。AC 一人消费一条消息(AC监听同一个路由)
八.Topic模型
Producer 生产者
TopicExchangeQueueConfig
设置一个交换机 三个队列 三个路由 one,two,three
/** * @Author: GG * @Date: 2022/3/7 17:39 */ @Component public class TopicExchangeQueueConfig { public final static String TEST_TOPIC_QUEUE_A ="TEST_TOPIC_QUEUE_A"; public final static String TEST_TOPIC_QUEUE_B ="TEST_TOPIC_QUEUE_B"; public final static String TEST_TOPIC_QUEUE_C ="TEST_TOPIC_QUEUE_C"; public final static String TEST_TOPIC_EXCHANGE="TEST_TOPIC_EXCHANGE"; @Bean public Queue topicQueueA(){ return new Queue(TEST_TOPIC_QUEUE_A); } @Bean public Queue topicQueueB(){ return new Queue(TEST_TOPIC_QUEUE_B); } @Bean public Queue topicQueueC(){ return new Queue(TEST_TOPIC_QUEUE_C); } @Bean("topicExchange") public TopicExchange topicExchange(){ return new TopicExchange(TEST_TOPIC_EXCHANGE); } @Bean public Binding topicExchangeBindingA(Queue topicQueueA,@Qualifier("topicExchange") TopicExchange topicExchange){ return BindingBuilder.bind(topicQueueA).to(topicExchange).with("one"); } @Bean public Binding topicExchangeBindingB(Queue topicQueueB,@Qualifier("topicExchange") TopicExchange topicExchange){ return BindingBuilder.bind(topicQueueB).to(topicExchange).with("two"); } @Bean public Binding topicExchangeBindingC(Queue topicQueueC,@Qualifier("topicExchange") TopicExchange topicExchange){ return BindingBuilder.bind(topicQueueC).to(topicExchange).with("three"); } }
发送消息
指定交换机,不用指定队列
路由为 one
@GetMapping("/Topic") public String test5(){ for (int i = 0; i < 3; i++) { User user = User.builder() .name("Topic" + i) .build(); String msg = JSONObject.toJSONString(user); CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + ""); rabbitTemplate.setConfirmCallback(mqFanoutCallBack); rabbitTemplate.setReturnsCallback(mqFanoutCallBack); rabbitTemplate.convertAndSend(TopicExchangeQueueConfig.TEST_TOPIC_EXCHANGE,"one",msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; },correlationData); } return "———————————Topic—————————Topic—————————————"; }
Topic Customer 消费者 消费消息
Topic exchange 交换机type类型设置为 topic
同一个exchange交换机 三个不同队列,路由为 one、two、one/three
producer 发送消息 customer根据路由接收对应消息
/** * Topic 模式star */ @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_TOPIC_QUEUE_A"), exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct key = {"one"} ) }) public void testWorkTopic1(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入one____________" + user); System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_TOPIC_QUEUE_B"), exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct key = {"two"} ) }) public void testWorkTopic2(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入two____________" + user); System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } @RabbitListener(bindings = { @QueueBinding( value = @Queue(value = "TEST_TOPIC_QUEUE_C"), exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct key = {"three","one"} ) }) public void testWorkTopic3(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:", msg); User user = JSON.parseObject(msg, User.class); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("进入three____________" + user); System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag()); System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered()); //TODO 具体业务 } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收!"); // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { System.out.println("消息即将再次返回队列处理!"); // requeue为是否重新回到队列,true重新入队 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }
END
运行结果
Topic Producer 生产者
发送成功三条消息
Topic Customer 消费者
topic one / two 都接收到三条消息
END
消息确认详解
一.消息发送确认
发送消息确认:用来确认生产者 producer
将消息发送到 broker
,broker
上的交换机 exchange
再投递给队列 queue
的过程中,消息是否成功投递。
消息从 producer
到 rabbitmq broker
有一个 confirmCallback
确认模式。
消息从 exchange
到 queue
投递失败有一个 returnCallback
退回模式。
我们可以利用这两个Callback
来确保消的100%送达。
@Slf4j @Component public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if (!ack) { log.error("消息发送异常!"); } else { log.info("发送者爸爸已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause); } } }
实现接口 ConfirmCallback
,重写其confirm()
方法,方法内有三个参数correlationData
、ack
、cause
。
correlationData
:对象内部只有一个id
属性,用来表示当前消息的唯一性。ack
:消息投递到broker
的状态,true
表示成功。cause
:表示投递失败的原因。
但消息被 broker
接收到只能表示已经到达 MQ服务器,并不能保证消息一定会被投递到目标 queue
里。所以接下来需要用到 returnCallback
。
ReturnCallback 退回模式
如果消息未能投递到目标 queue
里将触发回调 returnCallback
,一旦向 queue
投递消息未成功,这里一般会记录下当前消息的详细投递数据,方便后续做重发或者补偿等操作。
@Slf4j @Component public class ReturnCallbackService implements RabbitTemplate.ReturnCallback { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey); } }
实现接口ReturnCallback
,重写 returnedMessage()
方法,方法有五个参数message
(消息体)、replyCode
(响应code)、replyText
(响应内容)、exchange
(交换机)、routingKey
(队列)。
下边是具体的消息发送,在rabbitTemplate
中设置 Confirm
和 Return
回调,我们通过setDeliveryMode()
对消息做持久化处理,为了后续测试创建一个 CorrelationData
对象,添加一个id
为10000000000
。
@Autowired private RabbitTemplate rabbitTemplate; @Autowired private ConfirmCallbackService confirmCallbackService; @Autowired private ReturnCallbackService returnCallbackService; public void sendMessage(String exchange, String routingKey, Object msg) { /** * 确保消息发送失败后可以重新返回到队列中 * 注意:yml需要配置 publisher-returns: true */ rabbitTemplate.setMandatory(true); /** * 消费者确认收到消息后,手动ack回执回调处理 */ rabbitTemplate.setConfirmCallback(confirmCallbackService); /** * 消息投递到队列失败回调处理 */ rabbitTemplate.setReturnCallback(returnCallbackService); /** * 发送消息 */ rabbitTemplate.convertAndSend(exchange, routingKey, msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; }, new CorrelationData(UUID.randomUUID().toString())); }
ack
@RabbitHandler
channel
message
@Slf4j @Component @RabbitListener(queues = "confirm_test_queue") public class ReceiverMessage1 { @RabbitHandler public void processHandler(String msg, Channel channel, Message message) throws IOException { try { log.info("小富收到消息:{}", msg); //TODO 具体业务
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { log.error("消息已重复处理失败,拒绝再次接收...");
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息 } else { log.error("消息即将再次返回队列处理...");
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }
消费消息有三种回执方法,我们来分析一下每种方法的含义。
1、basicAck
basicAck
:表示成功确认,使用此回执方法后,消息会被rabbitmq broker
删除。
void basicAck(long deliveryTag, boolean multiple)
deliveryTag
:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag
都会增加。手动消息确认模式下,我们可以对指定deliveryTag
的消息进行ack
、nack
、reject
等操作。
multiple
:是否批量确认,值为 true
则会一次性 ack
所有小于当前消息 deliveryTag
的消息。
举个栗子: 假设我先发送三条消息deliveryTag
分别是5、6、7,可它们都没有被确认,当我发第四条消息此时deliveryTag
为8,multiple
设置为 true,会将5、6、7、8的消息全部进行确认。
2、basicNack
basicNack
:表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列。
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
deliveryTag
:表示消息投递序号。
multiple
:是否批量确认。
requeue
:值为 true
消息将重新入队列。
3、basicReject
basicReject
:拒绝消息,与basicNack
区别在于不能进行批量操作,其他用法很相似。
void basicReject(long deliveryTag, boolean requeue)
deliveryTag
:表示消息投递序号。
requeue
:值为 true
消息将重新入队列。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY