侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

设计高并发场景下的Spring MVC接口,实现防重提交与分布式幂等性

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

题目

设计高并发场景下的Spring MVC接口,实现防重提交与分布式幂等性

信息

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

考点

Spring MVC请求处理,分布式锁实现,幂等性设计,防重提交机制,高并发优化

快速回答

在高并发场景下实现防重提交和接口幂等性需要综合运用多种技术:

  • 防重提交:采用Token机制,前端提交携带唯一Token,后端验证后立即失效
  • 幂等性保障:基于业务唯一标识(如订单ID)配合分布式锁实现
  • 分布式锁:使用Redis实现原子锁操作,设置合理的锁超时时间
  • 高并发优化:结合异步处理、数据库乐观锁和限流机制
  • 错误处理:定义清晰的错误码体系,区分重复提交和业务异常
## 解析

1. 核心原理说明

防重提交:防止用户短时间内重复提交相同数据。核心是通过Token机制实现:

  • 页面加载时生成唯一Token存储到Redis并返回前端
  • 提交时携带Token,后端验证存在后立即删除
  • Token缺失或重复视为非法请求

幂等性设计:确保多次请求产生相同结果:

  • 基于业务唯一键(如订单ID+操作类型)创建防重表
  • 使用数据库唯一索引或Redis原子操作保证唯一性
  • 结合状态机验证业务状态流转有效性

2. 完整代码实现

防重Token生成接口:

@RestController
public class TokenController {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/token")
    public String generateToken(@RequestParam String bizType) {
        String token = UUID.randomUUID().toString();
        String key = "ANTI_REPEAT:" + bizType + ":" + token;
        // 设置30秒有效期
        redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.SECONDS);
        return token;
    }
}

幂等接口实现(使用分布式锁):

@RestController
public class OrderController {
    @Autowired
    private RedissonClient redissonClient;

    @PostMapping("/pay")
    public ResponseEntity<?> payOrder(
            @RequestHeader("X-Request-Token") String token,
            @RequestBody PaymentRequest request) {

        // 1. 防重提交验证
        String tokenKey = "ANTI_REPEAT:PAYMENT:" + token;
        if (!redisTemplate.delete(tokenKey)) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                .body(new ErrorResponse("REPEAT_SUBMIT", "重复提交"));
        }

        // 2. 获取分布式锁
        String lockKey = "IDEMPOTENT_LOCK:" + request.getOrderId();
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 等待锁时间0,持有锁5秒
            if (!lock.tryLock(0, 5, TimeUnit.SECONDS)) {
                return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                    .body(new ErrorResponse("OPERATION_IN_PROGRESS", "处理中"));
            }

            // 3. 幂等性检查
            if (orderService.isPaid(request.getOrderId())) {
                return ResponseEntity.ok(new PaymentResponse("ALREADY_PAID", 0));
            }

            // 4. 业务处理(带乐观锁)
            boolean success = orderService.processPayment(
                request.getOrderId(), 
                request.getAmount()
            );

            return success ? 
                ResponseEntity.ok(new PaymentResponse("SUCCESS", request.getAmount())) :
                ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(new ErrorResponse("PAYMENT_FAILED", "支付失败"));

        } finally {
            lock.unlock();
        }
    }
}

3. 最佳实践

  • 分层验证:前端按钮防重 → 网络层Token验证 → 业务层幂等检查
  • 锁优化
    • 锁粒度:基于业务ID而非全局锁
    • 锁时长:根据业务复杂度设置,添加看门狗自动续期
    • 锁释放:finally块确保释放,避免死锁
  • 数据库设计
    • 防重表:创建(order_id, operation_type)唯一索引
    • 乐观锁:使用version字段控制并发更新
  • 降级方案:Redis不可用时降级到数据库唯一约束

4. 常见错误与规避

  • Token未失效:验证后必须立即删除Token(使用Redis DEL原子操作)
  • 锁超时设置不当
    • 过短:业务未完成锁释放导致并发问题
    • 过长:系统故障时长时间阻塞
    • 方案:设置自动续期机制(如Redisson看门狗)
  • ABA问题:订单状态从已支付→退款→待支付时,需通过版本号或时间戳校验
  • 网络超时重试:客户端应使用相同Token重试,而非生成新Token

5. 高并发优化策略

  • 异步化:支付核心逻辑放入MQ,快速释放Tomcat线程
  • 限流:在API网关层配置令牌桶限流(如Redis+Lua)
  • 缓存优化
    • 幂等结果缓存:将已处理结果缓存5-10秒
    • 布隆过滤器:快速过滤无效订单ID
  • 数据库优化
    • 热点账户:使用账户分段减少锁竞争
    • 批量提交:合并短时间内的相同操作

6. 扩展知识

  • 分布式锁选型:Redis(AP)/ZooKeeper(CP)/ETCD根据场景选择
  • Token安全:JWT签名防止客户端篡改Token
  • 状态机引擎:使用Spring StateMachine规范状态流转
  • 监控指标:埋点统计重复请求率、锁等待时间等关键指标