5分钟快速理解redis分布锁
目标:
我们了解分布式锁先要理解几个问题:
1.任何时候只有一个线程持有锁
2.要防止一个线程长期持有锁甚至是死锁的情况
3.加锁和解锁必须是同一个进程
4.锁延续
Redis分布式锁:
常见的分布式锁有redis分布式锁,zookeeper分布式锁,本文将为大家阐述redis分布式锁。
首先,redis分布式锁的本质就是在redis占一个坑位,利用的setnx命令,然后处理完其余的业务后再del。再setnx后如果有其它的线程进来再setnx那么是set不进去的。这就是占坑的原理。
此时第一个问题就出现了:在del之前 我的业务如果出现了错误,那么就不会去执行del,就会出现死锁的情况。
这种情况的解决方案很简单 我们只需要增加一个超时时间即可。比如设置超时时间10s锁将会自动释放。在redis2.8之后 setnx和expire是原子操作 我们不用考虑setnx后因为各种问题没有expire的情况。
那么现在就会有第二个问题:锁超时问题。
Redisson分布式锁 这边我们使用redisson的分布式锁来解决这个问题。
先看一段lua脚本:
if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
和大家解释一下这一段lua脚本的意思:
exsist 先判断有没有这个key,来看锁是否存在。
存在的话用hincrby设置一个hsah结构,然后再pexpire设置过期时间
我们再看一下redisson的一个加锁解锁流程图:
我们可以看到redisson使用了 watchdog来做锁延迟操作。
在我们redisson.trylock的时候有一个参数是releasedTime,这个参数的含义就是释放锁的时间。我们这个参数如果传了,那么看门狗就会不生效,没传的话看门狗生效,这一点很重要。
redisson 看门狗会默认10s执行一次,如果没有锁释放,那么自动锁延续。
大家看这张图可以看到,redisson还采用了redis的消息订阅与发布,如果一个线程设置了waitTime,他就会去在这个时间里去等待,订阅了一个channel,当占锁线程一旦释放了锁,占锁线程就回去发布一条消息,等待的线程订阅到了 就可以去重试再占锁。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0EVK9Y0-1678841063259)(redis分布式锁流程.png)]
流程分析:
1.客户端1尝试获取锁,返回null则加锁成功,如果有设置释放时间则直接通过lua脚本去操作redis,如果没有设置则开启看门狗机制。当没有设置释放时间,默认释放时间为30s,看门狗机制会10s进行一次所延续。
2.当客户端2获取锁失败,则通过redis的channel订阅锁释放的时间。当超过最大等待时间,则锁失效。如果等待到了锁释放时间的通知,则开始重新进入循环开始重试加锁。
3.循环中每次都先试着获取锁,并得到已存在锁的剩余时间。如果拿到了锁,直接返回。如果锁还存在,那么等待释放锁的消息,这里采用了信号量来阻塞线程,当锁释放并发布释放锁的消息后,信号量的release方法被调用,此时被信号量阻塞的队列中的第一个线程就可以继续尝试获取锁了。
我们再看一下释放锁的代码
// 判断锁 key 是否存在
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
// 将该客户端对应的锁的 hash 结构的 value 值递减为 0 后再进行删除
// 然后再向通道名为 redisson_lock__channel publish 一条 UNLOCK_MESSAGE 信息
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
步骤解析:
1.判断是否存在,如果存在的话先把可重入的值递减为0,再进行删除
2.广播锁释放消息,通知阻塞等待的进程(向通道名为redisson_lock__channelpublish 一条 UNLOCK_MESSAGE 信息)。
3.取消看门狗机制,即将RedissonLock.EXPIRATION_RENEWAL_MAP里面的线程 id 删除,并且 cancel 掉 Netty 的那个定时任务线程。
总结
Redisson的优点: 1.通过watchdog解决了 锁延续问题
2.和zookeeper比较,性能更高。
3.支持可重入锁
4.在等待申请锁资源的进程等待申请锁的实现上做了优化,减少了无效的锁申请,提高了资源的利用率
缺点: 1.在redis分布式锁的情况下,Master redis 加锁,然后把key同步给slave,此时master宕机,那么slave变成了master,这就会出现问题,产生脏数据。 这里用连锁的方式可以解决这个问题。
相关文章
- 扩展基于Redis的数据库数量扩展方案(redis数据库数量)
- Redis累加:高效实现数据增量计算的利器(redis累加)
- 转换数据库至Redis:加速数据存储与访问(数据库转redis)
- Redis实现快速非重复集合存储(redis集合不重复)
- Java和Redis的配合安装方法(java redis安装)
- 一键删除,快速清空Redis数据库(批量删除redis数据库)
- Redis查看当前时间的简单方法(获取redis当前时间)
- Redis缓存技术的应用场景(简述redis应用场景)
- 深入理解掌握Redis源码之路(掌握redis源码)
- 构建高效的Redis连接池实现快速响应(创建redis连接池)
- Redis缓存实现大量商品数据管理(大量商品数据redis)
- 高速查询Redis数据库多线程技术助力(多线程查询redis)
- Redis实现快速查找的商品列表(商品列表redis结构)
- 精准掌控Redis默认进程数(redis默认进程数)
- 深入理解Redis默认的编码方式(redis默认的编码)
- Redis面试请你掌握的常见问题(redis面试常问为题)
- 如何在本机上快速部署Redis(redis部署到本机)
- 单例Redis能否实现梦想(redis适合做成单例吗)
- 解决Redis连接数上限的策略(redis 连接数上限)
- Redis过期则消失(redis 过期的可以)
- Redis迁移指定Key的快速操作(redis迁移指定key)
- Redis指令操作快速上手(redis输入命令)
- Redis实现快速获取存储时间戳(redis获取存储时间戳)
- Redis外网配置快速指南(redis配置外网地址)
- Redis远程批量删除实现快速数据清理(redis远程批量删除)
- Redis如何处理过期场景(redis过期场景)
- Redis快速读取默认配置的实践指南(redis读取默认配置)