学习springBoot(10)RabbitMQ

380 阅读13分钟

前面总结了一些rabbitmq相关的知识点,现在就自己动手试一试,主要从交换机(Exchange)的四种模式入手。

1、首先在pom中加入rabbitmq的依赖

        <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-amqp</artifactId>
       </dependency>

2、application.yml 加入rabbitmq的相关配置

  #rabbitmq 配置
  spring:
      rabbitmq:
        host: 192.168.183.128
        port: 5672
        username: admin
        password: 123456
        #虚拟主机
        virtual-host: /
        listener:
          simple:
            #手动ACK
            acknowledge-mode: manual

3、队列配置

/**
 * 创建消息队列
 * @Configuration 启动容器+@Bean注册Bean,@Bean下管理bean的生命周期
 * @Bean 标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>,作用为:注册bean对象
 */

@Configuration
public class RabbitConfig {

    public static final String DEFAULT_BOOK_QUEUE = "dev.book.register.default.queue";
    public static final String MANUAL_BOOK_QUEUE = "dev.book.register.manual.queue";

    @Bean
    public Queue defaultBookQueue() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(DEFAULT_BOOK_QUEUE, true);
    }

    @Bean
    public Queue manualBookQueue() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(MANUAL_BOOK_QUEUE, true);
    }
}

4、发送者

import org.springframework.amqp.rabbit.core.RabbitTemplate;

    @Autowired
    private  RabbitTemplate rabbitTemplate; //rabbitTemplate是springboot 提供的默认实现
    
    @RequestMapping(value="/rabbit")
    @ResponseBody
    public void defaultMessage() {

        for (int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, "生产者一 " + i);
        }
    }

5、接收者

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * BOOK_QUEUE 消费者
 */
@Component
public class BookHandler {

    private static final Logger logger = LoggerFactory.getLogger(BookHandler.class);

    /**
     * <p>TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用  listenerManualAck()</p>
     * 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
     * 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
     * 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去
     * spring.rabbitmq.listener.simple.acknowledge-mode=manual
     * <p>
     *
     * @param text 监听的内容
     * deliveryTag:该消息的index
     * multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
     */
    @RabbitListener(queues = {RabbitConfig.DEFAULT_BOOK_QUEUE})
    public void listenerAutoAck(String text, Message message, Channel channel) {

        // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            logger.info("[消费者一监听的消息] - [{}]", text);
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // TODO 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    @RabbitListener(queues = {RabbitConfig.DEFAULT_BOOK_QUEUE})
    public void listenerManualAck(String text, Message message, Channel channel) {
        logger.info("[消费者二监听的消息] - [{}]",text);
        try {
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            // TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
        }
    }
}

6、启动项目,发送消息

2018-09-07 11:01:19.490 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 0]
2018-09-07 11:01:19.491 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 2]
2018-09-07 11:01:19.491 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 4]
2018-09-07 11:01:19.492 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 6]
2018-09-07 11:01:19.492 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 8]
2018-09-07 11:01:19.492 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 10]
2018-09-07 11:01:19.493 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 12]
2018-09-07 11:01:19.493 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 14]
2018-09-07 11:01:19.495 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 1]
2018-09-07 11:01:19.496 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 3]
2018-09-07 11:01:19.496 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 5]
2018-09-07 11:01:19.496 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 16]
2018-09-07 11:01:19.497 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 7]
2018-09-07 11:01:19.497 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 9]
2018-09-07 11:01:19.497 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 11]
2018-09-07 11:01:19.498 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 13]
2018-09-07 11:01:19.498 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 18]
2018-09-07 11:01:19.498 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 15]
2018-09-07 11:01:19.498 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 17]
2018-09-07 11:01:19.499 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 19]

我们看到我们一共往队列里放了20 条消息,两个消费者是平均消费的也就是每个消费者10 条,也就是一对多的情况,在多对多的情况下应该也是平均消费的

7、交换机(Exchange)

Topic Exchange:

创建队列,交换机,将队列绑定到交换机上并配置路由模式


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 创建消息队列
 * @Configuration 启动容器+@Bean注册Bean,@Bean下管理bean的生命周期
 * @Bean 标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>,作用为:注册bean对象
 *       *** bean name为方法名 ****
 */

@Configuration
public class TopicRabbitConfig {

    public static final String DEFAULT_BOOK_QUEUE = "dev.book.topic.default.queue";
    public static final String MANUAL_BOOK_QUEUE = "dev.book.topic.manual.queue";

    @Bean
    public Queue queueMessage() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(DEFAULT_BOOK_QUEUE, true);
    }

    @Bean
    public Queue queueMessages() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(MANUAL_BOOK_QUEUE, true);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("exchange");
    }

    @Bean
    Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessage).to(exchange).with("dev.book.topic.default.queue");
    }

    @Bean
    Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessages).to(exchange).with("dev.book.topic.#");
    }
}

创建两个队列消息的消费者,每个队列一个消费者方便测试


import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * BOOK_QUEUE 消费者
 */
@Component
public class BookHandler {

    private static final Logger logger = LoggerFactory.getLogger(BookHandler.class);

    /**
     * <p>TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用  listenerManualAck()</p>
     * 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
     * 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
     * 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去
     * spring.rabbitmq.listener.simple.acknowledge-mode=manual
     * <p>
     *
     * @param text 监听的内容
     * deliveryTag:该消息的index
     * multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
     */
    @RabbitListener(queues = {TopicRabbitConfig.DEFAULT_BOOK_QUEUE})
    public void listenerAutoAck(String text, Message message, Channel channel) {

        // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            logger.info("[消费者一监听的消息] - [{}]", text);
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // TODO 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    @RabbitListener(queues = {TopicRabbitConfig.MANUAL_BOOK_QUEUE})
    public void listenerManualAck(String text, Message message, Channel channel) {
        logger.info("[消费者二监听的消息] - [{}]",text);
        try {
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            // TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
        }
    }
}

接下来进行测试

