当前位置: 首页 > news >正文

Java项目实现幂等性方案总结

幂等性(Idempotence)是分布式系统和API设计中一个重要的概念,指的是对同一个操作执行一次或多次,其产生的结果是相同的。在Java项目中实现幂等性可以避免重复操作带来的问题,如重复支付、重复下单等。

一、幂等性基础概念

1. 幂等性场景

  • 用户重复点击提交按钮
  • 消息队列重复消费
  • 接口超时重试
  • 分布式服务调用失败重试

2. 需要幂等的操作

  • 创建操作(需防止重复创建)
  • 更新操作(需防止重复更新)
  • 删除操作(需防止重复删除)
  • 支付/交易类操作

二、常见幂等性实现方案

1. 唯一索引/主键约束

适用场景:防止重复插入数据

// 数据库表添加唯一约束
ALTER TABLE orders ADD UNIQUE KEY (order_no);

// Java代码
@Service
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        try {
            orderDao.insert(order);
        } catch (DuplicateKeyException e) {
            // 捕获唯一键冲突异常
            log.warn("重复订单: {}", order.getOrderNo());
            throw new BusinessException("订单已存在");
        }
    }
}

2. 乐观锁

适用场景:更新操作幂等

// 数据库表添加version字段
ALTER TABLE products ADD COLUMN version INT DEFAULT 0;

// Java代码
@Service
public class ProductService {
    @Transactional
    public void updateStock(Long productId, int quantity) {
        Product product = productDao.selectById(productId);
        int affected = productDao.updateStock(
            productId, quantity, product.getVersion());
        
        if (affected == 0) {
            throw new OptimisticLockException("更新失败,请重试");
        }
    }
}

// Mapper XML
<update id="updateStock">
    UPDATE products 
    SET stock = stock - #{quantity}, 
        version = version + 1 
    WHERE id = #{productId} 
    AND version = #{version}
</update>

3. 分布式锁

适用场景:分布式环境下的幂等控制

@Service
public class PaymentService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void makePayment(String orderNo, BigDecimal amount) {
        String lockKey = "payment:" + orderNo;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁,等待5秒,锁有效期30秒
            boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException("系统繁忙,请稍后再试");
            }
            
            // 检查是否已处理过
            if (paymentDao.existsByOrderNo(orderNo)) {
                return; // 已处理,直接返回
            }
            
            // 执行业务逻辑
            processPayment(orderNo, amount);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("支付处理被中断");
        } finally {
            lock.unlock();
        }
    }
}

4. Token机制

适用场景:防止表单重复提交

@RestController
public class OrderController {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @GetMapping("/order/token")
    public String generateToken() {
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("order:token:" + token, "1", 5, TimeUnit.MINUTES);
        return token;
    }
    
    @PostMapping("/order/create")
    public ResponseEntity<?> createOrder(@RequestParam String token, Order order) {
        String key = "order:token:" + token;
        Boolean deleted = redisTemplate.delete(key);
        
        if (Boolean.FALSE.equals(deleted)) {
            return ResponseEntity.badRequest().body("无效或已使用的token");
        }
        
        orderService.createOrder(order);
        return ResponseEntity.ok().build();
    }
}

5. 状态机

适用场景:有状态流转的业务

@Service
public class OrderService {
    
    @Transactional
    public void cancelOrder(Long orderId) {
        Order order = orderDao.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
            
        if (order.getStatus() == OrderStatus.CANCELLED) {
            return; // 已经是取消状态,直接返回
        }
        
        if (order.getStatus() != OrderStatus.PAID) {
            throw new BusinessException("当前状态不能取消订单");
        }
        
        order.setStatus(OrderStatus.CANCELLED);
        orderDao.update(order);
    }
}

6. 消息队列幂等

适用场景:消息队列消费幂等

@Component
@RabbitListener(queues = "order.queue")
public class OrderMessageListener {
    
    @Autowired
    private OrderDao orderDao;
    
    @RabbitHandler
    public void process(@Payload OrderMessage message, 
                       @Headers Map<String, Object> headers) {
        String messageId = (String) headers.get("message_id");
        
        // 检查是否已处理过该消息
        if (orderDao.existsByMessageId(messageId)) {
            return;
        }
        
        // 处理订单
        Order order = convertToOrder(message);
        order.setMessageId(messageId);
        orderDao.insert(order);
    }
}

三、分布式系统幂等方案

1. 全局唯一ID + 去重表

@Service
public class PaymentService {
    
    @Autowired
    private DistributedIdGenerator idGenerator;
    
