RabbitMQ的消费者处理消息失败后之重试3次,重试3次仍然失败发送到死信队列。

1、为什么要重试?

       如果消费者处理消息失败后不重试,然后发送应答给rabbitmq,rabbitmq就会将队列中的消息删除,从而造成消息的丢失。所以我们要在消费者处理消息失败的时候,重试一定的次数。比如重试3次,如果重试3次之后还是失败,则把这条消息发送到死信队列。

       所以我们现在要实现消息的重试,实现效果为:

       首先,将消息携带routtingkey的消息发送到正常转发器exchange@normal,exchange@normal将消息发送到正常队列queue@normal,queue@normal得到消息后进行处理,如果处理成功,则给rabbitmq发送应答。如果消息处理失败,判断消息失败的次数:如果失败次数小于3次,则将消息发送到重试转发器exchange@retry,exchange@retry得到消息后,发送到重试队列queue@retry,queue@retry10s后,将该条消息再次发送到正常转发器exchange@normal进行正常的消费;如果失败次数超过3次,则将消息发送到失败转发器exchange@filed,exchange@filed将失败了的消息发送给失败队列queue@filed,然后可以根据业务需求处理失败了的数据。比如保存到失败文件或者数据库等,也可以人工处理后,重新发送给exchange@normal。

      思路图如下:

           

                 

2、重试实现的思路

      生产者端发送消息:

      1)、声明三个转发器

 //正常的转发器
 private static String NORMAL_EXCHANGE = "exchange@normal";
 //重试转发器
 private static String RETRY_EXCHANGE = "exchange@retry";
 //失败转发器
 private static String FILED_EXCHANGE = "exchange@filed";

                      ......

 //声明正常的转发器
 channel.exchangeDeclare(NORMAL_EXCHANGE,"topic");
 //声明重试的转发器
 channel.exchangeDeclare(RETRY_EXCHANGE,"topic");
 //声明失败的转发器
 channel.exchangeDeclare(FILED_EXCHANGE,"topic");

     2)、发送消息到正常的队列

//发送5条消息到正常的转发器,路由密匙为normal
for(int i=0;i<5;i++){
      String message = "retry..."+i;
      channel.basicPublish(NORMAL_EXCHANGE,"normal",             
      MessageProperties.PERSISTENT_BASIC,message.getBytes());
}

      消费者端接受、处理消息:

      1)、定义并声明转发器和队列,需要注意的是queue@normal和queue@filed两个队列就是普通的队列。queue@retry队列需要设置两个参数:x-dead-letter-exchange、x-message-ttl。这两个参数按照我自己的理解是x-dead-letter-exchange指定重试时将消息重发给哪一个转发器、x-message-ttl消息到达重试队列后,多长时间后重发。

    //转发器
    private static String NORMAL_EXCHANGE = "exchange@normal";
    private static String RETRY_EXCHANGE = "exchange@retry";
    private static String FILED_EXCHANGE = "exchange@filed";
    //队列
    private static String NORMAL_QUEUE = "queue@normal";
    private static String RETRY_QUEUE = "queue@retry";
    private static String FILED_QUEUE = "queue@filed";

                         ......
    
    //声明正常队列
    channel.queueDeclare(NORMAL_QUEUE,true,false,false,null);

    //声明重试队列,重试队列比较特殊,需要设置两个参数
    Map<String,Object> arg = new HashMap<String,Object>();
    //参数1:将消息发送到哪一个转发器
    arg.put("x-dead-letter-exchange",NORMAL_EXCHANGE);
    //参数2:多长时间后重发
    arg.put("x-message-ttl",10000);
    channel.queueDeclare(RETRY_QUEUE,true,false,false,arg);

    //声明失败队列
    channel.queueDeclare(FILED_QUEUE,true,false,false,null);

     2)、将队列绑定转发器和路由密匙

