Spring Boot项目使用RabbitMQ

都说好记性不如烂笔头,每天写一点,从量变到质变的开始!废话不多说,以下所有内容均来自本人实际操作:

周末花了点时间,重温了一下RabbitMQ,在此记录一下实际项目中(实时消息/延迟消息/死信消息)的基本使用,如果想了解更多可以查看官网教程,本文为Spring Boot项目,因此参考了Spring Boot官方文档.

1.准备一个RabbitMQ账号(用户名=test/密码=test/virtual-host=/test,并授予管理员权限)

RabbitMQ的安装(略) 

2.项目pom文件中导入依赖

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

Spring Boot版本为2.4.1

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

3.项目yml配置文件中配置rabbitmq连接信息

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: /test
    username: test
    password: test
    #确认机制,唯一
    publisher-confirm-type: correlated
#监听器默认使用simple,可以访问https://docs.spring.io/spring-amqp/docs/current/reference/html/#containerAttributes查看详细介绍
    listener:
      simple:
        #手动确认模式
        acknowledge-mode: manual
        #每个使用者上未确认的最大消息数
        prefetch: 1
        #默认情况下,拒绝的传递是否重新排队
        defaultRequeueRejected: false

#    listener:
#      type: direct
#      direct:
#        #手动确认模式
#        acknowledge-mode: manual
#        #每个使用者上未确认的最大消息数
#        prefetch: 1

4.创建一个RabbitMQConfig配置类

交换机类型统一使用topic类型,因为topic交换机发信息时路由键为空("")相当于fanout,路由键单个单词写死(不包含./*/#)相当于direct

@Configuration
public class RabbitMQConfig {

    /**
     * 定义交换机
     */
    public static final String DELAY_EXCHANGE = "springboot_delay_exchange";
    public static final String DEAD_LETTER_EXCHANGE = "springboot_dead_letter_exchange";
    public static final String TOPIC_EXCHANGE = "springboot_topic_exchange";

    /**
     * 定义队列
     */
    public static final String DELAY_QUEUE = "springboot_queue_delay";
    public static final String DEAD_LETTER_QUEUE = "springboot_queue_dead_letter";
    public static final String QUEUE_A = "springboot_queue_a";

    /**
     * 定义路由规则
     */
    public static final String DELAY_ROUTING_KEY = "springboot_delay.#";
    public static final String DEAD_LETTER_ROUTING_KEY = "springboot_dead_letter.#";
    public static final String QUEUE__ROUTING_KEY = "springboot.#";

延迟消息

    /**
     * 自定义交换机-作为延迟交换机
     * 要使用“延迟消息交换”,您只需要声明一个提供"x-delayed-message"交换类型的交换
     * https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/
     *
     * @return
     */
    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "topic");
        CustomExchange delayExchange = new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args);
        return delayExchange;
    }

    /**
     * 声明一个延时队列,并绑定到自定义的死信交换机上,此处不设置队列自身和消息的过期时间(这样做的话消息延迟时间将被写死),我们统一在发送消息的时候自己设置消息的延迟时间
     * 消息流转过程:生产者->延时交换机(可以是普通交换机)->延时队列->死信交换机(DLX)->死信队列->消费者
     * 这里的延迟意味着:将消息路由到队列或其他交换机的延迟
     *
     * @return
     */
    @Bean
    public Queue delayQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY);
        // "x-message-ttl"定义消息的过期时间单位毫秒
        args.put("x-message-ttl", 6000);
        //x-max-length 定义该队列最多接收消息数
        args.put("x-max-length", 6);
        return new Queue(DELAY_QUEUE, true, false, false, args);
    }

    /**
     * 把延时队列绑定到延时交换机(可以是普通交换机)
     *
     * @return
     */
    @Bean
    public Binding bindingDelayQueue() {
        return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY).noargs();
    }

死信消息

    /**
     * 声明一个交换机-作为死信交换机
     * 死信交换(DLX)是普通交换,它们可以是任何通常的类型,并且可以照常声明.
     *
     * @return
     */
    @Bean
    public TopicExchange deadLetterExchange() {
        return new TopicExchange(DEAD_LETTER_EXCHANGE);
    }

    /**
     * 声明一个死信队列(实际业务需要监听该队列)
     * 消息流转过程:生产者->延时交换机(可以是普通交换机)->延时队列->死信交换机(DLX)->死信队列->消费者
     *
     * @return
     */
    @Bean
    public Queue deadLetterQueue() {
        return new Queue(DEAD_LETTER_QUEUE);
    }

    /**
     * 把死信队列(实际业务需要监听该队列)绑定到死信交换
     *
     * @return
     */
    @Bean
    public Binding bindingDeadLetterQueue() {
        return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(DEAD_LETTER_ROUTING_KEY);
    }

实时消息

    /**
     * 声明一个主题交换机,默认持久化,不自动删除
     *
     * @return
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE);
    }

    /**
     * 声明一个队列,默认持久化,不自动删除
     *
     * @return
     */
    @Bean
    public Queue springbootQueueA() {
        return new Queue(QUEUE_A);
    }

    /**
     * 把队列绑定到交换机并指定路由规则
     *
     * @return
     */
    @Bean
    public Binding bindingA() {
        return BindingBuilder.bind(springbootQueueA()).to(topicExchange()).with(QUEUE__ROUTING_KEY);
    }
}

