消息幂等性深度解析与实现方案
一、幂等性基本原理与挑战
1.1 什么是消息幂等性?
定义:对于同一个操作,无论执行多少次,结果都与执行一次相同。
数学表达:f(f(x)) = f(x)
消息系统场景:
-
生产者可能因超时重试发送重复消息
-
消费者可能因故障重启重复消费
-
网络分区导致消息重投
1.2 为什么需要幂等性?
// 非幂等操作的危害示例
public class NonIdempotentService {
// 扣款操作 - 非幂等
public void deductBalance(String userId, BigDecimal amount) {
User user = userDao.findById(userId);
user.setBalance(user.getBalance().subtract(amount));
userDao.update(user); // 重复调用会导致多次扣款!
}
// 订单创建 - 非幂等
public Order createOrder(OrderRequest request) {
// 每次调用都会创建新订单
return orderDao.save(new Order(request));
}
}全局唯一ID生成方案
二、核心实现原理深度解析
2.1 唯一标识原理
全局唯一ID生成方案
https://blog.csdn.net/weixin_46322148/article/details/153195532?spm=1011.2415.3001.5331
原理核心:为每个操作生成全局唯一标识,通过记录标识防止重复处理。
/**
* 唯一标识生成策略比较表
*
* 策略类型 | 生成方式 | 唯一性保证 | 适用场景
* -----------------|-------------------------|--------------|-----------
* 业务主键 | 订单号、支付流水号等 | 业务系统保证 | 支付、订单
* UUID | 随机生成 | 概率极高 | 通用场景
* 雪花算法 | 时间戳+机器ID+序列号 | 分布式唯一 | 高并发场景
* Redis自增ID | INCR命令 | 单Redis内唯一 | 需要有序ID
*/
public class IdGenerator {
// 方案1:业务组合键(最可靠)
public String generateBizKey(String bizType, String bizId) {
return String.format("%s:%s:%s",
bizType,
bizId,
LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE));
}
// 方案2:雪花算法(Snowflake)
public class SnowflakeIdGenerator {
private final long datacenterId;
private final long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & 4095; // 12位序列号
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22)
| (datacenterId << 17)
| (machineId << 12)
| sequence;
}
}
}
2.2 状态机原理
原理核心:定义操作的状态流转图,只有符合状态流转的操作才执行。
java
/**
* 订单状态机实现
* 核心思想:状态只能按照预定路径转移,重复操作不会改变状态
*
* 状态流转图:
* CREATED → PAID → SHIPPED → COMPLETED
* ↓ ↓
* CANCELLED ←-----
*/
public class OrderStateMachine {
// 定义允许的状态转移
private static final Map<OrderStatus, Set<OrderStatus>> TRANSITION_RULES =
ImmutableMap.<OrderStatus, Set<OrderStatus>>builder()
.put(OrderStatus.CREATED, ImmutableSet.of(OrderStatus.PAID, OrderStatus.CANCELLED))
.put(OrderStatus.PAID, ImmutableSet.of(OrderStatus.SHIPPED, OrderStatus.CANCELLED))
.put(OrderStatus.SHIPPED, ImmutableSet.of(OrderStatus.COMPLETED))
.put(OrderStatus.CANCELLED, ImmutableSet.of()) // 终止状态
.put(OrderStatus.COMPLETED, ImmutableSet.of()) // 终止状态
.build();
/**
* 幂等状态更新方法
* @param orderId 订单ID
* @param newStatus 目标状态
* @param expectedVersion 期望的版本号(乐观锁)
*/
public boolean tryUpdateStatus(String orderId, OrderStatus newStatus, int expectedVersion) {
Order order = orderDao.get(orderId);
// 1. 检查状态是否允许转移
if (!TRANSITION_RULES.getOrDefault(order.getStatus(), Set.of())
.contains(newStatus)) {
log.warn("状态转移不允许: {} -> {}", order.getStatus(), newStatus);
return false; // 幂等性保障:重复操作不会改变状态
}
// 2. 乐观锁更新
int affected = orderDao.updateStatus(
orderId,
newStatus,
expectedVersion,
expectedVersion + 1
);
return affected > 0;
}
}
2.3 版本控制原理(乐观锁)
原理核心:通过版本号检测数据是否被修改过,避免ABA问题。
sql
-- 数据库表设计
CREATE TABLE account_balance (
id BIGINT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL UNIQUE,
balance DECIMAL(15,2) NOT NULL DEFAULT 0.00,
version INT NOT NULL DEFAULT 0, -- 版本号
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 幂等更新操作
UPDATE account_balance
SET balance = balance - 100.00,
version = version + 1
WHERE user_id = 'user123'
AND version = 5 -- 必须匹配期望的版本
AND balance >= 100.00; -- 业务约束
三、详细业务场景方案对比
3.1 支付系统场景
业务特点:金额敏感、不允许重复、事务性强、响应要求高
方案一:基于数据库事务的强一致性方案
java
@Service
@Transactional(rollbackFor = Exception.class)
public class PaymentService {
@Autowired
private PaymentRecordDao paymentRecordDao;
@Autowired
private AccountBalanceDao accountBalanceDao;
/**
* 支付处理 - 完整幂等实现
* 性能:约 300 TPS(单数据库)
* 可靠性:99.99%
* 适用:核心支付、金融交易
*/
public PaymentResult processPayment(PaymentRequest request) {
// 1. 生成幂等键(支付流水号 + 业务类型)
String idempotentKey = String.format("payment:%s:%s",
request.getPaymentNo(),
request.getBizType());
// 2. 检查幂等记录(快速失败)
PaymentRecord existing = paymentRecordDao.findByIdempotentKey(idempotentKey);
if (existing != null) {
return PaymentResult.from(existing); // 幂等返回
}
// 3. 插入幂等记录(数据库唯一索引保证原子性)
try {
PaymentRecord record = new PaymentRecord();
record.setIdempotentKey(idempotentKey);
record.setStatus(PaymentStatus.PROCESSING);
record.setCreatedTime(LocalDateTime.now());
paymentRecordDao.insert(record);
} catch (DuplicateKeyException e) {
// 并发情况下其他线程已插入,返回处理中
throw new BusinessException("支付处理中,请稍后查询");
}
try {
// 4. 扣款(乐观锁保证幂等)
int rows = accountBalanceDao.deductBalance(
request.getUserId(),
request.getAmount(),
request.getExpectedVersion()
);
if (rows == 0) {
// 版本不匹配或余额不足(可能已扣过款)
PaymentRecord completed = paymentRecordDao
.findCompletedByIdempotentKey(idempotentKey);
if (completed != null) {
return PaymentResult.from(completed); // 已成功
}
throw new BalanceInsufficientException("余额不足或支付已处理");
}
// 5. 更新支付记录为成功
paymentRecordDao.updateStatus(idempotentKey,
PaymentStatus.SUCCESS,
"支付成功");
// 6. 异步通知(需要保证最终一致性)
sendPaymentSuccessEvent(request);
return PaymentResult.success(request.getPaymentNo());
} catch (Exception e) {
// 7. 更新为失败状态
paymentRecordDao.updateStatus(idempotentKey,
PaymentStatus.FAILED,
e.getMessage());
throw e;
}
}
}
优缺点分析:
text
优点:
1. 强一致性,数据不会错乱
2. 基于数据库事务,可靠性极高
3. 支持分布式事务场景
缺点:
1. 性能受数据库限制
2. 需要精心设计唯一索引
3. 对数据库连接数要求高
性能考量:
- 需要数据库连接池优化
- 建议分库分表应对高并发
- 热点账户需要特殊处理(队列化)
3.2 电商订单创建场景
业务特点:并发高、可接受短暂延迟、允许少量重复但不影响业务
方案二:Redis + 数据库最终一致性方案
java
@Service
public class OrderCreationService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 订单创建 - 高性能幂等实现
* 性能:约 5000 TPS
* 可靠性:99.9%
* 适用:高并发下单、秒杀场景
*/
public Order createOrder(OrderRequest request) {
// 1. 生成订单幂等键
String orderDedupeKey = generateOrderKey(request);
String redisKey = "order:dedupe:" + orderDedupeKey;
// 2. Redis SETNX快速去重(第一层防护)
Boolean notExists = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "processing", 30, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(notExists)) {
// 检查是否已处理完成
String status = redisTemplate.opsForValue().get(redisKey);
if ("completed".equals(status)) {
// 返回已创建的订单(需从缓存或数据库查询)
return getExistingOrder(orderDedupeKey);
}
// 正在处理中,提示用户稍后重试
throw new BusinessException("订单创建中,请稍后查询");
}
// 3. 分布式锁保证串行化处理(防并发重复)
RLock lock = redissonClient.getLock("order:lock:" + orderDedupeKey);
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 4. 数据库幂等检查(第二层防护)
Order existing = orderDao.findByDedupeKey(orderDedupeKey);
if (existing != null) {
redisTemplate.opsForValue().set(redisKey, "completed", 300, TimeUnit.SECONDS);
return existing;
}
// 5. 创建订单(数据库事务)
Order order = doCreateOrderInTransaction(request, orderDedupeKey);
// 6. 更新Redis状态
redisTemplate.opsForValue().set(redisKey, "completed", 300, TimeUnit.SECONDS);
// 7. 异步扣减库存、发券等
sendOrderCreatedEvent(order);
return order;
} else {
throw new BusinessException("系统繁忙,请稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("系统异常");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private Order doCreateOrderInTransaction(OrderRequest request, String dedupeKey) {
// 使用数据库事务
return TransactionTemplate.execute(status -> {
// 插入订单(数据库唯一索引是第三层防护)
Order order = new Order();
order.setDedupeKey(dedupeKey);
// ... 设置其他字段
orderDao.insert(order);
// 扣减库存(乐观锁)
for (OrderItem item : request.getItems()) {
int rows = inventoryDao.deductStock(
item.getProductId(),
item.getQuantity(),
item.getExpectedVersion()
);
if (rows == 0) {
status.setRollbackOnly();
throw new InventoryNotEnoughException("库存不足");
}
}
return order;
});
}
}
性能优化策略:
@Configuration
public class RedisIdempotentConfig {
// 使用Redis集群提高可用性
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration config = new RedisClusterConfiguration();
config.addClusterNode(new RedisNode("redis1", 6379));
config.addClusterNode(new RedisNode("redis2", 6379));
config.setMaxRedirects(3);
return new JedisConnectionFactory(config);
}
// 本地缓存降低Redis压力
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000)
.recordStats());
return cacheManager;
}
}
优缺点分析:
text
优点:
1. 高性能,适合高并发场景
2. Redis响应快,用户体验好
3. 多层防护,可靠性较高
缺点:
1. 需要维护Redis高可用
2. 可能存在短暂数据不一致
3. 需要处理Redis故障降级
性能考量:
- Redis集群需要合理分片
- 热点数据需要本地缓存
- 需要监控Redis内存和QPS
3.3 消息队列消费场景
业务特点:异步处理、允许重试、需要保证最终一致性
方案三:消息队列原生支持 + 业务幂等
@Component
@RocketMQMessageListener(
topic = "${rocketmq.order.topic}",
consumerGroup = "${rocketmq.order.consumer-group}",
consumeMode = ConsumeMode.CONCURRENTLY,
messageModel = MessageModel.CLUSTERING
)
public class OrderMessageConsumer implements RocketMQListener<MessageExt> {
// 使用Guava LoadingCache做本地去重
private final LoadingCache<String, Boolean> processedMsgCache =
CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, Boolean>() {
@Override
public Boolean load(String key) {
return false;
}
});
@Override
public void onMessage(MessageExt message) {
String msgId = message.getMsgId();
String bizKey = message.getKeys(); // 业务唯一键
// 1. 本地缓存快速去重
if (processedMsgCache.getIfPresent(msgId) != null) {
log.info("消息已处理(本地缓存): {}", msgId);
return;
}
// 2. Redis分布式去重
String redisKey = "msg:dedupe:" + bizKey;
Boolean isNew = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "1", 24, TimeUnit.HOURS);
if (Boolean.FALSE.equals(isNew)) {
log.info("消息已处理(Redis): {}", bizKey);
return;
}
// 3. 处理消息(支持重试)
try {
processOrderMessage(message);
// 4. 更新本地缓存
processedMsgCache.put(msgId, true);
// 5. 异步更新数据库处理状态(最终一致性)
asyncUpdateProcessedStatus(bizKey);
} catch (Exception e) {
// 处理失败,删除Redis标记,允许重试
redisTemplate.delete(redisKey);
throw e;
}
}
private void processOrderMessage(MessageExt message) {
// 业务处理逻辑
OrderMessage orderMsg = JSON.parseObject(
message.getBody(),
OrderMessage.class);
// 数据库幂等检查(最终防线)
OrderProcessingRecord record = processingRecordDao
.findByBizKey(orderMsg.getOrderId());
if (record != null && record.getStatus() == ProcessStatus.SUCCESS) {
log.info("订单已处理完成: {}", orderMsg.getOrderId());
return;
}
// 执行业务逻辑
orderService.processOrder(orderMsg);
}
}
┌─────────────────────────────────────────────────────┐
│ 消息消费流程 │
├─────────────────────────────────────────────────────┤
│ 生产者 → RocketMQ Broker → 消费者(多实例并发消费) │
└─────────────────────────────────────────────────────┘
│
┌──────────┴──────────┐
│ 三层幂等防护 │
├─────────────────────┤
│ 1. 本地缓存去重 │ ← 快速过滤
│ 2. Redis分布式去重 │ ← 分布式一致性
│ 3. 数据库最终检查 │ ← 强一致性
└─────────────────────┘
原理1:消息ID的唯一性
// 消息结构
public class MessageExt {
private String msgId; // Broker生成的唯一ID(保证唯一)
private String keys; // 业务键(生产者指定)
private byte[] body; // 消息体
private int queueId; // 队列ID
private long queueOffset; // 队列偏移量
}
/**
* 消息ID生成原理:
* Broker在接收消息时生成唯一ID
* 格式:IP + Port + 时间戳 + 序列号
* 示例:0A0B0C0D0E0F 10987 1615449200000 1
*
* 保证:同一Broker内绝对不会生成相同的msgId
* 但:生产者重试发送时,会生成不同的msgId!
*/
原理2:本地缓存去重(第一层)
// Guava LoadingCache的工作原理
private final LoadingCache<String, Boolean> processedMsgCache =
CacheBuilder.newBuilder()
.maximumSize(10000) // 最大缓存条目数
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.build(new CacheLoader<String, Boolean>() {
@Override
public Boolean load(String key) {
return false; // 默认返回false,表示未处理
}
});
/**
* 实现机制:
* 1. 基于内存的ConcurrentHashMap实现
* 2. 使用LRU策略淘汰旧数据
* 3. 每个消费者实例独立维护自己的缓存
*
* 为什么有效?
* - 短时间内相同msgId的重复消息会被拦截
* - 极低延迟:内存操作,纳秒级别
*
* 局限性:
* - 只在本JVM内有效,多实例无效
* - 重启后缓存丢失
* - 内存限制,不能存储太多数据
*/
原理3:Redis分布式去重(第二层)
// Redis SETNX命令原理
Boolean isNew = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "1", 24, TimeUnit.HOURS);
/**
* SETNX (SET if Not eXists) 原子操作:
* 1. 检查key是否存在
* 2. 如果不存在,设置key-value,返回1
* 3. 如果存在,不做任何操作,返回0
*
* 原子性保证:Redis单线程执行,不会出现并发问题
*
* RedisKey设计:msg:dedupe:{bizKey}
* 示例:msg:dedupe:ORDER_202301010001
*
* 过期时间设计:
* - 24小时:覆盖消息最大重试周期
* - 防止Redis内存无限增长
*/
原理4:数据库最终检查(第三层)
// 数据库幂等表设计
CREATE TABLE order_processing_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
biz_key VARCHAR(128) NOT NULL COMMENT '业务唯一键',
status TINYINT NOT NULL DEFAULT 0 COMMENT '0-处理中,1-成功,2-失败',
result_data JSON COMMENT '处理结果',
created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_biz_key(biz_key) -- 唯一索引!
);
/**
* 为什么需要数据库检查?
* 1. Redis可能丢失数据(重启、内存淘汰)
* 2. 需要持久化存储处理结果
* 3. 支持数据查询和审计
*
* 唯一索引的作用:
* 数据库层面防止重复插入
* 即使所有缓存都失效,数据库也能保证幂等
*/
Kafka Exactly-Once实现
@Component
public class KafkaExactlyOnceProcessor {
@KafkaListener(topics = "payment-topic", groupId = "payment-group")
public void consumeWithExactlyOnce(ConsumerRecord<String, String> record) {
// Kafka的幂等生产者和事务支持
// 配置:enable.idempotence=true
// 业务处理逻辑
processPayment(record);
// 手动提交offset(保证至少一次)
// 需要业务层实现幂等
}
/**
* 使用Kafka事务实现端到端Exactly-Once
*/
@Transactional
public void processWithKafkaTransaction(String businessId, String message) {
// 1. 业务处理
businessService.process(businessId);
// 2. 发送结果消息(在同一事务中)
kafkaTemplate.send("result-topic", businessId, message);
// Kafka事务管理器会协调数据库和Kafka的事务
}
}
优缺点分析:
优点:
1. 天然支持重试机制
2. 可横向扩展消费者
3. 消息堆积不影响主业务
缺点:
1. 消息可能乱序到达
2. 需要处理重复消费
3. 系统复杂度较高
性能考量:
- 批量消费提高吞吐量
- 合理设置重试间隔
- 监控消费延迟和积压
四、综合对比与选型指南
4.1 方案对比矩阵
| 维度 | 数据库方案 | Redis方案 | 消息队列方案 |
|---|---|---|---|
| 性能 | 低(300-1000 TPS) | 高(3000-10000 TPS) | 中(1000-5000 TPS) |
| 一致性 | 强一致性 | 最终一致性 | 最终一致性 |
| 可靠性 | 极高 | 高(依赖Redis可用性) | 高 |
| 复杂度 | 低 | 中 | 高 |
| 适用场景 | 支付、金融交易 | 电商下单、秒杀 | 异步处理、数据同步 |
| 成本 | 数据库成本 | Redis内存成本 | 消息队列成本 |
| 延迟 | 低 | 极低 | 可能较高(异步) |
4.2 选型决策树
是否需要强一致性?
├── 是 → 数据库方案(支付、金融)
└── 否 →
├── QPS > 3000?
│ ├── 是 → Redis方案(电商、秒杀)
│ └── 否 →
│ ├── 是否需要解耦异步?
│ │ ├── 是 → 消息队列方案
│ │ └── 否 → Redis方案
│ └── 是否有严格顺序要求?
│ ├── 是 → 数据库方案 + 队列
│ └── 否 → Redis方案
└── 是否需要批量处理?
├── 是 → 消息队列方案
└── 否 → Redis方案
4.3 混合方案实践
java
/**
* 混合方案:根据业务重要性分级处理
* 1. 核心业务:数据库强一致性
* 2. 重要业务:Redis + 数据库
* 3. 普通业务:消息队列最终一致性
*/
public class HybridIdempotentProcessor {
public enum BizLevel {
CORE, // 支付、资金
IMPORTANT, // 订单、库存
NORMAL // 日志、通知
}
public <T> T processWithLevel(String bizKey,
BizLevel level,
Supplier<T> businessLogic) {
switch (level) {
case CORE:
return processWithDatabase(bizKey, businessLogic);
case IMPORTANT:
return processWithRedisAndDatabase(bizKey, businessLogic);
case NORMAL:
return processWithMessageQueue(bizKey, businessLogic);
default:
throw new IllegalArgumentException("未知的业务级别");
}
}
}
五、监控与运维要点
5.1 关键监控指标
java
@Component
public class IdempotentMetrics {
private final MeterRegistry meterRegistry;
// 重复请求率
private final Counter duplicateCounter;
// 处理时延分布
private final Timer processingTimer;
// 成功率
private final Counter successCounter;
private final Counter failureCounter;
public IdempotentMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
duplicateCounter = Counter.builder("idempotent.duplicate.total")
.description("重复请求总数")
.tag("type", "duplicate")
.register(meterRegistry);
processingTimer = Timer.builder("idempotent.processing.duration")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
}
public void recordDuplicate(String bizType) {
duplicateCounter.increment();
// 告警规则:重复率 > 1%
double duplicateRate = getDuplicateRate();
if (duplicateRate > 0.01) {
alertService.sendAlert("幂等重复率异常",
String.format("业务类型: %s, 重复率: %.2f%%",
bizType, duplicateRate * 100));
}
}
}
5.2 容灾与降级
@Component
public class IdempotentFallbackProcessor {
// 分级降级策略
public Object processWithFallback(String bizKey,
Supplier<Object> primaryLogic,
Supplier<Object> fallbackLogic) {
try {
// 尝试主方案
return primaryLogic.get();
} catch (RedisConnectionFailureException e) {
log.warn("Redis故障,降级到数据库方案");
// 降级到数据库方案
try {
return processWithDatabase(bizKey, primaryLogic);
} catch (Exception dbEx) {
log.error("数据库也故障,使用最终降级", dbEx);
// 最终降级:记录日志,人工介入
logEmergency(bizKey, e);
return fallbackLogic.get();
}
} catch (DatabaseConnectionException e) {
log.error("数据库故障,使用缓存方案");
// 使用本地缓存 + 异步补偿
return processWithLocalCache(bizKey, primaryLogic);
}
}
private void logEmergency(String bizKey, Exception e) {
// 记录到紧急日志,触发人工处理
emergencyLogDao.log(bizKey, "SYSTEM_FAILURE", e.getMessage());
// 发送告警
alertService.sendEmergencyAlert("幂等系统全面故障",
String.format("业务键: %s, 错误: %s", bizKey, e.getMessage()));
}
}
六、最佳实践总结
6.1 通用原则
-
分层防护:不要依赖单一方案,至少要有2-3层幂等防护
-
业务隔离:不同业务使用不同的幂等键策略
-
监控告警:实时监控重复率,设置合理阈值
-
定期清理:设置合理的过期时间,避免存储膨胀
6.2 实施步骤
-
业务分析:识别需要幂等的操作和业务重要性
-
方案设计:选择合适的技术组合
-
实现测试:编写单元测试和集成测试
-
压测验证:模拟重复请求验证幂等性
-
监控上线:上线后密切监控相关指标
-
迭代优化:根据实际运行情况持续优化
6.3 常见陷阱与规避
java
// 陷阱1:时间窗口内的重复(需要在业务层面解决)
public void processWithTimeWindow(String bizKey) {
// 错误:只在短时间内检查重复
// 正确:使用业务唯一标识,不受时间限制
}
// 陷阱2:分布式环境下的时钟不同步
public void processWithTimestamp() {
// 错误:依赖服务器时间戳
// 正确:使用逻辑时钟或版本号
}
// 陷阱3:清理策略导致的数据丢失
public void cleanupOldRecords() {
// 错误:过早清理未完成的记录
// 正确:只清理已完成的记录,保留失败记录供排查
}
通过深入理解不同方案的实现原理,结合具体业务场景的需求,可以选择最合适的幂等性保障方案,在性能和可靠性之间取得最佳平衡。