SpringCloud stream 集成RabbitMQ 之死信队列讲解

        

        当消息处理消费失败后,SpringCloud Stream 会自动默认重试3次,重试三次失败后,RepublishMessageRecoverer类recover方法会将改变routingkey为队列名称发送至死信队列。目前产生死信队列有两种方式:

  • 默认自动为每个消息队列产生一个死信队列,消费失败时会路由至该队列的死信队列

  • 直接指定每个消息队列绑定的死信队列,多个消息队列可绑定同一个死信队列

本案例采用第2种方式,便于所有消费失败信息处理。

1、配置注意事项

注意若系统中已经存在的消息队列,一定要在rabbitmq删除该队列,否则无法创建死信队列。 只需要配置spring.cloud.rabbit.bindings部分其中有两个注意点:

  • spring.cloud.bindings.input.group和spring.cloud.bindings.input.destination必须要配置,并且值不能一致,否则因为springcloud Dalston.SR1版本问题,死信消息队列的routingKey会取数错误,详细可参见RabbitMessageChannelBinder类createConsumerEndpoint方法,Spring Cloud 2.0发现该代码已经大部分改动了,没有BUG。

  • maxAttempts 可根据实际情况判断是否需要重试

 

在上一个步骤的例子中,我们在消费者端模拟一下消费消息出现异常的情况

             

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第1张图片

             

再次运行后,可以看到消息被重复执行了三次,因为springCloud stream消息默认情况下是消费消息时若是出错了会重试三次,超过三次之后就会进入死信队列,若没有配置死信队列则该消息会丢失。

 

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第2张图片

 

8.2、配置死信队列

在上个列子中消费者端增加了以下代码

 

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第3张图片

完整配置如下(对队列saveOrderInput配置了死信队列):

8.2.1 消费者配置文件

server:
  port: 8087
spring:
  application:
    name: HuaXing-stream-consumer
  cloud:
    stream:
      binders:  #需要绑定的rabbitmq的服务信息
        defaultRabbit:  #定义的名称,用于bidding整合
          type: rabbit  #消息组件类型
          environment:  #配置rabbimq连接环境
            spring:
              rabbitmq:
                host: localhost   #rabbitmq 服务器的地址
                port: 45672           #rabbitmq 服务器端口
                username: guest       #rabbitmq 用户名
                password: guest       #rabbitmq 密码
                virtual-host: /       #虚拟路径
      bindings:        #服务的整合处理
        saveOrderInput:    #这个是消息通道的名称 ---> 保存订单输入通道
          destination: exchange-saveOrder     #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
          content-type: application/json      #设置消息的类型,本次为json
          default-binder: defaultRabbit
          group: saveOrderGroup               #分组
        orderInput:         #这个是消息通道的名称 ---> 订单输入通道
          destination: exchange-order     #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
          content-type: application/json      #设置消息的类型,本次为json
          default-binder: defaultRabbit
          group: orderGroup               #分组
        orderOutput:        #这个是消息通道的名称 ---> 订单输出通道
          destination: exchange-order     #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
          content-type: application/json      #设置消息的类型,本次为json
          default-binder: defaultRabbit
          group: orderGroup               #分组
      rabbit:
        bindings:
          saveOrderInput:
            consumer:
              #ttl: 20000 # 默认不做限制,即无限。消息在队列中最大的存活时间。当消息滞留超过ttl时,会被当成消费失败消息,即会被转发到死信队列或丢弃.即消息在队列中存活的最大时间为 20s
              # DLQ相关
              autoBindDlq: true # 是否自动声明死信队列(DLQ)并将其绑定到死信交换机(DLX)。默认是false。
              republishToDlq: true
              #deadLetterExchange: exchange-order-dlq  #绑定exchange
              #deadLetterQueueName: exchange-order-dlq.saveOrderInput  #死信队列名字:exchanName.queueName

 

8.2.2 生产者配置文件

server:
  port: 8088
 
spring:
  cloud:
    stream:
      binders: 
        defaultRabbit: 
          type: rabbit 
          environment:
            spring:
              rabbitmq:
                host: localhost  
                port: 45672          
                username: guest      
                password: guest      
                virtual-host: /      
      bindings:       
        saveOrderOutput:   
          destination: exchange-saveOrder    
          content-type: application/json     
          default-binder: defaultRabbit
          group: saveOrderGroup

8.3 具体实现

8.3.1 在saveOrderInput通道消费者端模拟异常

        在saveOrderInput通道消费者端增加int i = 1 / 0; 代码来模拟异常的出现。

   

    /**
     * 保存订单逻辑
     * 通过OrderChannelProcessor.SAVE_ORDER_INPUT 接收消息
     * 然后通过@SendTo 将处理后的消息发送到 OrderChannelProcessor.ORDER_OUTPUT
     *
     * @param message
     * @return
     */
    @StreamListener(OrderInputChannelProcessor.SAVE_ORDER_INPUT)
    @SendTo(OrderOutputChannelProcessor.ORDER_OUTPUT)
    public String saveOrderMessage(Message message) {
        log.info("保存订单的消息:" + message);
        int i = 1 / 0;
        //处理之后的订单消息
        return "【" + message.getPayload() + "】";
    }

 

