侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

高并发场景下如何设计Spring MVC接口防止重复提交并保证数据一致性

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

题目

高并发场景下如何设计Spring MVC接口防止重复提交并保证数据一致性

信息

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

考点

并发控制,事务管理,分布式锁,幂等性设计,Spring MVC拦截器

快速回答

在高并发场景下防止重复提交和保证数据一致性,需要综合运用以下技术:

  • 幂等性设计:通过唯一标识(如token)确保同一操作只执行一次
  • 并发控制:使用分布式锁(如Redis/Redisson)控制并发访问
  • 事务管理:利用Spring事务保证数据操作的原子性
  • 前端拦截:通过按钮置灰等方式减少重复请求
  • 后端拦截:使用拦截器或AOP进行重复请求过滤
## 解析

1. 问题背景与挑战

在高并发场景(如秒杀、支付)中,重复提交会导致:
1) 数据不一致(如库存超卖)
2) 重复创建资源
3) 业务逻辑错误。需要从前端网络层服务端多层防御。

2. 核心解决方案

2.1 幂等性设计(关键)

实现方案:

  • Token机制
    @RestController
    public class TokenController {
        @GetMapping("/token")
        public String generateToken() {
            String token = UUID.randomUUID().toString();
            // 存入Redis并设置过期时间
            redisTemplate.opsForValue().set(token, "1", 5, TimeUnit.MINUTES);
            return token;
        }
    }
  • 业务唯一键:如订单ID+业务类型组合唯一索引

2.2 分布式锁实现

使用Redisson实现分布式锁:

@Service
public class OrderService {
    @Autowired
    private RedissonClient redissonClient;

    @Transactional
    public void createOrder(Order order) {
        String lockKey = "order_lock_" + order.getOrderId();
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试加锁,最多等待100ms,锁持有10秒
            if (lock.tryLock(100, 10000, TimeUnit.MILLISECONDS)) {
                // 1. 检查幂等token是否有效
                // 2. 执行业务逻辑
                // 3. 删除token
            }
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

2.3 Spring MVC拦截器实现

自定义重复提交拦截器:

public class RepeatSubmitInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) {

        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            RepeatSubmit annotation = method.getMethodAnnotation(RepeatSubmit.class);

            if (annotation != null) {
                String token = request.getHeader("X-Submit-Token");
                // 检查Redis中token是否存在
                if (!redisTemplate.hasKey(token)) {
                    throw new RepeatSubmitException("重复提交");
                }
                // 删除token
                redisTemplate.delete(token);
            }
        }
        return true;
    }
}

3. 事务管理关键点

  • 事务范围:锁必须在事务外部获取(避免锁释放后事务未提交)
  • 隔离级别:使用@Transactional(isolation = Isolation.SERIALIZABLE)处理幻读
  • 传播行为PROPAGATION_REQUIRES_NEW确保关键操作独立事务

4. 最佳实践

  • 分层防御:前端按钮置灰 + 网络层Nginx限流 + 服务端幂等
  • 锁超时设置:根据业务合理设置(太短导致业务中断,太长降低并发)
  • 降级策略:锁竞争激烈时返回"系统繁忙"提示
  • 监控:监控Redis锁竞争情况和事务回滚率

5. 常见错误

  • 错误1:在事务内部加锁(导致锁提前释放)
  • 错误2:忽略网络超时导致的重复请求
  • 错误3:使用本地锁代替分布式锁
  • 错误4:未处理锁释放异常(可能死锁)

6. 扩展知识

  • 数据库层面:乐观锁(version字段)、唯一约束、SELECT FOR UPDATE
  • 消息队列:通过Kafka等消息队列保证最终一致性
  • Token管理优化:使用JWT包含时间戳和业务ID
  • 限流组件:结合Sentinel实现QPS控制