1.分布式锁简介
在同一个单体应用内部,我们往往使用Synchroized或者Lock的方式来解决多线程之间的安全问题,但是在分布式的架构下,在多个应用之间,就需要一种比较高级的锁机制来处理不同应用之间的线程安全问题。解决方案就是分布式锁。
- Redis分布式锁
1.原理
Redis分布式锁机制,主要是依靠两个命令来完成。setnx
和expire
- setnx:当key不存在时,将key设置为value,存在则不做任何操作,并且返回0
setnx key value
- expire:设置key的过期时间
expire key time
步骤: 1. 当key不存在时去创建key,并设置value和对应的过期时间,返回1。成功加锁
如果key存在,则会返回0,此时抢锁失败。
当持有锁的线程释放锁时,手动删除key,或者过期时间到了,自动删除key->释放锁
问题:
1.如果加锁成功了,但是设置过期时间失败,但是此时系统挂掉了,那么这个锁就永远不会被释放,此时其他线程就会一直拿不到这个锁了。
解决:
1.setnx
d的同时,也设置过期时间,让一起执行。set key value [过期时间类型EX Second| PX millisenconds] [NX|XX]
其中EX,PX,NX,XX的描述信息为:
2.使用Lua脚本进行处理,命令如下
EVAL script numkeys key [key...] arg [arg...]
- script: 时一段Lua5.1脚本程序,它会被运行在Redis服务器上下文中
- numkeys:用户指定键名参数的个数
在Lua脚本中,可以使用redis.call()命令来执行Redis命令。例如
eval "return redis.call('set', KEY[1], ARGV[1])" 1 foo bar
这段脚本可以实现将foo的值设置为bar
- 1: 代表单数的个数,为一个
- foo:key[1]
- bar: ARGV[1]
2.实现
- 加锁 加锁就是调用
set key PX NX
命令设置一个key值 - 解锁 解锁就是删除指定key,即释放了锁
- 注意: 要保证操作的原子性,如果把判断锁和释放锁分开处理的话,可能会出现误解锁的情况,所以最好保持执行的原子性,即使用Lua脚本把判断锁和是加锁,释放锁都放在一起
3.锁过期问题
如果某个业务操作规定时10s,锁设置的时20s,但是由于操作过于复杂,导致锁的执行时间超过了20s,拿此时业务会在无锁的情况下执行,此时就会发生数据紊乱的情况。 解决:
- 乐观锁方式,增加版本号 通过根据锁的版本号来判断,当前线程持有的锁是否还存在,如果对比后版本号不一致,则直接拒绝处理业务。但是这种方式会入侵代码。
- 通过watch dog自动延期机制 当客户端加锁成功了,就会启动一个后台守护线程,该线程每10s会检查一下,如果客户端还持有锁,就会不断的延长锁key的生存时间。注:watchDog只在未指定显示的加锁时间才有效,即默认设置key过期时间的情况下。
- Redisson分布式锁
1.简介
Redisson是基于Netty的Redis客户端。不但能操作原生的Redis数据结构,还为使用者提供了一系列具有分布式特性的常用工具类,实现了分布式锁。
- Redisson分布式锁和JUC的Lock方法相似。RLock接口继承了Lock接口。
- 所得结构是Hash -key:锁的名字 -字段:UUID+threadId -值:代表的重入次数 !
- 锁的重入 当锁重入一次,重入值就会+1,并且过期时间也会更新
2.加锁原理
- 判断有没有“DISLOCK”,如果没有,设置UUID:1=1,并且设置它的过期时间
- 如果key和字段都存在,进行锁重入,执行命令
incrby UUID:1 1
,结果:DISLOCK:{UUID:1 2} - 客户端2进入,判断有没有KEY,没有字段,返回过期时间,客户端2自旋等待
3.释放锁
1.判断KEY是否存在,如果不存在返回nil,如果存在,使用hincrby-1
把重入次数减1 2.减完后,counter>0值依然大于0,则返回0 3.剪完后,counter<=0,则删除key,并使用publish广播锁释放消息
本文转载自: 掘金