Spring Boot学习 RabbitMQ 消息队列 (二) SpringBoot整合 设置消息确认机制,可靠抵达 ConfirmCallbac ReturnCallback
Spring Boot学习 RabbitMQ 消息队列 (一) 消息队列 基本概念 web端操作
3. SpringBoot整合 RabbitMQ
3.1 环境搭建
3.1.1 导入整合依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.1.2 配置文件
spring.rabbitmq.host=192.168.231.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3.1.3 @EnableRabbit
@EnableRabbit //开启基于注解的RabbitMQ模式
@SpringBootApplication
public class AmqpApplication {
public static void main(String[] args) {
SpringApplication.run(AmqpApplication.class, args);
}
}
3.1.4 SpringBoot对rabbitmq的自动配置
/**
* 引入 spring-boot-starter-amqp 场景启动器后,自动配置生效
* 自动配置
* 1、RabbitAutoConfiguration
* 2、自动配置了连接工厂ConnectionFactory;
* 3、RabbitProperties 封装了 RabbitMQ的配置
* 4、 RabbitTemplate :给RabbitMQ发送和接受消息;
* 5、 AmqpAdmin : RabbitMQ系统管理功能组件;
* AmqpAdmin:创建和删除 Queue,Exchange,Binding
* 6、@EnableRabbit + @RabbitListener 监听消息队列的内容
*
*/
3.2 AmqpAdmin系统管理功能组件
作用:操作exchange、queue、bind
@Autowired
AmqpAdmin amqpAdmin;
@Test
public void createExchange(){
//创建不同类型的交换机使用不同的类作为参数
//DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
System.out.println("创建完成");
//创建队列
amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
//创建绑定规则
/**
* new Binding("amqpadmin.queue",
Binding.DestinationType.QUEUE,
"amqpadmin.exchange",
"amqp.haha",
null)
* 目的地(队列名or交换器名)、
目的地类型(绑定到队列or交换器)、
绑定的交换器名称、
路由键、
MAP的参数集合
*/
amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null));
}
3.3 rabbitTemplata对象的使用
作用:给RabbitMQ发送和接受消息
@SpringBootTest
class AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
AmqpAdmin amqpAdmin;
/**
* 1、单播(点对点)
*/
@Test
public void contextLoads() {
//方法一:send Message需要自己构造一个;定义消息体内容和消息头
//rabbitTemplate.send(exchage,routeKey,message);
//方法二:convertAndSend object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq;
//使用这种方法,发送的对象必须实现序列化接口 Serializable
//rabbitTemplate.convertAndSend(exchage,routeKey,object);
Map<String,Object> map = new HashMap<>();
map.put("msg","这是第一个消息");
map.put("data", Arrays.asList("helloworld",123,true));
//对象被默认序列化以后发送出去
/**
* 传入一个交换器和一个路由键
*/
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);
}
/**
* 广播
*/
@Test
public void sendMsg(){
//广播无需指定路由键且需要发送到fanout类型的交换器
rabbitTemplate.convertAndSend("exchange.fanout","",new Book("红楼梦","曹雪芹"));
}
//接受数据
@Test
public void receive(){
Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
System.out.println(o.getClass());
System.out.println(o);
}
}
3.4 配置json序列化方式
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;
@Configuration
public class MyRabbitmqConfig {
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
}
3.5 Rabbit监听注解
3.5.1 @RabbitListener
- Queue可以很多被很多方法同时监听.只要收到消息,队列信息删除,而且只能有一个收到此消息
- 同一个消息,只有一个客户端可以收到
- 只有一个消息完全处理完,方法运行结束后,才会去接受下一个消息
/**参数可以写一下三个类型:
* 1. Message message :原生消息的详细信息 头+体
* 2. T<发送的对象类型>:
* 3. Channel :当前传输数据的信道,用于手动ack
*/
@Service
public class BookService {
//queues 表示监听的队列
@RabbitListener(queues = "book.news")
public void receive(Book book){
System.out.println("收到消息"+book);
}
@RabbitListener(queues = "book.news")
public void receive02(Message message){
System.out.println("收到消息"+message.getBody());
System.out.println("收到消息"+message.getMessageProperties());
}
}
3.5.1 @RabbitHandler
- RabbitHandler 只能标注在方法上,能够根据参数object的类型进行获取,重载区分同队列中的不同消息
- RabbitListener 可以标注在类和方法上
使用场景:当一个队列(a.q)中有同时2种消息(对象)时,可以同时使用RabbitListener 加RabbitHandler 进行区分
@RabbitListener(queues ="a.q")
@Service
public class BookService {
@RabbitHandler
public void receive(Book book){
System.out.println("收到消息"+book);
}
@RabbitHandler
public void receive02(Student student){
System.out.println("收到消息"+student);
}
}
此时,receive02方法只会处理消息内容为Student的消息,receive方法只会处理消息内容为Book的消息.
3.6 设置消息确认机制,可靠抵达
定制RabbitTemplata 实现消息确认机制
- 服务端broker收到消息的回调
- spring.rabbitmq.publisher-confirm-type=correlated
- 设置确认回调 setConfirmCallback
- 消息正确抵达队列进行回调
- spring.rabbitmq.publisher-returns=true
- spring.rabbitmq.template.mandatory=true
- 设置确认回调setReturnCallback
- 消费端确认(保证每个消息被正确消费,此时才可以让broker删除这个信息)
- 默认是自动确认(即方法接受到消息后直接确认,无论方法执行是否出现问题)
- 因此需要开启手动ack模式.只要我们没有明确告诉mq(在监听方法中使用Channel参数),就不会删除队列中的消息
- spring.rabbitmq.listener.simple.acknowledge-mode=manual
- 在监听方法中使用方法进行确认:
- channel.basicAck(deliveryTag,false); 签收,第二个参数表示是否批量确认
- channel.basicNack(deliveryTag,false,true)拒签,第二个参数表示是否批量确认,第三个参数表示是否让该消息重新回队列
配置文件:
spring.rabbitmq.host=192.168.231.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#设置服务器收到消息确认回调 还需要再配置类中定制rabbitTemplate
#spring.rabbitmq.publisher-confirms 过时
spring.rabbitmq.publisher-confirm-type=correlated
#设置服务端正确将消息传到队列的确认回调 还需要再配置类中定制rabbitTemplate
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调spring.rabbitmq.publisher-returns
spring.rabbitmq.template.mandatory=true
#开启消费端手动ack,再业务中进行确认 利用方法参数中的通道Channel
spring.rabbitmq.listener.simple.acknowledge-mode=manual
配置类:
@Configuration
public class MyRabbitmqConfig {
@Resource
RabbitTemplate rabbitTemplate;
//设置序列化类型
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
* 定制rabbitTemplate 实现可靠抵达
*/
//@PostConstruct 表示当构造器MyRabbitmqConfig构造完成后 再调用此方法
@PostConstruct
public void initRabbitTemplate(){
//设置服务器收到消息确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 当前消息的唯一关键数据(id) 发送消息时可以指定UUID
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//当消息成功抵达消息中间件后会调用
//处理具体的业务
System.out.println(correlationData.toString()+ack+cause);
}
});
//设置消息正确抵达队列的回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没有投递给指定的队列,就触发这个失败回调
* @param message 消息的详细内容
* @param replyCode 回复的状态码
* @param replyText 回复的文本内容
* @param change 当时这个消息发给哪个交换机
* @param routingKey 当时这个消息使用哪个路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String change, String routingKey) {
//只要消息没有投递给指定的队列,就触发这个失败回调
//处理具体的业务
System.out.println(message.toString()+replyCode+replyText+change+routingKey);
}
});
}
}
手动ack案例:
@RabbitListener(queues ="a.q")
@Service
public class BookService {
@RabbitHandler
public void receive02(Message message,Student student,Channel channel){
System.out.println("收到消息"+student);
//发送ack确认
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,false);
}
}