一、实现方案
本方案参考「RabbitMQ消息可靠性深度解析|从零构建高可靠消息系统的实战指南」,向开源致敬!
1、业务层幂等处理
每个消息携带一个全局唯一ID,在业务处理过程中,首先检查这个ID是否已经被处理过。例如,将已处理消息的ID记录到数据库的“已处理消息表”中,下次收到同样ID的消息时直接返回成功而不进行实际操作。
对于更新型操作,可以使用乐观锁或分布式锁来保证同一事务多次执行结果相同,例如通过版本号(version)控制更新操作,只有当版本号未变时才执行更新。
对于创建型操作,确保即使多次调用也不会生成多个资源,例如通过查询是否存在相同的唯一键来决定是否创建新的资源。
2、确认模式选择
使用acknowledgement模式,消费者接收到消息后必须发送确认给RabbitMQ,只有收到确认后RabbitMQ才会从队列中移除消息,否则会在连接恢复后重新投递。
设置publisher confirms,生产者可以得到消息发布的确认,确保消息确实到达了MQ服务器并持久化存储。
3、死信队列与重试策略
配置死信交换机和死信队列,对于那些重复投递依然无法正确处理的消息,可以转移到死信队列,并设置相应的重试策略及最大重试次数,超过限制则记录日志、报警或手动介入处理。
4、幂等服务设计
设计能够应对重复调用的服务接口,这些接口内部应该包含足够的逻辑判断以识别重复请求并作出正确的响应。
5、事务与补偿机制
对于涉及多个系统的分布式事务场景,可以考虑采用TCC(Try-Confirm-Cancel)模式或其他分布式事务解决方案,使得整个流程具有幂等性。
总结来说,在RabbitMQ中实现幂等性主要依赖于业务逻辑层面的改造和优化,同时配合RabbitMQ自身的消息确认机制来确保消息不会因为异常情况而重复处理。
二、代码实战
1、通过雪花算法生成分布式唯一ID
/**
* SnowflakeIdWorker雪花算法
*
* @author huahua
* @DATE 2025/5/25
**/
@Component
public class SnowflakeIdWorker {
// 起始的时间戳 (2010-01-01)
private final long twepoch = 1288834974657L;
// 机器标识位数
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
// 序列号位数
private final long sequenceBits = 12L;
// 工作机器ID最大值
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 每一部分向左的偏移量
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 时间戳边界值
private long lastTimestamp = -1L;
// 工作节点ID(0~31)
@Value("${snowConfig.workerId}")
private long workerId;
// 数据中心ID(0~31)
@Value("${snowConfig.datacenterId}")
private long datacenterId;
// 每个节点每毫秒内的序列号
private AtomicLong sequence = new AtomicLong(0L);
/**
* 通过专属工作节点ID和数据中心ID构建专属的雪花算法工具类
*/
public SnowflakeIdWorker() {
if (this.workerId > maxWorkerId || this.workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (this.datacenterId > maxDatacenterId || this.datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
}
/**
* 分布式唯一ID生成
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果是同一时间生成的,则进行序列号的自增
if (lastTimestamp == timestamp) {
sequence.incrementAndGet()
SpringBoot+Redis实现RabbitMQ幂等性设计

最低0.47元/天 解锁文章
2354

被折叠的 条评论
为什么被折叠?



