谈谈Redis的SETNX
来源:互联网 发布:小度总管软件下载 编辑:程序博客网 时间:2024/05/11 15:05
在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!
比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。
下面以目前 PHP 社区里最流行的 PHPRedis 扩展为例,实现一段演示代码:
<?php$ok = $redis->setNX($key, $value);if ($ok) { $cache->update(); $redis->del($key);}?>
缓存过期时,通过 SetNX 获取锁,如果成功了,那么更新缓存,然后删除锁。看上去逻辑非常简单,可惜有问题:如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。于是乎我们需要给锁加一个过期时间以防不测:
<?php$redis->multi();$redis->setNX($key, $value);$redis->expire($key, $ttl);$redis->exec();?>
因为 SetNX 不具备设置过期时间的功能,所以我们需要借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 SetNX 成功了 Expire 却失败了。 可惜还有问题:当多个请求到达时,虽然只有一个请求的 SetNX 可以成功,但是任何一个请求的 Expire 却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。于是乎我们需要在保证原子性的同时,有条件的执行 Expire,接着便有了如下 Lua 代码:
local key = KEYS[1]local value = KEYS[2]local ttl = KEYS[3]local ok = redis.call('setnx', key, value) if ok == 1 then redis.call('expire', key, ttl)end return ok
没想到实现一个看起来很简单的功能还要用到 Lua 脚本,着实有些麻烦。其实 Redis 已经考虑到了大家的疾苦,从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。
<?php$ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));if ($ok) { $cache->update(); $redis->del($key);}?>
如上代码是完美的吗?答案是还差一点!设想一下,如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况,所以我们在创建锁的时候需要引入一个随机值:
<?php$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));if ($ok) { $cache->update(); if ($redis->get($key) == $random) { $redis->del($key); }}?>
如此基本实现了单机锁,假如要实现分布锁,请参考:Distributed locks with Redis,这里就不深入讨论了,总结:避免掉入 SETNX 陷阱的最好方法就是永远不要使用它!
此条目由老王发表在Technical分类目录,并贴了Redis标签。将固定链接加入收藏夹。《谈谈Redis的SETNX》上有18条评论
评论导航
在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!
比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。
下面以目前 PHP 社区里最流行的 PHPRedis 扩展为例,实现一段演示代码:
<?php$ok = $redis->setNX($key, $value);if ($ok) { $cache->update(); $redis->del($key);}?>
缓存过期时,通过 SetNX 获取锁,如果成功了,那么更新缓存,然后删除锁。看上去逻辑非常简单,可惜有问题:如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。于是乎我们需要给锁加一个过期时间以防不测:
<?php$redis->multi();$redis->setNX($key, $value);$redis->expire($key, $ttl);$redis->exec();?>
因为 SetNX 不具备设置过期时间的功能,所以我们需要借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 SetNX 成功了 Expire 却失败了。 可惜还有问题:当多个请求到达时,虽然只有一个请求的 SetNX 可以成功,但是任何一个请求的 Expire 却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。于是乎我们需要在保证原子性的同时,有条件的执行 Expire,接着便有了如下 Lua 代码:
local key = KEYS[1]local value = KEYS[2]local ttl = KEYS[3]local ok = redis.call('setnx', key, value) if ok == 1 then redis.call('expire', key, ttl)end return ok
没想到实现一个看起来很简单的功能还要用到 Lua 脚本,着实有些麻烦。其实 Redis 已经考虑到了大家的疾苦,从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。
<?php$ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));if ($ok) { $cache->update(); $redis->del($key);}?>
如上代码是完美的吗?答案是还差一点!设想一下,如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况,所以我们在创建锁的时候需要引入一个随机值:
<?php$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));if ($ok) { $cache->update(); if ($redis->get($key) == $random) { $redis->del($key); }}?>
如此基本实现了单机锁,假如要实现分布锁,请参考:Distributed locks with Redis,这里就不深入讨论了,总结:避免掉入 SETNX 陷阱的最好方法就是永远不要使用它!
伪代码:
$lock = 0
while($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()
if(now() < GET('lock.foo')){
DEL('lock.foo');
}
- 谈谈Redis的SETNX
- 谈谈Redis的SETNX
- 谈谈Redis的SETNX
- 谈谈Redis的SETNX分布式锁
- 谈谈redis中的setnx
- Redis set setnx increase 的理解
- 使用redis的setnx制作排他锁
- 基于redis setnx的简易分布式锁
- redis 的setnx,数据库读取缓存
- redis setNX 锁来实现防止重复提交的
- 基于redis的setnx()、get()、getset()方法 分布式锁
- redis分布式锁-SETNX实现
- redis分布式锁-SETNX实现
- redis分布式锁-SETNX实现
- 用redis SETNX进行加锁
- redis分布式锁-SETNX实现
- 【Redis】 redis setnx命令实现分布式锁
- redis的setnx锁到了超时时间失效,并发的问题
- 搭建Hadoop分布式集群
- AOP源码解析(四)寻找匹配的增强器
- 数据结构示例之带头节点的、单循环链表
- ns3通过TapBridge连接外部控制器floodlight
- opencv-warpAffine,matlab-imwarp
- 谈谈Redis的SETNX
- 如何使用 Service 模式?
- 欢迎使用CSDN-markdown编辑器
- char char* char[] sizeof strlen
- 如何使用 Presenter 模式?
- hdu5950 矩阵快速幂
- ubuntu14.04+caffe+cuda8.0+cudnn-8.0-v5.1(gtx1070)安装与测试
- poj 3734 指数型生成函数
- 上班第一天(3)--一个程序员的成长史(12)