Redis分布式锁实现基本概念

Redis分布式锁实现基本概念

1-redis分布式锁

简答:我们通常使用 Redis 实现分布式锁,通过 SETNX + EXPIRE 保证原子获取锁,同时设置唯一UUID避免误删其他线程锁,释放锁时使用 Lua 脚本保证删除操作的原子性。生产项目一般使用 Redisson,它封装了锁续期、可重入锁、公平锁等能力。

1-1.set nx

SET key_name client_id NX PX 30000

NX:只有 Key 不存在时才设置成功(保证互斥)。

PX 30000:设置过期时间为 30 秒(防止节点宕机导致死锁)。

client_id:每个客户端生成唯一的随机 ID(如 UUID),用来标识“谁加的锁”。

1-2.Lua脚本-释放锁

lua脚本写法:

取出值,然后作比较,确定是自己创建自己删

ifredis.call('get',KEYS[1])==ARGV[1]thenreturnredis.call('del',KEYS[1])elsereturn0end

用这个脚本去读取,然后判断是不是现在这个方法生成的uuid,保证原子性,大致代码如下:

publicvoiddoBusiness(){StringlockKey="lock:order:123";Stringuuid=UUID.randomUUID().toString();// set nx,内部的值为uuidBooleansuccess=redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,30,TimeUnit.SECONDS);if(Boolean.TRUE.equals(success)){try{// ===================// 业务代码// ===================createOrder();}finally{// 释放锁,用lua脚本releaseLock(lockKey,uuid);}}else{thrownewRuntimeException("系统繁忙,请稍后重试");}}

1-3.Redisson

现在有下列情况:

T0: 业务A执行 SETNX lock key_A EX 10(锁10秒过期) T1-T8: 业务A执行中... T10: 锁到期,Redis自动删除 key T11: 业务B执行 SETNX lock key_B EX 10 → 成功(拿到锁) T12: 业务A终于执行完了,执行Lua脚本删除锁

也就是业务A时间过长,导致锁自动过期删除,后续业务B看到没锁了,就创建key,正好业务A运行完了,去释放锁,导致业务B的key被删除了,我们用lua脚本,加上uuid的判断可以规避释放锁

// 业务A的代码if(setnx("lock",uuidA,10)){// 业务A以为有锁,开始执行// 假设这里执行了15秒...doSomeLongWork();// ← 第10秒时锁已经没了,但代码还在跑!// 第15秒执行完,尝试解锁delScript("lock",uuidA);// 失败,因为uuid不匹配}

但是A自己的业务在锁过期后还在运行,这会影响到数据一致性,此时我们需要续期,这个时候使用Redisson

1-3-2.续期

正常情况下,我们可以手动续期,但是一般企业会规范化使用Redisson

publicvoiddoBusiness(){// 1. 获取锁对象(Redisson 的锁)RLocklock=redissonClient.getLock("lock:order:123");try{// 2. 加锁(阻塞等待,直到获取到锁)lock.lock(30,TimeUnit.SECONDS);// 或者直接 lock(),使用默认30秒看门狗// 3. 执行业务createOrder();}catch(Exceptione){// 业务异常处理thrownewRuntimeException("业务执行失败",e);}finally{// 4. 释放锁lock.unlock();}}

PS:

  • 服务器宕机:因为redisson内部是隔一段时间就续个期,如果服务器宕机了,那么这个锁到期了也会释放,锁不会永久存在
  • 业务超时卡死:业务代码卡死,导致锁一直没释放,此时可以通过设置锁的最大时间规避,或者设置接口时间等等