1. 分布式锁的特点
- 互斥性:同一时刻只有一个客户端可以持有锁
- 容错性:只要锁服务集群中大部分节点正常运行,客户端就可以进行锁操作
- 避免死锁:保证锁一定能释放,正常释放或超时释放
- 加锁和解锁是同一个客户端
2. 分布式锁的实现方式
- 基于数据库实现分布式锁(乐观锁、悲观锁)
- 基于zookeeper时节点的分布式锁
- 基于Redis的分布式锁
- 基于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的设置方式:
- 将加锁的线程ID(服务标识+线程ID)
- 时间戳+服务标识生成随机数
- 官方推荐从 /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