Spring Boot中幂等性的应用

Spring Boot 中,幂等性是实现分布式系统设计和接口调用的一个重要概念,尤其在高并发、分布式环境下,确保接口重复调用不会引发系统数据异常至关重要。

幂等性概念

幂等性(Idempotence)是指一次请求和重复多次请求对系统的影响完全相同。在接口调用中,如果一个接口满足幂等性,那么无论调用多少次,最终结果是一样的。

场景分析

  1. 支付系统
    防止重复支付。例如用户多次点击支付按钮,导致重复扣款。
  2. 订单创建
    防止用户重复下单,产生多个相同订单。
  3. 短信发送
    防止重复发送短信,避免浪费资源。
  4. 库存扣减
    防止并发扣减库存,导致库存不足或超卖。
  5. 分布式任务处理
    防止任务重复执行,保证最终一致性。

如何实现幂等性

在 Spring Boot 中,常用以下几种方法实现幂等性:

1.基于数据库唯一约束

原理:

利用数据库的唯一约束机制,确保同一请求只能操作一次。

实现:

  • 在数据库表中增加一个唯一字段(如订单号、请求 ID)。
  • 插入数据时,利用唯一约束防止重复写入。

代码示例:

java 复制代码
@Entity
@Table(name = "orders", uniqueConstraints = {@UniqueConstraint(columnNames = "orderId")})
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String orderId; // 唯一订单号
    
    @Column(nullable = false)
    private BigDecimal amount;

}

当重复提交时,数据库会抛出 DuplicateKeyException,可以捕获并返回提示。

2.基于唯一 Token 实现

原理:

  • 每次请求都需要携带唯一的 token,服务器校验 token 是否已使用。
  • 若已使用,则拒绝请求。

实现步骤:

1.客户端向服务器申请唯一 token(如 UUID)。

2.在请求时携带 token。

3.服务端验证 token:

  • 若 token 未使用,处理业务并标记 token 为已使用。
  • 若 token 已使用,直接返回提示。

代码示例:

java 复制代码
@RestController
@RequestMapping("/api/order")
public class OrderController {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @PostMapping("/create")
    public String createOrder(@RequestParam String token) {
        // 校验 token 是否已存在
        Boolean isTokenExists = redisTemplate.opsForValue().setIfAbsent(token, "1", 10, TimeUnit.MINUTES);
        if (Boolean.FALSE.equals(isTokenExists)) {
            return "重复请求,请勿再次提交";
        }
    
        // 执行业务逻辑
        // ...
    
        return "订单创建成功";
    }

}

优点:

  • 无需修改数据库结构。
  • 使用 Redis 提高性能,适用于高并发场景。

3.基于幂等字段校验

原理:

  • 接口请求体中包含幂等字段(如订单号、请求 ID)。
  • 服务端通过幂等字段判断请求是否已处理。

实现步骤:

  • 在业务表中增加 requestId 字段,标记唯一请求。
  • 每次请求前查询是否存在相同的 requestId。
  • 若存在,直接返回处理结果。

代码示例:

java 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;
    
    public String createOrder(String requestId, Order order) {
        // 校验幂等字段
        if (orderRepository.existsByRequestId(requestId)) {
            return "订单已创建,请勿重复提交";
        }
    
        // 保存订单
        order.setRequestId(requestId);
        orderRepository.save(order);
        return "订单创建成功";
    }

}

4.基于分布式锁

原理:

  • 利用分布式锁(如 Redis 的 SETNX)对关键资源加锁,确保同一时刻只有一个请求处理。

实现步骤:

  1. 请求时加锁,锁的唯一标识为幂等字段(如订单号)。
  2. 若加锁成功,执行业务逻辑。
  3. 业务执行完成后释放锁。

代码示例:

java 复制代码
@Service
public class SmsService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public String sendSms(String phoneNumber) {
        String lockKey = "sms:lock:" + phoneNumber;
    
        // 加锁
        Boolean isLockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 2, TimeUnit.MINUTES);
        if (Boolean.FALSE.equals(isLockAcquired)) {
            return "短信发送过于频繁,请稍后再试";
        }
    
        try {
            // 执行业务逻辑
            // ...
            return "短信发送成功";
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }

}

5.基于状态校验

原理:

  • 根据业务状态判断请求是否重复。
  • 常用于支付、库存等有明确状态的场景。

实现步骤:

  • 增加状态字段(如订单状态、支付状态)。
  • 请求前校验状态是否已完成。

代码示例:

java 复制代码
@Service
public class PaymentService {

    @Autowired
    private OrderRepository orderRepository;
    
    public String payOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("订单不存在"));
    
        // 校验状态
        if (order.getStatus().equals("PAID")) {
            return "订单已支付,请勿重复操作";
        }
    
        // 修改订单状态
        order.setStatus("PAID");
        orderRepository.save(order);
    
        return "支付成功";
    }

}

幂等性设计注意事项

1.选择合适的幂等方案

  • 数据库唯一约束适合低并发场景。
  • Redis 分布式锁适合高并发场景。
  • 幂等字段校验适合需要记录请求 ID 的场景。

2.幂等字段的设计

  • 幂等字段应具有唯一性,如订单号、请求 ID。
  • 客户端生成或服务端分配均可。

3.幂等性与事务

  • 确保幂等校验与业务逻辑在同一事务中执行,避免校验通过但业务未执行完成的情况。

4.性能优化

  • 使用缓存(如 Redis)提高幂等校验性能,减少数据库压力。

总结

Spring Boot 中的幂等性实现,是确保接口安全性和数据一致性的关键。根据业务场景的不同,选择合适的幂等方案至关重要:

  • 数据库唯一约束:简单场景,直接使用。
  • Redis 分布式锁:高并发场景,提升性能。
  • 幂等字段校验:需要记录唯一请求的场景。

幂等性设计不仅是接口安全的保障,更是系统稳定性的核心体现。

相关推荐
Chen-Edward13 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
magic3341656313 小时前
Springboot整合MinIO文件服务(windows版本)
windows·spring boot·后端·minio·文件对象存储
开心-开心急了13 小时前
Flask入门教程——李辉 第一、二章关键知识梳理(更新一次)
后端·python·flask
掘金码甲哥13 小时前
调试grpc的哼哈二将,你值得拥有
后端
陈小桔13 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!13 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg367814 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July14 小时前
Hikari连接池
java
微风粼粼14 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad14 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud