使用lua脚本
加锁命令
set
SET lockKey value NX PX expireTime
eg:
jedis.set(keys,uuid,"NX","PX",30000)
incr
原子操作
3、setNX
不能设置过期时间,需借助事物设置过期时间
设计分布锁
同一线程可重入。
加锁要保证原子性,使用setnx。
value要具有唯一性。
释放锁需要首先释放锁的线程与加锁的线程是不是同一个。
释放锁时要验证value值。
释放锁时为何要验证value值
如果A线程执行业务耗时超过了锁的持有时间,锁会自动释放;锁自动释放之后,线程B又加锁成功,此时A线程执行完业务逻辑之后,去释放锁,但A线程的锁已经自动释放了,如果没有value来标识的话,它可能就会去释放B线程的锁
锁超时问题
A线程执行业务耗时超过了锁的持有时间,锁会自动释放
Redis集群发生主从切换,出现锁丢失问题
如果master节点由于某原因发生了主从切换,那么就会出现锁丢失的情况;在master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,master故障,发生故障转移,slave节点升级为master节点,导致锁丢失。解决方案:Redlock
RedLock
RedLock算法虽然是需要多个实例,但是这些实例都是独自部署的,没有主从关系。之所以要用独立的,是避免了Redis异步复制造成的锁丢失。在Java中Redisson包实现了对RedLock的封装。
RedLock 的加锁过程
N个Redis服务或集群
Client 获取当前毫秒级时间戳,并设置超时时间 TTL
依次向 N 个 Redis 服务发出请求,用能够保证全局唯一的 value 申请锁 key
如果从N/2+1个Redis 服务中都获取锁成功,那么,本次分布式锁的获取被视为成功,否则视为获取锁失败
如果获取锁失败,或执行达到 TTL,则向所有 Redis 服务都发出解锁请求。
Redisson中关于分布锁的实现
缺点
加锁时只作用在一个Redis节点上
它加锁时只作用在一个Redis节点上,即使Redis通过Sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况。
RedissonRedLock才是最终的银弹,只是使用时需要同时在多个节点上加锁,效率略低
Redisson使用守护线程来进行锁的续期,(当主线程销毁时,守护线程和主线程一起销毁。)防止程序宕机后,线程依旧不断续命,造成死锁!
另外,Redisson还实现并且优化了RedLock算法、公平锁、可重入锁、连锁等操作,使Redis分布式锁的实现方式更加简便高效!
看门狗机制-Watchdog
定义:
RedLock的Java版本Redisson实现了一种保证锁失效时间绝对大于业务程序执行时间的机制。
主要原理
在程序成功获取锁之后,会fork一条子线程去不断的给该锁续期,直至该锁释放为止。
和zookeeper比较
貌似redis比较复杂,需要考虑过期时间、原子性、高可用等
如果Redis获取锁的那个客户端挂了,那么只能等待超时时间之后才能释放锁
而对于ZooKeeper,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁
ZK CP,选举导致不可用
Redis AP