    @Transactional
    public void processPayment(PaymentRequest request) {
        // 生成唯一业务ID
        String businessId = "pay_" + idGenerator.nextId();
        
        // 检查是否已处理
        if (paymentDao.existsByBusinessId(businessId)) {
            return;
        }
        
        // 记录处理标记
        paymentDao.insertProcessingRecord(businessId);
        
        try {
            // 执行业务逻辑
            doPayment(request);
            
            // 更新状态为成功
            paymentDao.updateStatus(businessId, "SUCCESS");
        } catch (Exception e) {
            // 更新状态为失败
            paymentDao.updateStatus(businessId, "FAILED");
            throw e;
        }
    }
}

2. 基于Redis的幂等控制

@Service
public class IdempotentService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean checkAndSet(String idempotentKey, long expireSeconds) {
        // setIfAbsent = SETNX + EXPIRE
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(idempotentKey, "1", expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }
    
    public void release(String idempotentKey) {
        redisTemplate.delete(idempotentKey);
    }
}

// 使用示例
@RestController
public class ApiController {
    
    @Autowired
    private IdempotentService idempotentService;
    
    @PostMapping("/api/do-something")
    public ResponseEntity<?> doSomething(@RequestHeader("X-Request-Id") String requestId) {
        if (!idempotentService.checkAndSet("idempotent:" + requestId, 3600)) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                .body("请求正在处理或已处理完成");
        }
        
        try {
            // 执行业务逻辑
            return ResponseEntity.ok().build();
        } finally {
            idempotentService.release("idempotent:" + requestId);
        }
    }
}

四、最佳实践建议

  1. 合理选择方案:根据业务场景选择最适合的幂等方案
  2. 客户端配合:前端应防止重复提交(如按钮禁用)
  3. 日志记录:关键操作记录详细日志以便排查问题
  4. 过期机制:设置合理的过期时间,避免存储无限增长
  5. 性能考虑:幂等控制不应成为系统瓶颈
  6. 异常处理:设计良好的异常处理机制
  7. 测试验证:充分测试幂等逻辑,特别是并发场景

五、常见问题解决方案

1. 网络超时问题

  • 客户端超时后应查询结果而不是直接重试
  • 服务端应提供查询接口

2. 并发问题

  • 使用分布式锁控制并发
  • 数据库使用乐观锁/悲观锁

3. 分布式环境一致性问题

  • 考虑使用分布式事务或最终一致性方案
  • 引入消息队列实现异步处理

通过合理选择和组合上述方案,可以在Java项目中有效实现各种场景下的幂等性需求,提高系统的健壮性和可靠性。

http://www.dtcms.com/a/98352.html

相关文章:

  • CSS3学习教程,从入门到精通, CSS3 盒子模型的详细语法知识点及案例代码(23)
  • Spring AI MCP 架构详解
  • [笔记.AI]向量化
  • Linux系统修改网卡名为eth0、eth1
  • 【CSS】相对位置小练习
  • SQL Server 备份相关信息查看
  • http与tcp的关系
  • 自动化发布工具CI/CD实践Jenkins常用工具和插件的使用
  • 二分查找【看了包会】
  • 线程概念与控制(中)
  • 深度剖析 ansible:从部署基础到模块运用及剧本编写
  • 【算法day25】 最长有效括号——给你一个只包含 ‘(‘ 和 ‘)‘ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
  • [Raspberry Pi]如何將看門狗(WatchDog)服務建置在樹莓派的Ubuntu作業系統中?
  • 查看openjdk源码
  • TDengine 中的异常恢复
  • 北斗导航 | 基于因子图优化的GNSS/INS组合导航完好性监测算法研究,附matlab代码
  • pyinstaller 对 pyexecjs模块打包老会有终端框闪烁
  • 【学Rust写CAD】18 定点数2D仿射变换矩阵结构体(MatrixFixedPoint结构别名)
  • 基于深度学习的手势识别系统设计
  • 3. 第三放平台部署deepseek
  • 部署堆叠+链路聚合架构,解锁网络高可用新体验
  • AGV-----RCS基础任务发布
  • 22_js运动函数
  • 历史数据分析——宝钢
  • AI赋能单片机开发的环节与方法
  • 观察者模式:解耦对象间的依赖关系
  • 【嵌入式学习3】多任务编程
  • (二)万字长文解析:deepResearch如何用更长的思考时间换取更高质量的回复?各家产品对比深度详解
  • 锐评|希捷NVMe闪存+磁盘混合存储阵列
  • AB包介绍及导出工具实现+AB包资源简单加载