redis分布式锁实现 redis分布式锁实现流程
小周给大家谈谈redis分布式锁实现,以及redis分布式锁实现流程应用的知识点,希望对你所遇到的问题有所帮助。
redis分布式锁实现 redis分布式锁实现流程
redis分布式锁实现 redis分布式锁实现流程
1、在每一轮选举中,每个哨兵一票。
2、投票遵循先来先到的原则,如果某个哨兵没有投给别人,就会投给哨兵A。
3、在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!因为 SetNX 不具备设置过期时间的功能,所以我们需要借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 SetNX 成功了 Expire 却失败了。
4、 可惜还有问题:当多个请求到达时,虽然只有一个请求的 SetNX 可以成功,但是任何一个请求的 Expire 却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。
5、于是乎我们需要在保证原子性的同时,有条件的执行 Expire,接着便有了如下 Lua 代码:比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。
6、下面以目前 PHP 社区里的 PHPRedis 扩展为例,实现一段演示代码:$ok = $redis->setNX($key, $value);if ($ok) {$redis->del($key);}?>缓存过期时,通过 SetNX 获取锁,如果成功了,那么更新缓存,然后删除锁。
7、看上去逻辑非常简单,可惜有问题:如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。
8、于是乎我们需要给锁加一个过期时间以防不测:$redis->setNX($key, $value);$redis->expire($key, $ttl);?>local key = KEYS[1]local value = KEYS[2]local ttl = KEYS[3]local ok = redis.call('setnx', key, value)if ok == 1 thenendreturn ok没想到实现一个看起来很简单的功能还要用到 Lua 脚本,着实有些麻烦。
9、其实 Redis 已经考虑到了大家的疾苦,从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。
10、$ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));if ($ok) {$redis->del($key);}?>如上代码是完美的吗?是还一点!设想一下,如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况,所以我们在创建锁的时候需要引入一个随机值:$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));if ($ok) {if ($redis->get($key) == $random) {$redis->del($key);}}?>如此基本实现了单机锁,如要实现分布锁,请参考:Distributed locks with Redis,不过分布式锁需要注意的地方更多:How to do distributed locking,Is Redlock safe。
11、此条目由老王发表在Technical分类目录,并贴了Redis标签。
12、将固定链接加入收藏夹。
13、《谈谈REDIS的SETNX》上有21条评论评论导航木子李在2015-09-1509:56hash最适合的就是做对象缓存:13说道:伪代码:$lock = 0while($lock != 1){$timestamp = time() + $timeout + 1;$lock = SETNX(‘lock.foo’,$timestamp);if($lock == 1 or (time() > (GET(‘lock.foo’) and time() > (GETSET(‘lock.foo’,timestamp))){break;}else{sleep(10ms);}}do_job()# releaseif(now() DEL('lock.foo');}回复 ↓robbinhan在2015-09-1611:35:41说道:一个情况我认为其实也有问题,如果同时有很多请求,而每次请求都遇到了锁过期,但更新还没执行完的情况,也可能会产生雪崩吧猫特在2016-02-1717:05:32说道:SET 涵盖了 SETEX 的功能,不会在发生雪崩了frank在2015-10-3010:12:12说道:lua脚本和种php实现有同样的隐患吧?能解释lua不会在设置过期时间前中断么老王在2015-11-0317:17:53说道:lua 脚本在 redis 中执行的时候是原子的,要成功都成功,要失败都失败,不会出现成功一部分的情况,所以没问题。
14、Pingback引用通告: [狗尾续貂第二篇]Redis内存锁的实现方法 - IT大道tim在2016-03-0309:48:47说道:一个情况也是有问题的当key加了随机数之后,这个key就变成了另一个key了,这样每个请求生成的key就不一致了,那setnx 这个函数就没意义了。
15、jewelknife在2016-04-0615:06:16说道:随机数加的是value不是keyweer在2016-04-2112:05:25说道:php脚步里面,加上条件判断后,再去更新过期时间,应该也没问题吧?在加mutil的情况下天涯逐梦在2016-05-2819:10:48说道:不行,muti/exec模式,后一条命令不能依赖前一条命令的输出结果的在2016-05-0617:19:54说道:不错Pingback引用通告: [转]谈谈Redis的SETNX – 王春伟的技术博客liaomengge在2016-05-2619:35:40说道:一个情况:如果update时间很长,超过过期时间,此时,redis应该会自动帮你删除这个key吧,此时的手动删除,没任何意义吧!!!也会导致同样的问题,不知道我的理解正确不?个人觉得楼给出的,还是比较完善的,但是也会有这类问题。
16、这里的删除作还是需要的,不删除的话后续需要更新缓存的作就必须等到缓存失效才能做更新了考虑下一个业务上的更新要求缓存失效更新其值为新的值的情况不会发生雪崩这样不会在5分钟之内有大量访问更新db的作,但会存在大量访问db作(目标缓存已经失效)。
本文到这结束,希望上面文章对大家有所帮助。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系 836084111@qq.com 删除。