消息的重复消费及处理

重复消费概述

当消息回退到队列里面后,会被再次消费,但是我们不能让消息消费成功2次其实, MQ 自己就可以保证消息不被重复消费,因为 MQ 可以把消息投递给消费者时,是阻塞的,不会把一个消息投递给多个消费者!但是面试时,有人问你,消息怎么保证不被重复消费!无论在 RabbitMQ, 或者 ActiveMQ 里面,解决思路都是一样的!!对于重复消费 → 去重操作 → 接口的幂等操作( → 找到该操作的有个唯一标识 → 具体的情况,具体讨论)Eg: 微信里面,若有人关注我了,我给他发红包,我的红包只能发送一次,我们可以使用用户的 openId 做一个去重的操作订单不重复(订单的编号)(先按订单编号查询,再操作)数据库不能重复(数据库的 id)(先查询是否存在,再进行操作)总结:去重操作就是找到一个该操作的唯一标识,把该标识,放在一个空间里面,在此操作时,我们可以先判断该空间是否已经操作过它了。

利用 BloomFilter 实现去重

官方地址:https://www.hutool.cn/docs/#/bloomFilter/%E6%A6%82%E8%BF%B0

添加 Hutool 的依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.5</version>
</dependency>

配置 BitMapBloomFilter

/**
 * @author BNTang
 */
@Configuration
public class BloomFilterConfig {

    @Bean
    public BitMapBloomFilter bitMapBloomFilter() {
        return new BitMapBloomFilter(1024);
    }

}

使用 Hutool-BloomFilter 去重

改造之前的消费代码如下所示:

/**
 * @author BNTang
 */
@Component
public class MessageReceive {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private BitMapBloomFilter bitMapBloomFilter;

    /**
     * 消息的前缀
     */
    private String MESSAGE = "message:";

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("queue"),
                    key = {"error"},
                    exchange = @Exchange(value = "directs")
            )
    })
    public void receiveMessage(String content, Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String messageId = message.getMessageProperties().getMessageId();

        System.out.println("消息投递ID → :" + deliveryTag);
        System.out.println("消息自定义ID → :" + messageId);

        if (this.bitMapBloomFilter.contains(messageId)) {
            System.out.println("该消息被消费过,不能重复进行消费");
            try {
                // 如果进入到这里面,说明这个消息之前被消费过,但是 MQ 认为你没有消费,所以我们要签收这条消息
                channel.basicAck(deliveryTag, false);
                return;
            } catch (Exception e) {
                System.out.println(e);
            }
        }

        // 如果消息内容为 123456 就签收它
        if (content.equals("123456")) {
            channel.basicAck(deliveryTag, true);
            System.out.println("消息签收成功");

            // 消费成功之后放到布隆过滤器里面
            this.bitMapBloomFilter.add(messageId);
        } else {
            String count = this.redisTemplate.opsForValue().get(MESSAGE + messageId);

            if (count != null && Long.valueOf(count) >= 3) {
                channel.basicNack(deliveryTag, false, false);
                System.out.println("该消息消费【3】次都失败,我们记录它,人工处理" + content);
            } else {
                // 如果不是 123456 就决绝签收
                channel.basicNack(deliveryTag, false, true);
                System.out.println("消息被决绝签收");

                // 因为拒绝了,我们把消息ID放到Redis里面
                this.redisTemplate.opsForValue().increment(MESSAGE + messageId);
            }
        }
    }
}

发消息的代码和前面章节一样,不在贴了。

posted @ 2020-11-13 11:16  BNTang  阅读(1871)  评论(0编辑  收藏  举报