muitLock和redLock

muitLock和redLock
1. 不加锁会超扣问题演示// 伪代码两个线程同时转账都基于旧余额判断classAccount{intbalance;}voidtransfer(Account from,Account to,int amount){// 危险读、判断、写中间没有互斥if(from.balance amount){from.balance- amount;// 线程1、线程2可能同时进来to.balance amount;}}// A100// 线程1A→B 转 80// 线程2A→C 转 80// 两边都读到 100都认为够 → 实际转出 160A 却可能只剩 202. 一把一把加顺序反了死锁RLocklockAredisson.getLock(account:A);RLocklockBredisson.getLock(account:B);// 线程1A → BnewThread(()-{lockA.lock();sleep(100);// 模拟业务间隙lockB.lock();// 等 Btry{// 转账}finally{lockB.unlock();lockA.unlock();}}).start();// 线程2B → A顺序反了newThread(()-{lockB.lock();sleep(100);lockA.lock();// 等 Atry{// 转账}finally{lockA.unlock();lockB.unlock();}}).start();// 结果线程1 拿着 A 等 B线程2 拿着 B 等 A → 死锁3. MultiLock正确写法推荐看这个importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;publicclassTransferService{privatefinalRedissonClientredisson;publicTransferService(RedissonClientredisson){this.redisson redisson;}/*** 从 fromUser 转 amount 到 toUser*/publicbooleantransfer(LongfromUser,LongtoUser,intamount){// 两把账户锁RLocklockFromredisson.getLock(lock:account: fromUser);RLocklockToredisson.getLock(lock:account: toUser);// 联锁要么都加上要么整体失败内部处理顺序/释放RLockmultiLockredisson.getMultiLock(lockFrom, lockTo);booleanlockedfalse;try{// 最多等 3 秒抢锁不指定 leaseTime → 可走看门狗locked multiLock.tryLock(3,TimeUnit.SECONDS);if(!locked){returnfalse;// 抢锁失败稍后再试}// 临界区同一时刻只有一个转账在改这两个账户 intfromBalancegetBalance(fromUser);// 查库/查缓存if(fromBalance amount){returnfalse;// 余额不足}deduct(fromUser, amount);// A 减钱add(toUser, amount);// B 加钱saveLog(fromUser, toUser, amount);returntrue;}catch(InterruptedExceptione){Thread.currentThread().interrupt();returnfalse;}finally{if(locked){multiLock.unlock();// 两把锁一起放}}}// 下面这些你用 MyBatis/MySQL 实现即可privateintgetBalance(LonguserId){return100;}privatevoiddeduct(LonguserId,intamount){}privatevoidadd(LonguserId,intamount){}privatevoidsaveLog(Longfrom,Longto,intamount){}}对照理解不加锁两个转账同时读到余额100 → 超扣乱加锁线程1锁A等B线程2锁B等A → 死锁MultiLock同时申请 AB都成功才进临界区改完一起 unlock → 安全且不易死锁和你秒杀的对应关系// 你项目锁的是「用户」防止一人多单RLocklockredisson.getLock(lock:order userId);lock.tryLock(1,TimeUnit.SECONDS);// 转账例子锁的是「两个账户」防止余额算错RLockmultiredisson.getMultiLock(lockA, lockB);multi.tryLock(3,TimeUnit.SECONDS);本质一样改共享数据前先互斥改完再放。红锁呢红锁 RedLock是 Redis 作者提出的「多 Redis 节点」分布式锁方案。别和 MultiLock 搞混。和 MultiLock 的区别先分清MultiLock联锁RedLock红锁锁几个key多个不同业务 key账户A、账户B同一个业务但写到多个 Redis锁几台 Redis通常 1 台多台独立 Redis如 5 台解决啥同时保护多个资源、防死锁单机 Redis 挂了/主从切换丢锁MultiLock1个Redis里 → lockA lockBRedLock 5个Redis里 → 同一把锁各写一遍超过半数成功才算拿到为什么要有红锁单 Redis 锁的坑1. 客户端在「主节点」加锁成功2. 主还没同步给从3. 主挂了从被提升成新主4. 新主上没有这把锁5. 另一个客户端又能加锁 → 两个人同时持锁RedLock 想用多数派降低这种风险。RedLock 怎么工作假设 5 个独立 Redis不是主从是 5 个 master1. 记录当前时间2. 依次向 5 个节点执行 SET key value NX PX3. 如果「成功节点数 ≥ 3」超过半数且「总耗时 锁有效期」→ 加锁成功4. 否则 → 失败并把已加上的节点上的锁全删掉5. 解锁 → 每个节点都删用 Lua 判断 valueRedis1 ✅ Redis2 ✅ Redis3 ❌ Redis4 ✅ Redis5 ❌成功 3 个 ≥ 3 → 拿到锁Redisson 代码// 假设配了多个 RedissonClient指向不同 RedisRLocklock1redisson1.getLock(lock:order: userId);RLocklock2redisson2.getLock(lock:order: userId);RLocklock3redisson3.getLock(lock:order: userId);// 红锁同一把锁多个 Redis 实例RLockredLockredisson1.getRedLock(lock1, lock2, lock3);booleanlockedfalse;try{// 等最多 1 秒锁持有 10 秒也可不写 leaseTime 用看门狗locked redLock.tryLock(1,10,TimeUnit.SECONDS);if(!locked){returnResult.fail(系统繁忙);}// 业务下单 / 转账 ...returndoBusiness();}finally{if(locked){redLock.unlock();}}底层思路每个节点都是一把普通 RLockRedLock 负责「多数成功才算成功」。红锁一定安全吗不一定。业界有争议Martin Kleppmann 质疑过。仍可能出问题的情况机器时钟跳变锁提前过期客户端 Full GC 卡住锁过期了还以为自己有锁本质仍依赖「过期时间」不是强一致共识所以多数业务单 Redis Redisson 数据库唯一约束/幂等就够真要强一致更倾向ZooKeeper / etcd或加fencing token。