侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计高并发场景下的防重复提交与数据一致性解决方案

2025-12-12 / 0 评论 / 4 阅读

题目

设计高并发场景下的防重复提交与数据一致性解决方案

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

幂等性设计,分布式锁实现,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)