Redis分布式锁

1. 分布式锁的特点

  1. 互斥性:同一时刻只有一个客户端可以持有锁
  2. 容错性:只要锁服务集群中大部分节点正常运行,客户端就可以进行锁操作
  3. 避免死锁:保证锁一定能释放,正常释放或超时释放
  4. 加锁和解锁是同一个客户端

2. 分布式锁的实现方式

  1. 基于数据库实现分布式锁(乐观锁、悲观锁)
  2. 基于zookeeper时节点的分布式锁
  3. 基于Redis的分布式锁
  4. 基于Etcd的分布式锁

3. Redis方式实现分布式锁

  3.1 获取分布式锁

  在Redis2.6.12版本之前,使用Lua脚本保证设置键值对设置过期时间两个操作的原子性。

  在Redis2.6.12版本开始,使用Set命令实现加锁

public static boolean tryLock(Jedis jedis,String lockKey,String lockValue,int expireTime){
    String result = jedis.set(lockKey, lockValue,"NX","PX",expireTime);
    if("OK".equals(result)) return true;
    return false;
}

  其中,过期时间短业务流程设置2秒,超长业务流程设置6秒,一般在2~6秒之间。必须设置过期时间,否则在释放锁时服务宕机,则造成死锁

  "NX":SET IF NOT EXISTS,如果键不存在则设置,否则不做操作 / 若为"XX":SET IF EXISTS,只有键存在时才设置

  "PX":需要设置过期时间,单位毫秒,由expireTime决定 / 若为"EX",则单位为秒

  3.2 释放分布式锁

  使用Lua脚本,在删除锁的之前必须确保删除的是当前线程占有的锁(比对释放时的value和获取时的value)。

//解锁Lua脚本,必须先比较value,防止误解锁
private static final String SCRIPT_UNLOCK = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

public static boolean unLockLua(Jedis jedis,String lockKey,String lockValue){
    long result = (long)jedis.eval(SCRIPT_UNLOCK,Arrays.asList(lockKey),Arrays.asList(lockValue));
    if(result == 1) return true;
    return false;
}

   锁value的设置方式

  1. 将加锁的线程ID(服务标识+线程ID)
  2. 时间戳+服务标识生成随机数
  3. 官方推荐从 /dev/urandom 中取20个byte作为随机数

   3.3 守护线程"续航"锁

   问题:当锁的超时时间设置短于实际业务需求,导致Redis意外删除锁。

  在线程获取锁的同时,创建一个守护线程,该守护线程周期性延长锁的过期时间。在主线程处理完,安全释放锁的同时,删除守护线程。

  参考:https://www.cnblogs.com/wangzaiplus/p/10864135.html

4. Redis分布式锁优缺点

  基于内存,速度快。需要程序处理原子性、超时、误删的情况,在获取锁失败时,客户端只能自旋等待,在高并发的情况下,性能消耗比较大

  CAP原理:在分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)三者不可兼得,分为CP(强一致模型)和AP(高可用模型)

  • 一致性:所有节点的数据同一时刻是一致的
  • 可用性:每个请求不管成功或者失败在一定时间内都有响应
  • 分区容错性:当网络出现异常导致分区节点间无法通信,要保证整个系统是可用的

5. 生产环境中的分布式锁-Redisson

 

  加锁机制

  加锁命令 hset lockKey key value

  其中key的生成规则:UUID + ":" + threadId;value的值初始为1,代表重入次数。加锁时长默认30秒

  锁互斥机制、可重入机制

  当客户端尝试加锁时,判断lockKey是否存在,若存在,查看key是否对应当前客户端,若不是则加锁失败返回锁剩余时长,客户端进入轮训。否则重入次数+1

  自动延时机制

  加锁成功后,启动一个看门狗线程,是一个辅助线程,每10秒检查一次,若当前加锁客户端依然持有锁,则延长加锁时间

  锁释放机制

  每次释放,锁的重入次数减1。当重入次数为0时,del lockKey

 6. Redis集群模式下的分布式锁

  之前讨论单机模式下的加锁,在主从模式下,主从复制之间存在延迟,如果主机尚未将加锁key信息同步到从机,主机下线了,从机上线,此时加锁信息就丢失了。为了处理这种情况,可以部署多组独立的主从服务,对每组都采用一样的加锁操作,只有在半数以上加锁成功才表示真正获得锁,否则认为加锁失败。

   参考:https://www.jianshu.com/p/8605713cb83b

 

posted @ 2021-03-10 10:45  walker993  阅读(117)  评论(0编辑  收藏  举报