【RabbitMQ】之 RabbitMQ 与 SpringBoot 整合

一、SpringBoot 整合 RabbitMQ


1)、引入依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>

2)、添加配置

# 配置 RabbitMQ 服务器 IP 和端口
spring.rabbitmq.host=192.168.56.10
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/

# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

3)、开启 RabbitMQ 功能

  • 1、引入了 amqp 场景启动器后,RabbitAutoConfiguration 就会自动生效
  • 2、SpringBoot 给容器自动配置了:RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate;
  • 3、在自动配置中,SpringBoot 使用的是 @ConfigurationProperties(prefix = "spring.rabbitmq",所以,我们可以给配置文件中配置以 spring.rabbitmq 开头的 rabbitmq 相关信息;
  • 4、在主启动类中使用 @EnableRabbit 注解来开启 RabbitMQ 功能。

二、使用 RabbitMQ


要使用 RabbitMQ,我们需要创建交换机 Exchange、队列 Queue 和绑定 Binding 这三个对象,我们可以使用 AmqpAdmin 进行创建这三个对象。

1)、创建 Exchange、Queue 、Binding

@Autowired
private AmqpAdmin amqpAdmin;

// 创建 Exchange
public void createExchange() {
    // 创建一个 DirectExchange,参数为:交换机的名称、是否持久化、是否自动删除
    Exchange directExchange = new DirectExchange("hello-java-exchange", true, false);
    amqpAdmin.declareExchange(directExchange);
    log.info("Exchange[{}]创建成功:","hello-java-exchange");
}

// 创建 Queue
public void testCreateQueue() {
    // 队列参数为:队列名称、是否持久化、是否排他,是否自动删除
    Queue queue = new Queue("hello-java-queue", true, false, false);
    amqpAdmin.declareQueue(queue);
    log.info("Queue[{}]创建成功:","hello-java-queue");
}

// 创建 Binding
public void createBinding() {
    // destination【目的地】,如果【目的地类型】为队列则填写队列名称
    // destinationType【目的地类型】
    // exchange【交换机】
    // routingKey【路由键】
    // argument【自定义参数】
    // 功能:将交换机 exchange 和 destination 目的地进行绑定,使用 routingKey 作为指定的路由键
    Binding binding = new Binding("hello-java-queue",
                                  Binding.DestinationType.QUEUE,
                                  "hello-java-exchange",
                                  "hello.java",
                                  null);
    amqpAdmin.declareBinding(binding);
    log.info("Binding[{}]创建成功:","hello-java-binding");

}

2)、发送消息

发送消息使用的是 SpringBoot 给我们提供的 RabbitTemplate 来操作消息的发送:

@Autowired
private RabbitTemplate rabbitTemplate;

public void sendMessageTest() {
    // 发送字符串消息
    String msg = "Hello World";
    
    // 发送消息类型为自定义Java对象
    OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
    reasonEntity.setId(1L);
    reasonEntity.setCreateTime(new Date());
    reasonEntity.setName("reason");
    reasonEntity.setStatus(1);
    reasonEntity.setSort(2);   

    // 发送消息
    rabbitTemplate.convertAndSend("hello-java-exchange",     // 交换机
                                  "hello.java",              // 路由键 
                                  reasonEntity,				// 消息类型为对象
                                  new CorrelationData(UUID.randomUUID().toString()));
    log.info("消息发送完成:{}",reasonEntity);
}

发送消息,既可以发送字符串消息也可以发送 Java 对象,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现 Serializable 接口。

如果想要把发送的对象转换为 JSON,我们可以自定消息转换器,通过自定义消息转换器可以把对象消息转换为 JSON。

@Configuration
public class MyRabbitConfig {
    // 自定义消息转换器
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

配置好后,再次调用 rabbitTemplate.convertAndSend() 方法时就会把消息对象转换成 JSON 对象。

3)、接收消息

RabbitMQ 接收消息使用的是 @RabbitListener 注解和 @RabbitHandler 注解,并且要使用 @EnableRabbit 注解开启 RabbitMQ 功能。

