Redis学习记录----分布式锁

95 阅读2分钟

分布式锁

概念

image.png

image.png

基于Redis的分布式锁

Redis实现加锁的原理是NX(互斥)

image.png

具体实现

获取锁、释放锁接口

image.png

实现流程

image.png

实现获取锁、释放锁方法

image.png

调用,调用时锁的Key为锁前缀KEY_PREFIX(lock)+name(业务+用户id)

image.png

极端情况一

由于业务阻塞,导致分布式锁超过过期时间而失效,此时如果另一个线程就可以获取到锁,而当第一个线程苏醒并完成业务后,会释放掉别人的锁(另一个线程),这样一释放二、二释放三、....导致出现极端问题

image.png

解决方法:每次释放锁时需要判断是否是自己的锁

image.png

image.png

改进分布式锁

在获取锁时存入线程标识,为什么? 按照原先的做法,锁的key为lock:order:userId,value为threadId,但在集群模式下,每个jvm的id都是单独自增,互不影响的,因此可能在另一个jvm上也出现相同的threadId(即虽然已经用redis分布式锁保证了不同jvm已经可以只有一个线程执行,但因为存在阻塞的情况,所以还是可能多线程并发地执行,且由于此时不同jvm的threadId可能相同,所以导致阻塞的线程苏醒后误认为是自己的锁,进而释放掉该锁),所以需要整一个uuid先标识jvm,再在uuid后面拼接上threadId,这样就可以区分不同jvm的不同线程

image.png

修改为uuid拼接线程id,toString(true)表示默认去掉下划线

image.png

极端情况二

由于判断标识和释放锁是两个动作,有可能在判断标识后(判断为自己的锁),释放锁被阻塞了(jvm里的垃圾回收会阻塞线程)导致锁超时过期,此时另一个线程来获取锁成功,执行业务,而某段时间后前一个线程的阻塞结束了,且它已经经过判断,认为此时的锁还是自己的,于是释放,再一次出现释放了别人的锁的问题

image.png

再次改进分布式锁

使用lua脚本保证操作的原子性,lua是一种编程语言,可以通过redis.call来执行redis的命令

image.png

image.png

编写lua脚本

image.png

如果释放锁每次都要读取这个lua脚本,可能出现io流异常,所以应该提前读好lua脚本,比如这里的UNLOCK_SCRIPT

image.png

调用lua脚本,实质是将查询、判断、释放合并为一个操作(原子性)

image.png

基于Redis的分布式锁存在的问题:

image.png

该问题会在下一章:使用Redisson解决