5.为了方便测试,引入openapi和lombok依赖

        <!-- swagger部分:用于自动生成和显示接口文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

6.创建生产者

@Slf4j
@RestController
@RequestMapping(value = "rabbitmq")
@Tag(name = "rabbitmq演示controller", description = "手动控制消息发送时机,查看消息接收情况")
public class Producer {

    @Autowired
    public RabbitTemplate rabbitTemplate;

    private void sendDelayMsg(String exchange, String routingKey, Object message, Integer delayTime) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(exchange, routingKey, message, a -> {
            a.getMessageProperties().setDelay(delayTime);
            return a;
        }, correlationData);
        log.info("发送了消息---" + message + "---消息ID---" + correlationData.getId());
    }

    @GetMapping("sendMsg")
    @Operation(summary = "发送消息接口", description = "默认发送消息到springboot_topic_exchange交换机")
    @Parameter(name = "msg", description = "发送的消息", example = "this is a real time message")
    @Parameter(name = "routingKey", description = "消息的路由规则", example = "springboot.key1")
    public ResponseEntity<String> sendMsg(@RequestParam(name = "msg", defaultValue = "this is a real time message") String msg,
                                          @RequestParam(name = "routingKey", defaultValue = "") String routingKey) {
        try {
            this.sendDelayMsg(TOPIC_EXCHANGE, routingKey, msg, 0);
            return ResponseEntity.ok(msg);
        } catch (AmqpException e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

    @GetMapping("sendTTLMsg")
    @Operation(summary = "发送延迟消息接口", description = "默认发送消息到springboot_delay_exchange交换机")
    @Parameter(name = "msg", description = "发送的消息", example = "this is a delay message")
    @Parameter(name = "delayTime", description = "消息延迟多长时间(毫秒)发送", example = "10000")
    @Parameter(name = "routingKey", description = "消息的路由规则", example = "springboot_delay.key1")
    public ResponseEntity<String> sendTTLMsg(@RequestParam(name = "msg", defaultValue = "this is a delay message") String msg,
                                             @RequestParam(name = "delayTime", defaultValue = "5000") Integer delayTime,
                                             @RequestParam(name = "routingKey", defaultValue = "springboot_delay.") String routingKey) {
        try {
            this.sendDelayMsg(DELAY_EXCHANGE, routingKey, msg, delayTime);
            return ResponseEntity.ok(msg);
        } catch (AmqpException e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

}

7.创建消费者

@Slf4j
@Component
public class Consumer {

    /**
     * 接收普通队列消息
     */
    @RabbitListener(queues = QUEUE_A)
    public void processMessage(Message message, Channel channel, @Headers Map<String, Object> headers) throws IOException {
        try {
            //每次处理一条消息
            channel.basicQos(1);
            String msg = new String(message.getBody(), "UTF-8");
            log.info("接收到普通队列的消息:---{}---消息ID---{}", msg, headers.get("spring_returned_message_correlation"));
            /**
             * 第一个参数,消息的标识
             * RabbitMQ 推送消息给 Consumer 时,会附带一个 Delivery Tag,以便 Consumer可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了;
             * 第二个参数 multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;
             * 如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认。
             */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            /**
             * 第一个参数,消息的标识
             * 第二个参数是否批量处理消息,true:将一次性拒绝所有小于deliveryTag的消息。
             * 第三个参数,消息是否重入队列,false将消息存队列删除。true:会重复消费该消息直到消息被确认消费
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

    /**
     * 接收延迟消息
     */
    @RabbitListener(queues = DELAY_QUEUE)
    public void processMessage1(Message message, Channel channel, @Headers Map<String, Object> headers) throws IOException {
        try {
            String msg = new String(message.getBody(), "UTF-8");
            log.info("接收到延迟消息:---{}---消息ID---{}", msg, headers.get("spring_returned_message_correlation"));
        } catch (UnsupportedEncodingException e) {
            //第二个false将消息存队列删除,true放回队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    /**
     * 接收死信队列消息
     */
    @RabbitListener(queues = DEAD_LETTER_QUEUE)
    public void processMessage2(Message message, Channel channel, @Headers Map<String, Object> headers) throws IOException {
        try {
            //可以考虑数据库记录,每次进来查数量,达到一定的数量,进行预警,人工介入处理
            String msg = new String(message.getBody(), "UTF-8");
            log.info("接收到死信消息:---{}---消息ID---{}", msg, headers.get("spring_returned_message_correlation"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //回复ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

8.启动项目,访问openapi接口页面发消息测试

8.1 发送普通消息

8.2 发送延迟消息

8.3 发送死信消息

要想收到死信消息,必须使消息死亡然后通过死信交换机转移到死信队列,修改代码重启项目测试

 发送了两条消息,可以看到第二条消息直接转移到了死信队列,并且时间间隔变成了8秒(消息自身2s+队列6s),第一条消息死亡后还在原队列(因为消费者还在)

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值