/**
  * @RabbitListener 属性:
  * 	queues:声明需要监听的所有队列
  * 	
  * 接收消息函数参数类型:
  * 	1、Message message:原生的消息详细消息:消息头 + 消息体
  * 	2、可以指定消息内容对应的对象,spring 帮我们自动转换:T<发送消息的类型>
  *		3、channel:当前传输数据的通道
  */
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message, 
                           OrderReturnReasonEntity content,
                           Channel channel) {
    // 拿到主体内容
    byte[] body = message.getBody();
    // 拿到的消息头属性信息
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接受到的消息...内容" + message + "===内容:" + content);
}

@RabbitListener 注解可以标注在类和方法上,而 @RabbitHandler 注解只能标注在方法上,所以我们一般使用 @RabbitListener 注解标注在类上表明监听的队列,而 @RabbitHandler 注解标注在方法上就可以进行方法并实现监听同一个队列的不同类型的消息:

@RabbitListener(queues = {"hello-java-queue"})
public class OrderService {
    
    @RabbitHandler 
    public void revieveMessage(Message message,  OrderReturnReasonEntity content) {
    	System.out.println("接受到的消息...内容" + message + "===内容:" + content);
    }
    
    @RabbitHandler 
    public void revieveMessage2(Message message,  OrderEntity content) {
    	System.out.println("接受到的消息...内容" + message + "===内容:" + content);
    }
    
}

三、RabbitMQ 消息确认机制


由于网络的卡顿原因,或者服务宕机了都可能会导致的消息丢失,消息的丢失包括生产者丢失消息、消息列表丢失消息、消费者丢失消息。为了保证我们发送的消息不丢失、可靠抵达我们以前可以使用事务消息来解决,但这种方式性能下降严重,所以就引入了消息确认机制。

由于生产者发送的消息涉及两步:

  • 1、生产者发送消息给 Broker;
  • 2、Broker 收到消息后把消息交给 Exchange,并由 Exchange 根据路由键交给指定的 Queue。

所以生产者端的确认机制涉及两种:

  • 1、生产者到 Broker 的 ConfirmCallback 确认模式;
  • 2、Exchange 到 Queue 到 ReturnCallback 回退模式。

而消费者使用的是 Ack 应答机制。

1)、生产者确认机制

为了实现生产者的确认机制,我需要自定义 RabbitTemplate :

@Configuration
public class MyRabbitConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @PostConstruct  // MyRabbitConfig 对象创建完成以后,执行这个方法
    public void initRabbitTemplate() {

        /**
         * 设置确认回调,只要消息抵达Broker就ack=true
         * 		correlationData:当前消息的唯一关联数据(这个是消息的唯一id)
         * 		ack:消息是否成功收到
         * 		cause:失败的原因
         */
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            System.out.println("确认回调:" + correlationData + ack + cause);
        });


        /**
         * 设置 Return 回调,只要消息没有投递给指定的队列,就触发这个失败回调
         *       message:投递失败的消息详细信息
         *       replyCode:回复的状态码
         *       replyText:回复的文本内容
         *       exchange:当时这个消息发给哪个交换机
         *       routingKey:当时这个消息用哪个路邮键
         */
        rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> {
            System.out.println("Return 回调:" + message + replyCode + replyText + exchange + routingKey);
        });
    }
}

并且需要在配置文件中开启相关设置:

# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true

2)、消费者确认机制

RabbitMQ 消费者默认是自动确认的,只要消息收到,客户端会自动确认,所以,不管你消息有没有处理成功都会把消息从消息队列中删除。

所以为了消费者的消息可靠,我们需要修改消息为手动确认,修改为手动确认需要修改配置文件内容:

# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

设置为手动确认模式后,只要我们没有明确告诉 MQ 消息被签收,即没有 Ack 时,消息就是一直 unacked 状态,即使 Consumer 宕机了,消息也不会丢失,会重新变为 Ready 状态,下一次有新 客户端连接时再返回给客户端。

消费者消息签收确认代码如下:

@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message, 
                           OrderReturnReasonEntity content,
                           Channel channel) {
    System.out.println("受到的消息..." + content);
    
    // deliveryTag:消息投递标签,为一个 channel 内按顺序自增数字
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    
    try {
        // 处理成功则签收:1、签收的 deliveryTag;2、是否批量签收
        channel.basicAck(deliveryTag, false);
    } catch (Exception e) {
        // 处理失败退签:1、退签的 deliveryTag;2、是否批量操作;3、是否把消息返回给服务器
        channel.basicNack(deliveryTag, false, true)
    }
}

你可能感兴趣的:(RabbitMQ,rabbitmq)