完整的代码如下:

8.3.2 生产者代码

/**
 * @Description 订单控制器
 * @author: 姚广星
 * @time: 2020/11/29 12:03
 */
@RestController
@Slf4j
public classOrderController {
 
    @Autowired
    private OrderMessageProducerorderMessageProducer;
 
    /**
     * 发送保存订单消息
     *
     * @param message
     */
    @GetMapping(value ="/sendSaveOrderMessage")
    public voidsendSaveOrderMessage(@RequestParam("message") String message) {
        //发送消息
        orderMessageProducer.sendMsg(message);
        log.info("发送保存订单消息成功");
    }
}

 

/**
 * @Description 订单消息发送者
 * @author: 姚广星
 * @time: 2020/11/29 19:37
 */
@Slf4j
@EnableBinding(value ={OrderOutputChannelProcessor.class})
public classOrderMessageProducer {
    @Autowired
   @Output(OrderOutputChannelProcessor.SAVE_ORDER_OUTPUT)
    private MessageChannel channel;
    /**
     * 发送消息
     *
     * @param msg
     */
    public void sendMsg(String msg) {
       channel.send(MessageBuilder.withPayload(msg).build());
        log.info("消息发送成功:" + msg);
    }
}

 

8.3.3 消费者代码

/**
 * @Description 订单消息监听处理服务
 * @author: 姚广星
 * @time: 2020/11/29 16:03
 */
@Slf4j
@EnableBinding({OrderInputChannelProcessor.class,OrderOutputChannelProcessor.class})
public classOrderMessageListener {
 
    /**
     * 保存订单逻辑
     * 通过OrderChannelProcessor.SAVE_ORDER_INPUT接收消息
     * 然后通过@SendTo 将处理后的消息发送到 OrderChannelProcessor.ORDER_OUTPUT
     *
     * @param message
     * @return
     */
    @StreamListener(OrderInputChannelProcessor.SAVE_ORDER_INPUT)
   @SendTo(OrderOutputChannelProcessor.ORDER_OUTPUT)
    public StringsaveOrderMessage(Message message) {
        log.info("保存订单的消息:" + message);
        int i = 1 / 0;
        //处理之后的订单消息
        return "【" + message.getPayload() + "】";
    }
 
    /**
     * 监听OrderChannelProcessor.ORDER_INPUT通道,进行发送短信业务操作
     *
     * @param message
     */
   @StreamListener(OrderInputChannelProcessor.ORDER_INPUT)
    public void orderToSMSMessage(Stringmessage) {
        log.info("进行发送短信业务操作:{}", message);
    }
 
    /**
     * 监听OrderChannelProcessor.ORDER_INPUT通道,进行存储到es业务操作
     *
     * @param message
     */
   @StreamListener(OrderInputChannelProcessor.ORDER_INPUT)
    public void orderToEsMessage(Stringmessage) {
        log.info("进行存储到es业务操作:{}", message);
    }
}

 

        分别启动消费者和生产者,可以在RabbitMQ web界面中可以看到已经创建exchange-saveOrder.saveOrderGroup队列的死信队列 exchange-saveOrder.saveOrderGroup.dlq。

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第4张图片

运行4次(任何次数都行)生产者方法发送消息给消费者,在RabbitMQ web页面可以明显的看出出错了的消息已经全部进入的死信队列当中。

 

4、查看dlq队列

点击页面队列名exchange-saveOrder.saveOrderGroup.dlq进入:

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第5张图片

Get messages按钮可以查看当前在dlq队列中存放的消息,可以输入数量(相当于查看几条),点击按钮:

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第6张图片

可以看到消息原封不动保存,并且有具体的报错信息。方便与我们来进行分析代码。

 

5、清空dlq队列所有消息

一开始时是没有Move Messages按钮的,这时我们需要安装插件

SpringCloud stream 集成RabbitMQ 之死信队列讲解_第7张图片

 

按提示需要执行下面命令开启插件,不需要重启rabbitmq

rabbitmq-plugins enablerabbitmq_shovel rabbitmq_shovel_management

 

清空消息时,可以再输入框中填入目标队列名称(可以从Get messages 部分的RoutingKey进行复制),点击按钮,清空死信队列消息。这时死信队列中的消息会回到原来的队列当中去。

本文涉及到的文献

官方文档:https://www.rabbitmq.com/

Git 代码地址:https://github.com/17666555910/HuaXing-SpringCloudDemo.git

CSDN 博客地址:https://blog.csdn.net/a767815662/category_10598332.html

微信公众号:【华星详谈】

你可能感兴趣的:(队列,java,rabbitmq,python,spring)