幂等性设计方案通常在分布式系统中,常见的幂等性设计方案如下:


1、唯一性约束

利用数据库的唯一性约束,如唯一索引或主键,来避免插入重复数据。


mysql> INSERT INTO `mydb`.`orders` (`order_id`, `user_id`, `product_id`, `quantity`, `order_status`, `create_time`, `pay_time`, `version`) VALUES ('ORD-20231023-0001', 'USR-A123456', 'PRD-X123', 2, 0, '2023-10-23 10:15:30', NULL, 1);

ERROR 1062 (23000): Duplicate entry 'ORD-20231023-0001' for key 'orders.PRIMARY'

注意:业务上要求生成全局唯一的主键。且不是自增策略,否则在分库分表的场景下,不同的表之间主键互不关联。


2. 乐观锁

 通过记录数据的版本号或时间戳,仅当数据未被其他事务修改时,才允许更新操作执行。每次更新数据时,版本号都会递增。


UPDATE orders

SET

 quantity = 1,

 order_status = 1,

 pay_time = '2024-04-30 10:20:00',

 version = version + 1

WHERE

 order_id = 'ORD-20231023-0001' AND

 version = 1;

效果演示:




如果 Session-01 已经提交了事务,Session-02 的更新操作将不会影响任何行,因为 version 已经从 1 增加到了 2。


3. 悲观锁

 使用悲观锁,事务在读取数据时会锁定相应的数据行,直到事务结束(提交或回滚)。这可以防止其他事务在锁定期间修改这些数据,从而确保数据的一致性。


在执行读取操作时,使用 SELECT ... FOR UPDATE 语句来锁定相关记录。


-- 锁定记录

SELECT * FROM orders WHERE order_id = 'ORD-20231023-0001' FOR UPDATE;

-- 执行业务逻辑

UPDATE orders SET quantity = 1, order_status = 1, pay_time = '2023-10-23 10:20:00' WHERE order_id = 'ORD-20231025-0003';

效果演示:




 由此可见,悲观锁确保每个事务也能安全地执行,而不会导致数据不一致的问题。但是,悲观锁可能会因为锁定机制而导致 性能问题 ,尤其是在高并发的系统中,这可能会引起 锁争用和死锁 。


4. 分布式锁

在分布式系统中,使用分布式锁来保证同一时间只有一个实例处理特定消息或请求。




当前使用redis分布式锁案例实现,


public class MyService {

   private final RedisDistributedLock lock;

   public MyService(Jedis jedis, String lockKey, int lockTimeout) {

       this.lock = new RedisDistributedLock(jedis, lockKey, lockTimeout);

   }

   public void executeInLock() {

       if (lock.tryLock()) {

           try {

               // 执行业务逻辑

           } finally {

               lock.unlock();

           }

       } else {

           // 处理无法获取锁的情况,例如重试或记录日志

       }

   }

}


这里顺便提一句,建议采用Lua脚本实现删除锁的逻辑,保证原子性。


public void unlock() {

       // 释放锁,使用Lua脚本来确保原子性

       String unlockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +

                             "return redis.call('del', KEYS[1]) " +

                             "else " +

                             "return 0 " +

                             "end";

       jedis.eval(unlockScript, 1, lockKey, "1");