//将队列绑定转发器和路由密匙
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
channel.queueBind(RETRY_QUEUE,RETRY_EXCHANGE,"normal");
channel.queueBind(FILED_QUEUE,FILED_EXCHANGE,"normal");

     3)、定义consumer,消费消息。因为消息设置的是自动应答,所以不需要手动应答。如果你设置了手动应答,则在消息消费成功或者失败后都要应答。

Consumer consumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //此处处理消息
                    try{
                        String message = new String(body,"utf-8");
                        System.out.println("消费者接受到的消息:"+message);
                        //模拟处理消息产生异常
                        int i = 1/0;
                    }catch(Exception e){
                        try{
                            //延迟5s
                            Thread.sleep(5000);
                            //判断失败次数
                            long retryCount = getRetryCount(properties);
                            if(retryCount>=3){
                                //如果失败超过三次,则发送到失败队列
                                channel.basicPublish(FILED_EXCHANGE,envelope.getRoutingKey(),MessageProperties.PERSISTENT_BASIC,body);
                                System.out.println("消息失败了...");
                            }else{
                                //发送到重试队列,10s后重试
                                channel.basicPublish(RETRY_EXCHANGE,envelope.getRoutingKey(),properties,body);
                                System.out.println("消息重试中...");
                            }
                        }catch (Exception e1){
                            e.printStackTrace();
                        }

                    }
                }
            };
//消费消息
channel.basicConsume(NORMAL_QUEUE,true,consumer);

     判断消息失败次数的方法如下:

public long getRetryCount(AMQP.BasicProperties properties){
        long retryCount = 0L;
        Map<String,Object> header = properties.getHeaders();
        if(header != null && header.containsKey("x-death")){
            List<Map<String,Object>> deaths = (List<Map<String,Object>>)header.get("x-death");
            if(deaths.size()>0){
                Map<String,Object> death = deaths.get(0);
                retryCount = (Long)death.get("count");
            }
        }
        return retryCount;
}

重试的完整代码如下:

     1)、生产者

@Component
public class RetryPublisher {

    private static String NORMAL_EXCHANGE = "exchange@normal";
    private static String RETRY_EXCHANGE = "exchange@retry";
    private static String FILED_EXCHANGE = "exchange@filed";
    public void send(){
        Connection connection = ConnectionUtil.getInstance();
        Channel channel = null;
        try{
            channel = connection.createChannel();
            //声明正常的转发器
            channel.exchangeDeclare(NORMAL_EXCHANGE,"topic");
            //声明重试的转发器
            channel.exchangeDeclare(RETRY_EXCHANGE,"topic");
            //声明失败的转发器
            channel.exchangeDeclare(FILED_EXCHANGE,"topic");

            //发送5条消息到正常的转发器,路由密匙为normal
            for(int i=0;i<5;i++){
                String message = "retry..."+i;
                channel.basicPublish(NORMAL_EXCHANGE,"normal", MessageProperties.PERSISTENT_BASIC,message.getBytes());
            }
        }catch (Exception e){

        }
    }
}

     2)、消费者方法和判断消息失败次数的方法

@Component
public class RetryReceiver {

    //转发器
    private static String NORMAL_EXCHANGE = "exchange@normal";
    private static String RETRY_EXCHANGE = "exchange@retry";
    private static String FILED_EXCHANGE = "exchange@filed";
    //队列
    private static String NORMAL_QUEUE = "queue@normal";
    private static String RETRY_QUEUE = "queue@retry";
    private static String FILED_QUEUE = "queue@filed";


