侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

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

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

题目

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

信息

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

考点

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

快速回答

在高并发场景下设计防重复提交且保证数据一致性的接口需要综合以下方案:

  • 采用幂等性设计:通过唯一业务ID或Token机制识别重复请求
  • 使用分布式锁:如Redis RedLock或ZooKeeper实现跨JVM互斥
  • 实现事务隔离:结合@Transactional与数据库隔离级别控制
  • 添加拦截器层:自定义HandlerInterceptor前置过滤重复请求
  • 设置限流熔断:通过Resilience4j或Sentinel保护系统
## 解析

1. 核心挑战与解决思路

在高并发场景下需同时解决:
重复提交(网络抖动导致客户端重试)
数据竞争(多线程同时修改共享资源)
系统过载(突发流量冲击)

2. 完整解决方案

2.1 幂等性设计(关键基础)

// 接口定义示例
@PostMapping("/order")
public ResponseEntity<String> createOrder(
    @RequestHeader("X-Idempotency-Key") String idempotencyKey, 
    @RequestBody OrderRequest request) {

    // 检查Redis中是否存在该Key
    if (redisTemplate.hasKey(idempotencyKey)) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body("Duplicate request");
    }

    // 执行业务逻辑...
}

实现要点

  • 客户端生成全局唯一ID(UUID或雪花算法)
  • 服务端在Redis记录ID+状态(设置TTL自动过期)
  • 采用HTTP 409 Conflict状态码返回重复请求

2.2 分布式锁实现(Redisson方案)

// 订单服务方法
@Transactional
public void processOrder(Order order) {
    RLock lock = redissonClient.getLock("ORDER_" + order.getId());
    try {
        // 尝试加锁(等待500ms,锁有效期30s)
        if (lock.tryLock(500, 30000, TimeUnit.MILLISECONDS)) {
            // 检查订单状态(防并发重复处理)
            Order current = orderRepository.findById(order.getId());
            if (current.getStatus() != Status.NEW) {
                throw new IllegalStateException("Order already processed");
            }

            // 扣减库存(悲观锁保证原子性)
            inventoryRepository.decrementStock(
                order.getProductId(), 
                order.getQuantity(), 
                LockModeType.PESSIMISTIC_WRITE);

            // 更新订单状态
            orderRepository.updateStatus(order.getId(), Status.COMPLETED);
        }
    } finally {
        lock.unlock();
    }
}

关键配置

  • Redisson的watchDog机制自动续期锁
  • 结合数据库行级锁(SELECT ... FOR UPDATE)
  • 锁粒度控制到业务ID级别

2.3 拦截器层优化

// 自定义幂等拦截器
public class IdempotencyInterceptor implements HandlerInterceptor {

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

        String idempotencyKey = request.getHeader("X-Idempotency-Key");
        if (StringUtils.isEmpty(idempotencyKey)) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            return false;
        }

        // Redis原子操作判断重复
        Boolean isNew = redisTemplate.opsForValue()
            .setIfAbsent(idempotencyKey, "PROCESSING", 5, TimeUnit.MINUTES);

        if (Boolean.FALSE.equals(isNew)) {
            response.setStatus(HttpStatus.CONFLICT.value());
            return false;
        }
        return true;
    }
}

2.4 事务与隔离级别配置

// Service层事务配置
@Transactional(
    isolation = Isolation.REPEATABLE_READ,  // 解决不可重复读
    propagation = Propagation.REQUIRED,
    rollbackFor = Exception.class
)
public void createOrder(Order order) {
    // 业务操作...
}

隔离级别选择

  • REPEATABLE_READ(MySQL默认):防止脏读和不可重复读
  • SERIALIZABLE:完全隔离但性能差,慎用

3. 最佳实践

  • 分层防护
    前端防抖(2s内禁用按钮) → Nginx限流(漏桶算法) → 服务端幂等
  • 锁超时设置
    业务最大耗时 * 2 + 缓冲时间(避免死锁)
  • 补偿机制
    对已处理请求直接返回缓存结果(Redis存储响应)

4. 常见错误

  • 锁粒度过大
    错误:锁定整个订单表 → 正确:锁定单个订单ID
  • 未处理锁释放
    未在finally块释放锁导致死锁
  • 双重检查缺失
    仅依赖分布式锁未在事务内二次校验状态

5. 扩展知识

  • 令牌桶限流
    Guava RateLimiter或Redis-Cell实现突发流量控制
  • 最终一致性方案
    对账任务补偿+消息队列(RabbitMQ死信队列)
  • 数据库优化
    乐观锁(version字段)+ 异步更新