RabbitMq基础及死信队列使用

交换机模式

  1. fanout
    fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
  2. direct
    direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
  3. topic
    前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但是key可以用模糊匹配。

项目使用

手动应答模式下,nack或者不ack都会让数据在MQ中积压,抛出异常会重试,到达重试次数会丢失该数据。在实际使用中,我们trycatch业务代码,当发送异常时候,必须在catch中手动抛出异常,MQ才会使用重试机制(类似事务的机制)。重试次数与yml中配置一致,且需要缓存错误次数。另外,当消息到达错误次数上限的时候,通过nack让数据进入死信队列。在死信队列中将消息入库。

重试机制 + 死信队列

yml配置

  rabbitmq:
    host: 121.36.44.93
    port: 5672
    username: admin
    password: admin
    listener:
      type: simple
      simple:
        default-requeue-rejected: false
        acknowledge-mode: manual
        retry:
          max-attempts: 5
          enabled: true

普通业务队列,绑定死信交换机

	@Bean
    public Queue systemQueue() {
        Map<String, Object> args = new HashMap<>(2);
//       x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", BizEnum.Message.DLX_EXCHANGE.getType());
//       x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", "deadLetter.system");
        return QueueBuilder.durable(BizEnum.Message.SYSTEM_QUEUE.getType()).withArguments(args).build();
    }

死信队列

	@Bean
    public Queue deadLetterQueue() {
        return new Queue(BizEnum.Message.DLX_QUEUE.getType(), true, false, false, null);
    }

死信交换机

	@Bean
    TopicExchange deadLetterExchange() {
        return new TopicExchange(BizEnum.Message.DLX_EXCHANGE.getType(), true, false, null);
    }

业务队列监听

	@RabbitListener(queues = {"system-queue"})
    @Transactional(rollbackFor = Exception.class)
    public void handleMessage(Message message, com.rabbitmq.client.Channel mqChannel) throws IOException {
        String consumerTag = message.getMessageProperties().getConsumerTag();
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
           // ... 业务处理
           mqChannel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            if(MqCache.checkMaxRetry(consumerTag)){
            	MqCache.clearConsumerTagCount(consumerTag);
                mqChannel.basicNack(deliveryTag, false, false);
            }else{
                MqCache.cacheConsumerTagCount(consumerTag);
                log.error("数据错误 ===> {}", e);
                throw e;
            }
        }
    }

死信队列监听,可以将错误数据入库,或者不ack,后期再消费该队列数据

	@RabbitListener(queues = {"dlx-queue"})
    @Transactional(rollbackFor = Exception.class)
    public void dlMessage(Message message, Channel channel) throws IOException {
       String msg = new String(message.getBody());
       // 入库
         channel.basicAck(deliveryTag, false);
    }

缓存失败次数

public class MqCache extends BaseCache {

    private final static StringRedisTemplate stringRedisTemplate;

    static {
        stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
    }


    // 与yml配置一致
    private static Integer retryCount = 5;

    private static final String CONSUMER_TAG_KEY = "pe:mq:consumerTag:";

    public static void cacheConsumerTagCount(String consumerTag) {
        stringRedisTemplate.opsForValue().increment(CONSUMER_TAG_KEY + consumerTag, 1);
        bladeRedis.expire(CONSUMER_TAG_KEY + consumerTag, Duration.ofHours(12));
    }

    public static boolean checkMaxRetry(String consumerTag) {
        return getConsumerTagCount(consumerTag) >= retryCount - 1;
    }

    public static Integer getConsumerTagCount(String consumerTag) {
        String failCount = stringRedisTemplate.opsForValue().get(CONSUMER_TAG_KEY + consumerTag);
        if (failCount != null) {
            return Integer.parseInt(failCount);
        }
        return 0;
    }

    public static void clearConsumerTagCount(String consumerTag) {
        bladeRedis.del(CONSUMER_TAG_KEY + consumerTag);
    }
}

消息确认机制

在这里插入图片描述

@Component
@AllArgsConstructor
@Slf4j
public class MqSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    private void initRabbitTemplate() {
        //设置消息发送确认回调,发送成功后更新消息表状态
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);

    }

    public void sendMessage(String exchange, String routingKey, OrderBean orderBean) {
        rabbitTemplate.convertAndSend(exchange, routingKey, JSON.toJSONString(orderBean),
                message -> {
                    //设置消息持久化
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    return message;
                },
                new CorrelationData(orderBean.getOrderNo()));
    }


    public void sendMessage(String exchange, String routingKey, Object message) {
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (!ack) {
            /*
             *   处理消息没有到达交换机,数据丢失的情况
             *   根据订单号查询到订单数据,并将数据保存到异常消息表中,定时补发,并报警人工处理
             * */
            String orderId = correlationData.getId();
        } else {
            //查询订单号是否在异常消息表,在的话要删除
            log.info(">>>下单消息发送成功{}<<<", correlationData);

        }
    }

    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        //消息到达交换机,没有路由到队列,根据订单号查询到订单数据,并将数据保存到异常消息表中,定时补发,并报警人工处理
        /*
         *  1 交换机没有绑定队列
         *  2 交换机根据路由键没有匹配到队列
         *  3 队列消息已满
         * */
        byte[] body = message.getBody();
        JSONObject json = JSONObject.parseObject(new String(body));
        System.out.println("return============================");
        System.out.println(message);
    }

消息确认回调:

  1. 当消息发送到了一个不存在的交换机,会进入confirm方法(ack参数为false)。
  2. 当消息发送到了一个存在的交换机,且
    (1 交换机没有绑定队列、2 交换机根据路由键没有匹配到队列、3.队列消息已满)
    ,会先进入returnedMessage方法,再进入confirm方法(ack参数为true)。
私信队列RabbitMQ中的一种特殊队列,用于处理无法被消费者处理或处理失败的消息。当消息在被消费者拒绝后,可以通过私信队列重新发送给特定的消费者进行处理。私信队列的实现方式是通过死信机制来实现的。 在RabbitMQ中,通过将一个队列绑定到一个特定的死信交换机上,当消息在原队列中被拒绝、过期或达到最大重试次数时,就会被发送到私信队列中。这样,我们可以对私信队列进行单独的处理,例如记录日志、发送通知等。 为了实现私信队列,我们可以设置消息的过期时间,并将队列绑定到一个死信交换机上。当消息过期后,它会被路由到死信交换机,然后再路由到私信队列,供消费者处理。通过这种方式,我们可以很好地处理那些无法被正常消费的消息,确保消息的可靠性和稳定性。 总之,私信队列是通过RabbitMQ死信机制来实现的,用于处理无法被消费者处理或处理失败的消息。它是一种非常有用的机制,可以帮助我们解决消息消费中的异常情况,并提高消息处理的可靠性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Spring RabbitMQ死信机制原理实例详解](https://download.csdn.net/download/weixin_38500948/12742891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【二】RabbitMQ基础篇(延迟队列死信队列实战)](https://blog.csdn.net/weixin_56995925/article/details/123711256)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [rabbitmq TTL和私信队列](https://blog.csdn.net/wufagang/article/details/117197627)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值