    //测试1
    @RequestMapping(value="/rabbit")
    @ResponseBody
    public void defaultMessage() {

        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("exchange","dev.book.topic.default.queue", "生产者一 " + i);
        }
    }
    <!--进行访问我们看到如下的结果,两个消费者收到了同样的消息,说明消息发送到了两个队列中,
    因为我们发送消息的“routingKey” 可以匹配到两个队列绑定的“routingKey”中,具体的配置规则可以参考上一篇的介绍-->
    
    2018-09-08 14:27:03.596 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 0]
    2018-09-08 14:27:03.597 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 0]
    2018-09-08 14:27:03.597 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 1]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 2]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 1]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 3]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 2]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 3]
    2018-09-08 14:27:03.598 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 4]
    2018-09-08 14:27:03.599 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 4]
    2018-09-08 14:27:03.599 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 5]
    2018-09-08 14:27:03.599 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 5]
    2018-09-08 14:27:03.601 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 6]
    2018-09-08 14:27:03.602 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 7]
    2018-09-08 14:27:03.603 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 6]
    2018-09-08 14:27:03.604 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 7]
    2018-09-08 14:27:03.605 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 8]
    2018-09-08 14:27:03.605 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 8]
    2018-09-08 14:27:03.606 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 9]
    2018-09-08 14:27:03.606 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 9]

    //测试2
    @RequestMapping(value="/rabbit2")
    @ResponseBody
    public void defaultMessage2() {

        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("exchange","dev.book.topic.manual.queue", "生产者一 " + i);
        }
    }
    
    <!--我们看到只有消费者二接收到了消息,也就是消息只发送到了队列“dev.book.topic.manual.queue” 中,
    因为它只匹配到了队列绑定的“dev.book.topic.#”这个“routing”中-->
    
    2018-09-08 14:34:28.997 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 0]
    2018-09-08 14:34:28.999 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 1]
    2018-09-08 14:34:28.999 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 2]
    2018-09-08 14:34:29.000 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 3]
    2018-09-08 14:34:29.000 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 4]
    2018-09-08 14:34:29.003 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 5]
    2018-09-08 14:34:29.004 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 6]
    2018-09-08 14:34:29.026 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 7]
    2018-09-08 14:34:29.031 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 8]
    2018-09-08 14:34:29.032 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 9]

    

Fanout Exchange(订阅模式):

直接贴一下主要代码和运行结果

创建消息队列

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 创建消息队列
 * @Configuration 启动容器+@Bean注册Bean,@Bean下管理bean的生命周期
 * @Bean 标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>,作用为:注册bean对象
 *       *** bean name为方法名 ****
 */

@Configuration
public class FanoutRabbitConfig {

    public static final String DEFAULT_BOOK_QUEUE = "dev.book.fanout.a.queue";
    public static final String MANUAL_BOOK_QUEUE = "dev.book.fanout.b.queue";

    @Bean
    public Queue queueMessageA() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(DEFAULT_BOOK_QUEUE, true);
    }

    @Bean
    public Queue queueMessageB() {
        // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
        return new Queue(MANUAL_BOOK_QUEUE, true);
    }

    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }

    @Bean
    Binding bindingExchangeMessage(Queue queueMessageA, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueMessageA).to(fanoutExchange);
    }

    @Bean
    Binding bindingExchangeMessages(Queue queueMessageB, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueMessageB).to(fanoutExchange);
    }
}

消费者

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * BOOK_QUEUE 消费者
 */
@Component
public class BookHandler {

    private static final Logger logger = LoggerFactory.getLogger(BookHandler.class);

    /**
     * <p>TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用  listenerManualAck()</p>
     * 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
     * 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
     * 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去
     * spring.rabbitmq.listener.simple.acknowledge-mode=manual
     * <p>
     *
     * @param text 监听的内容
     * deliveryTag:该消息的index
     * multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
     */
    @RabbitListener(queues = {FanoutRabbitConfig.DEFAULT_BOOK_QUEUE})
    public void listenerAutoAck(String text, Message message, Channel channel) {

        // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            logger.info("[消费者一监听的消息] - [{}]", text);
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // TODO 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    @RabbitListener(queues = {FanoutRabbitConfig.MANUAL_BOOK_QUEUE})
    public void listenerManualAck(String text, Message message, Channel channel) {
        logger.info("[消费者二监听的消息] - [{}]",text);
        try {
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            // TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
        }
    }
}

测试

    @RequestMapping(value="/rabbit3")
    @ResponseBody
    public void fanoutMessage3() {

        for (int i = 0; i < 5; i++) {
            rabbitTemplate.convertAndSend("fanoutExchange","", "生产者一 " + i);
        }
    }
    
   <!-- 运行结果,两个队列都发送了消息-->
   
    2018-09-08 15:06:37.446 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 0]
    2018-09-08 15:06:37.448 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 1]
    2018-09-08 15:06:37.449 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 2]
    2018-09-08 15:06:37.451 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 0]
    2018-09-08 15:06:37.460 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 3]
    2018-09-08 15:06:37.461 INFO  com.test.rabbitmq.BookHandler 39 listenerAutoAck - [消费者一监听的消息] - [生产者一 4]
    2018-09-08 15:06:37.461 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 1]
    2018-09-08 15:06:37.462 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 2]
    2018-09-08 15:06:37.462 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 3]
    2018-09-08 15:06:37.462 INFO  com.test.rabbitmq.BookHandler 54 listenerManualAck - [消费者二监听的消息] - [生产者一 4]