SpringBoot整合RabbitMQ实现消息确认机制
注:这里记得给先我们用户授权virtualhost,由于我使用的virtualhost为'/',用户为admin,所以使用以下命令进行授权
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
生产者
1.导入相关依赖包
在创建SpringBoot项目的时候其实就可以选择相关依赖包进行导入
<!--springboot整合rabbitmq依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!-- lombok插件 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
2.修改application.yml文件
server:
port: 8081
spring:
application:
name: rabbitmq-provider
rabbitmq:
#rabbitmq集群配置方式如下
#addresses: 192.168.79.128:5672,192.168.79.135:5672
host: 192.168.111.129
port: 5672
username: admin
password: 123
virtual-host: /
3.创建RabbitMQ配置文件
在SpringBoot中我们通过配置文件来实现交换机、队列的创建与绑定
import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitExchangeAndQueueConfig { /** * 创建交换机 * durable:是否持久化 * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ //创建fanout交换机 @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange("fanout-exchange",true,false); } //创建direct交换机 @Bean public DirectExchange directExchange(){ return new DirectExchange("direct-exchange",true,false); } //创建topic交换机 @Bean TopicExchange topicExchange(){ return new TopicExchange("topic-exchange",true,false); } /** * 创建队列 * durable:是否持久化 * exclusive:默认false,只能在当前创建连接时使用,连接关闭后队列自动删除,该优先级高于durable * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ @Bean public Queue emailQueue(){ return new Queue("emailQueue",true); } @Bean public Queue smsQueue(){ return new Queue("smsQueue",true); } /** * 绑定交换机和队列 */ //email队列绑定fanout交换机 @Bean public Binding emailFanoutBinding(){ return BindingBuilder.bind(emailQueue()).to(fanoutExchange()); } //sms队列绑定fanout交换机 @Bean public Binding smsFanoutBinding(){ return BindingBuilder.bind(smsQueue()).to(fanoutExchange()); } //sms队列绑定direct交换机 @Bean public Binding smsDirectBinding(){ return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms"); } //email队列绑定topic交换机 @Bean public Binding emailTopicBinding(){ return BindingBuilder.bind(emailQueue()).to(topicExchange()).with("email.#"); } //sms队列绑定topic交换机 @Bean public Binding smsTopicBinding(){ return BindingBuilder.bind(smsQueue()).to(topicExchange()).with("sms.#"); } }
4.测试
import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class RabbitmqProviderApplicationTests { @Autowired RabbitTemplate rabbitTemplate; @Test void testFanout(){ String msg = "fanout"; rabbitTemplate.convertAndSend("fanout-exchange",null,msg); } @Test void testDirect() { String msg = "direct"; rabbitTemplate.convertAndSend("direct-exchange","sms",msg); } @Test void testTopic(){ String msg = "email-topic"; String msg1 = "sms-topic"; rabbitTemplate.convertAndSend("topic-exchange","email.chen",msg); rabbitTemplate.convertAndSend("topic-exchange","sms.chen",msg1); } }
消费者
1.导入相关依赖包
在创建SpringBoot项目的时候其实就可以选择相关依赖包进行导入
<!--springboot整合rabbitmq依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!-- lombok插件 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
2.修改application.yml文件
server:
port: 8082
spring:
application:
name: rabbitmq-consumer
rabbitmq:
host: 192.168.111.129
port: 5672
username: admin
password: 123
virtual-host: /
3.创建Service接收消息
import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; @Service public class DirectReceiver { @RabbitHandler @RabbitListener(queues = "emailQueue") //监听的队列名称 public void emailProcess(String testMessage){ System.out.println("emailQueue: " + testMessage.toString()); } @RabbitHandler @RabbitListener(queues = "smsQueue") //监听的队列名称 public void smsProcess(String testMessage){ System.out.println("smsQueue: " + testMessage.toString()); } }
消息确认机制
基于上方例子做以下操作
生产者
1.修改application.yml文件
新增最后两项
server: port: 8081 spring: application: name: rabbitmq-provider rabbitmq: host: 192.168.111.129 port: 5672 username: admin password: 123 virtual-host: / #确保消息已发送到交换机 publisher-confirm-type: correlated #确保消息没有指定到对应queue时,将消息返回,而不是丢弃 publisher-returns: true
2.创建一个新的RabbitMQ配置文件
对RabbitTemplate的配置
import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitTemplateConfig { @Bean public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){ RabbitTemplate rabbitTemplate = new RabbitTemplate(); rabbitTemplate.setConnectionFactory(connectionFactory); //指定消息在没有被队列接收时是否强行退回还是直接丢弃 //该项通常与yml配置文件中的publisher-returns配合一起使用,若不配置该项,setReutrnCallback将不会有消息返回 rabbitTemplate.setMandatory(true); //消息发送成功的回调 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { System.out.println("ConfirmCallback: "+"相关数据:"+correlationData); System.out.println("ConfirmCallback: "+"确认情况:"+ack); System.out.println("ConfirmCallback: "+"原因:"+cause); }); //发生异常时的消息返回提醒 rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { System.out.println("ReturnCallback: "+"消息:"+message); System.out.println("ReturnCallback: "+"回应码:"+replyCode); System.out.println("ReturnCallback: "+"回应信息:"+replyText); System.out.println("ReturnCallback: "+"交换机:"+exchange); System.out.println("ReturnCallback: "+"路由键:"+routingKey); }); return rabbitTemplate; } }
可以看到上面写了两个回调函数,一个是ConfirmCallback,一个是ReturnCallback,那么这两种回调函数什么时候触发呢。
推送消息存在四种情况:
- 消息推送到Server,但是找不到交换机
- 消息推送到Server,找到交换机了,但是找不到队列
- 消息推送到Server,交换机和队列都没找到
- 消息推送成功
经过测试,情况1,3,4都是触发ConfirmCallbak,情况2是会同时触发ConfirmCallback和ReturnCallback
消费者
1.修改application.yml文件
新增最后项
server:
port: 8082
spring:
application:
name: rabbitmq-consumer
rabbitmq:
host: 192.168.111.129
port: 5672
username: admin
password: 123
virtual-host: /
listener:
simple:
# NONE 自动确认 RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。
# 所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
# 一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。
# MANUAL 手动确认 需要人为地获取到channel之后调用方法向server发送ack(或消费失败时的nack)信息。
# AUTO 由spring-rabbit依据消息处理逻辑是否抛出异常自动发送ack(无异常)或nack(异常)到server端。
acknowledge-mode: manual #手动ACK
2.修改Service接收信息项
import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; import java.io.IOException; @Service public class DirectReceiver { @RabbitHandler @RabbitListener(queues = "emailQueue") //监听的队列名称 public void emailProcess(Channel channel, Message testMessage) throws IOException { try{ System.out.println(new String(testMessage.getBody(),"UTF-8")); //成功接收到消息后,确认消息,要不确认消息将会进入unacked,false只确认当前一个消息收到,true确认所有consumer获得的消息 channel.basicAck(testMessage.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { //若是消息没有成功接收,第二个参数设置为true的话,代表重新放回队列中,false则为丢弃,在此也可以做成放置死信队列的操作 channel.basicReject(testMessage.getMessageProperties().getDeliveryTag(),false); } } @RabbitHandler @RabbitListener(queues = "smsQueue") //监听的队列名称 public void smsProcess(Channel channel, Message testMessage) throws IOException { try{ System.out.println(new String(testMessage.getBody(),"UTF-8")); channel.basicAck(testMessage.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { //第二个参数,true代表会重新放回队列 channel.basicReject(testMessage.getMessageProperties().getDeliveryTag(),false); } } }