重复执行限制技术解读
2025/12/3大约 4 分钟
重复执行限制技术解读
1. 技术概述
重复执行限制技术用于防止在分布式系统中由于网络延迟、用户重复点击等原因导致的重复操作问题,是保证系统数据一致性和业务正确性的重要手段。
2. 应用场景
- 订单创建:防止重复下单导致创建多个相同订单
- 支付处理:防止重复支付导致多扣用户资金
- 库存扣减:防止重复扣减导致库存不一致
- 优惠券领取:防止重复领取导致超发
- 积分兑换:防止重复兑换导致积分异常
- 批量操作接口:防止重复提交导致业务重复处理
3. 核心技术方案
3.1 分布式锁实现
通过Redis分布式锁实现操作的互斥性:
@Service
public class OrderService {
@Autowired
private ServiceLocker serviceLocker;
public Order createOrder(OrderRequest request) {
String lockKey = "order:create:" + request.getUserId() + ":" + request.getProgramId();
try {
if (serviceLocker.lock(lockKey, 5, TimeUnit.SECONDS)) {
// 检查是否已存在相同订单
if (orderExist(request)) {
throw new BusinessException("订单已存在");
}
// 创建订单逻辑
return createNewOrder(request);
} else {
throw new BusinessException("操作过于频繁,请稍后重试");
}
} finally {
serviceLocker.unlock(lockKey);
}
}
}3.2 Redis幂等性处理
利用Redis的SETNX命令实现接口幂等性:
@Component
public class IdempotentUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 检查并设置幂等性标识
* @param key 幂等性key
* @param expireTime 过期时间
* @param timeUnit 时间单位
* @return 是否可以执行操作
*/
public boolean checkAndSetIdempotentKey(String key, long expireTime, TimeUnit timeUnit) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", expireTime, timeUnit);
return Boolean.TRUE.equals(success);
}
/**
* 移除幂等性标识
* @param key 幂等性key
*/
public void removeIdempotentKey(String key) {
redisTemplate.delete(key);
}
}3.3 数据库唯一索引
通过数据库唯一索引防止重复数据插入:
-- 订单表唯一索引示例
ALTER TABLE `order` ADD UNIQUE INDEX `idx_user_program` (`user_id`, `program_id`, `show_time_id`);
-- 优惠券领取记录唯一索引示例
ALTER TABLE `coupon_receive` ADD UNIQUE INDEX `idx_user_coupon` (`user_id`, `coupon_id`);3.4 消息队列去重
通过消息队列的幂等性设计防止消息重复消费:
@Service
public class OrderMessageConsumer {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private OrderService orderService;
@KafkaListener(topics = "order-create-topic")
public void handleOrderCreateMessage(OrderMessage message) {
String messageId = message.getMessageId();
String deduplicationKey = "msg:dedup:" + messageId;
// 幂等性处理
Boolean success = redisTemplate.opsForValue().setIfAbsent(deduplicationKey, "1", 24, TimeUnit.HOURS);
if (Boolean.TRUE.equals(success)) {
// 处理消息
orderService.processOrderCreate(message);
} else {
// 消息已处理过,记录日志
log.info("消息已处理,messageId: {}", messageId);
}
}
}4. 防重复提交最佳实践
4.1 前端防重复提交
- 按钮防抖:点击后禁用按钮,防止快速重复点击
- Token机制:每次请求携带唯一Token,服务器验证后失效
- 请求合并:短时间内相同请求合并处理
4.2 后端防重复提交方案
4.2.1 基于注解的防重复提交
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeatSubmit {
/**
* 过期时间,默认1秒
*/
long expire() default 1;
/**
* 时间单位,默认秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 错误提示消息
*/
String message() default "请勿重复提交";
}4.2.2 切面实现
@Aspect
@Component
public class NoRepeatSubmitAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(noRepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("Authorization");
String method = request.getMethod();
String requestURI = request.getRequestURI();
// 构建唯一标识
String key = "repeat:submit:" + token + ":" + method + ":" + requestURI;
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", noRepeatSubmit.expire(), noRepeatSubmit.timeUnit());
if (Boolean.TRUE.equals(success)) {
try {
return joinPoint.proceed();
} finally {
// 根据业务需求决定是否删除标识,对于短时间内确实需要再次提交的接口可以删除
// redisTemplate.delete(key);
}
} else {
throw new BusinessException(noRepeatSubmit.message());
}
}
}4.3 使用示例
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
@NoRepeatSubmit(expire = 3, message = "提交过于频繁,请稍后再试")
public Result<OrderDTO> createOrder(@RequestBody OrderRequest request) {
OrderDTO order = orderService.createOrder(request);
return Result.success(order);
}
}5. 最佳实践
- 结合多种方案:根据业务特点选择合适的技术方案,或结合多种方案使用
- 设置合理的过期时间:避免影响正常操作,同时防止资源浪费
- 添加监控告警:监控重复操作情况,及时发现异常
- 提供友好的用户提示:对于重复提交的请求,返回明确的错误信息
- 设计补偿机制:对于已发生的重复操作,提供补偿处理方案
- 定期清理数据:定期清理Redis中的幂等性数据,避免占用过多内存
- 分布式一致性:在分布式环境中,确保防重复机制的一致性