Redis学习笔记---Redis的分布式锁框架Redisson

Redis学习笔记—Redis的分布式锁框架Redisson

  1. Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。
  2. Redisson在基于NIO的Netty框架上,生产环境使用分布式锁。

1. Redisson分布式锁的实现原理

  1. 由下图可知Redisson锁实现是由lua脚本加锁实现的
  2. watch dog小程序可以续租锁,通过锁的可重入机制,可以延长锁的时间再次加锁
    Redis学习笔记---Redis的分布式锁框架Redisson_第1张图片

2. 加锁机制

  1. 如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。
  2. 发送lua脚本到redis服务器上,脚本如下:
    Redis学习笔记---Redis的分布式锁框架Redisson_第2张图片
  3. lua的作用:保证这段复杂业务逻辑执行的原子性。

3. 锁互斥机制

  1. 那么在这个时候,如果客户端2来尝试加锁,执行了同样的一段lua脚本,会咋样呢?
  2. 很简单,第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在了。
  3. 接着第二个if判断,判断一下,myLock锁key的hash数据结构中,是否包含客户端2的ID,但是明显不是的,因为那里包含的是客户端1的ID。
  4. 所以,客户端2会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的剩余生存时间。比如还剩15000毫秒的生存时间。
  5. 此时客户端2会进入一个while循环,不停的尝试加锁。

4. 自动延时机制

  1. 只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

5. 可重入锁机制

  1. 第一个if判断肯定不成立,“exists myLock”会显示锁key已经存在了。
  2. 第二个if判断会成立,因为myLock的hash数据结构中包含的那个ID,就是客户端1的那个ID,也就是“8743c9c0-0795-4907-87fd-6c719a6b4586:1”
  3. 此时就会执行可重入加锁的逻辑,他会用:incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1
  4. 通过这个命令,对客户端1的加锁次数,累加1。数据结构会变成:myLock :{“8743c9c0-0795-4907-87fd-6c719a6b4586:1”:2 }

6. 释放锁机制

执行lua脚本如下:

# 如果key已经不存在,说明已经被解锁,直接发布(publish)redis消息 
				"if (redis.call('exists', KEYS[1]) == 0) then " + 
						"redis.call('publish', KEYS[2], ARGV[1]); " + 
						"return 1; " + 
					"end;" + 
# key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。 不是我加的锁 不能解锁 
				"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + 
						"return nil;" + 
					"end; " + 
# 将value减1 
				"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + 
# 如果counter>0说明锁在重入,不能删除key 
				"if (counter > 0) then " + 
						"redis.call('pexpire', KEYS[1], ARGV[2]); " + 
						"return 0; " + 
# 删除key并且publish 解锁消息 
					"else " + 
					"redis.call('del', KEYS[1]); " + #删除锁 
					"redis.call('publish', KEYS[2], ARGV[1]); " + 
					"return 1; "+
					"end; " + 
					"return nil;",
  1. – KEYS[1]:需要加锁的key,这里需要是字符串类型。
  2. – KEYS[2]:redis消息的ChannelName,一个分布式锁对应唯一的一个channelName:“redisson_lockchannel{” + getName() + “}”
  3. – ARGV[1]:reids消息体,这里只需要一个字节的标记就可以,主要标记redis的key已经解锁,再结合redis的Subscribe,能唤醒其他订阅解锁消息的客户端线程申请锁。
  4. – ARGV[2]:锁的超时时间,防止死锁
  5. – ARGV[3]:锁的唯一标识,也就是刚才介绍的 id(UUID.randomUUID()) + “:” + threadId
  6. 如果执行lock.unlock(),就可以释放分布式锁,此时的业务逻辑也是非常简单的。
  7. 其实说白了,就是每次都对myLock数据结构中的那个加锁次数减1。
  8. 如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从redis里删除这个key。
  9. 然后呢,另外的客户端2就可以尝试完成加锁了。

7.分布式锁特性

  1. 互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
  2. 同一性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。
  3. 可重入性:持有某个锁的客户端可继续对该锁加锁,实现锁的续租
  4. 容错性:锁失效后(超过生命周期)自动释放锁(key失效),其他客户端可以继续获得该锁,防止死锁

你可能感兴趣的:(Redis)