消息的重复消费及处理
重复消费概述
当消息回退到队列里面后,会被再次消费,但是我们不能让消息消费成功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);
}
}
}
}
发消息的代码和前面章节一样,不在贴了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具