    public void receiver(){

        Connection connection = ConnectionUtil.getInstance();
        final Channel channel;
        try{
            channel = connection.createChannel();
            //声明正常队列
            channel.queueDeclare(NORMAL_QUEUE,true,false,false,null);

            //声明重试队列,重试队列比较特殊,需要设置两个参数
            Map<String,Object> arg = new HashMap<String,Object>();
            //参数1:将消息发送到哪一个转发器
            arg.put("x-dead-letter-exchange",NORMAL_EXCHANGE);
            //参数2:多长时间后发送
            arg.put("x-message-ttl",10000);
            channel.queueDeclare(RETRY_QUEUE,true,false,false,arg);

            //声明失败队列
            channel.queueDeclare(FILED_QUEUE,true,false,false,null);


            //将队列绑定转发器和路由密匙
            channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
            channel.queueBind(RETRY_QUEUE,RETRY_EXCHANGE,"normal");
            channel.queueBind(FILED_QUEUE,FILED_EXCHANGE,"normal");

            Consumer consumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //此处处理消息
                    try{
                        String message = new String(body,"utf-8");
                        System.out.println("消费者接受到的消息:"+message);
                        //模拟处理消息是产生异常
                        int i = 1/0;
                    }catch(Exception e){
                        try{
                            //延迟5s
                            Thread.sleep(5000);
                            //判断失败次数
                            long retryCount = getRetryCount(properties);
                            if(retryCount>=3){
                                //如果失败超过三次,则发送到失败队列
                                channel.basicPublish(FILED_EXCHANGE,envelope.getRoutingKey(),MessageProperties.PERSISTENT_BASIC,body);
                                System.out.println("消息失败了...");
                            }else{
                                //发送到重试队列,10s后重试
                                channel.basicPublish(RETRY_EXCHANGE,envelope.getRoutingKey(),properties,body);
                                System.out.println("消息重试中...");
                            }
                        }catch (Exception e1){
                            e.printStackTrace();
                        }

                    }
                }
            };
            //消费消息
            channel.basicConsume(NORMAL_QUEUE,true,consumer);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获取消息失败次数
     * @param properties
     * @return
     */
    public long getRetryCount(AMQP.BasicProperties properties){
        long retryCount = 0L;
        Map<String,Object> header = properties.getHeaders();
        if(header != null && header.containsKey("x-death")){
            List<Map<String,Object>> deaths = (List<Map<String,Object>>)header.get("x-death");
            if(deaths.size()>0){
                Map<String,Object> death = deaths.get(0);
                retryCount = (Long)death.get("count");
            }
        }
        return retryCount;
    }
}

     3)、测试Controlelr

@Controller
public class RetryController {
    @Autowired
    private RetryPublisher publisher;
    @Autowired
    private RetryReceiver receiver;

    @RequestMapping("/retrySend")
    @ResponseBody
    public void send(){
        publisher.send();
    }

    @RequestMapping("/retryReceive")
    @ResponseBody
    public void receive(){
        receiver.receiver();
    }

}

本文的思路图来自博主:https://www.cnblogs.com/itrena/p/9044097.html

  • 3
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
死信队列(Dead Letter Queue,简称DLQ)是 RabbitMQ 中的一种特殊队列,用于存储无法被消费者正确处理消息。当消息被标记为死信时,它将被重新路由到死信队列中,而不是被丢弃或重新投递给原始队列。 在 RabbitMQ 中,死信消息的产生通常由以下几种情况引起: 1. 消息被拒绝(basic.reject或basic.nack)并且不重新入队。 2. 消息队列中过期。 3. 队列达到最大长度限制。 使用死信队列可以实现一些重要的功能,例如: 1. 错误处理:将处理失败消息转移到死信队列中,以便进一步分析和处理。 2. 延迟消息:通过设置消息的过期时间,将过期的消息路由到死信队列,实现延迟处理。 3. 消息重试:可以通过设置死信队列和原始队列之间的绑定关系,将死信消息重新发送到原始队列进行重试。 要使用死信队列,需要进行以下步骤: 1. 创建一个普通的队列和一个死信队列。 2. 定义一个交换机和与普通队列绑定。 3. 设置普通队列的参数,包括最大长度、过期时间等。 4. 将死信队列和普通队列之间建立绑定关系,指定死信的路由键。 通过以上配置,当消息被拒绝、过期或达到最大长度时,会被路由到死信队列中。可以通过消费死信队列中的消息进行进一步处理或分析。 希望以上信息对你有帮助!如果还有其他问题,请继续提问。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值