题目
设计高并发场景下的防重复提交与数据一致性解决方案
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
幂等性设计,分布式锁实现,Spring事务管理,拦截器应用,并发控制策略
快速回答
在高并发场景下防止重复提交和保证数据一致性,需要综合运用以下技术:
- 幂等性设计:通过唯一请求ID或Token机制确保操作只执行一次
- 分布式锁:使用Redis或ZooKeeper实现跨JVM的并发控制
- 事务管理:结合Spring的@Transactional注解保证数据操作的原子性
- 拦截器优化:在请求处理链早期拦截重复请求
- 限流降级:通过Guava RateLimiter或Sentinel控制并发流量
1. 问题背景与挑战
在高并发场景(如秒杀系统)中,网络延迟或用户重复点击可能导致重复提交,引发数据不一致(如库存超扣)、资金损失等严重问题。需要从请求层到数据层建立多层防护。
2. 核心解决方案
2.1 幂等性设计(关键基础)
原理:通过唯一标识(如requestId)确保同一操作多次执行结果一致。
实现方案:
- 客户端生成唯一UUID,服务端校验是否已存在
- 数据库唯一索引约束关键业务字段
- 状态机机制(如订单状态不可逆)
2.2 分布式锁实现
Redis实现示例(Redisson客户端):
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
@Transactional
public void createOrder(OrderRequest request) {
String lockKey = "order_lock:" + request.getRequestId();
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,等待5秒,锁有效期30秒
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
if (orderDao.existsByRequestId(request.getRequestId())) {
throw new DuplicateRequestException("重复请求");
}
// 核心业务逻辑
orderDao.save(buildOrder(request));
inventoryService.reduceStock(request.getSkuId());
}
} finally {
lock.unlock();
}
}
}注意事项:
- 锁粒度要细(如按用户ID+业务类型)
- 设置合理的锁超时时间,避免死锁
- 使用Lua脚本保证原子性操作
2.3 事务与并发控制
Spring事务管理:
@Transactional(isolation = Isolation.REPEATABLE_READ,
propagation = Propagation.REQUIRED,
rollbackFor = Exception.class)
public void processPayment(String orderId) {
// 1. 查询订单状态(REPEATABLE_READ保证可重复读)
// 2. 更新订单为已支付
// 3. 增加商户账户余额
}数据库优化:
- 使用乐观锁(版本号机制)
- 悲观锁(SELECT FOR UPDATE)
- UPDATE语句带条件检查(如:UPDATE stock SET count=count-1 WHERE count>0)
2.4 拦截器优化
自定义拦截器:
public class DuplicateSubmitInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String requestId = request.getHeader("X-Request-ID");
if (StringUtils.isEmpty(requestId)) {
throw new InvalidRequestException("缺少请求标识");
}
// 检查Redis中是否存在该requestId
if (redisTemplate.hasKey(requestId)) {
throw new DuplicateRequestException("请勿重复提交");
}
// 设置30秒过期
redisTemplate.opsForValue().set(requestId, "processing", 30, TimeUnit.SECONDS);
return true;
}
}3. 最佳实践
- 分层防护:前端按钮置灰 + 网络层拦截 + 服务层幂等 + 数据层约束
- 性能平衡:Redis锁比DB锁性能高10倍以上
- 错误处理:定义明确的异常码(如409 Conflict)
- 监控:记录重复请求日志并告警
4. 常见错误
- 未设置锁超时导致死锁
- 在事务提交前释放锁(可能读到脏数据)
- 忽略网络分区导致Redis锁失效(可考虑RedLock)
- 未处理解锁异常导致锁泄漏
5. 扩展知识
- Token Bucket算法:实现精确限流
- 分布式事务:Seata框架解决跨服务数据一致性问题
- CDC技术:通过Debezium监听数据库变更日志
- 热点数据处理:库存分桶(如100库存拆为10个key)