SETNX命令简介

SETNX key value
将key的值设为value,并且仅当key不存在。
若给定的key已经存在,则SETNX不做任何操作。
SETNX 是SET if Not eXists的简写。
返回整数,具体为

  • 1,当 key 的值被设置
  • 0,当 key 的值没被设置

使用SETNX实现分布式锁

多个进程执行以下Redis命令:

SETNX lock.foo <current Unix time + lock timeout + 1>

如果 SETNX 返回1,说明该进程获得锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁的有效时间)。
如果 SETNX 返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。

解决死锁

正常第一反应利用SETNX实现分布式锁可能是这样的

if(SETNX key value){//如果设置成功表示拿到了锁return true;
}
return false;

然后释放锁的时候就直接 DEL掉;
简单思路是这样,但是这样会有很多问题

  • 如果一个进程获得锁之后,断开了与redis的连接(进程挂断或者网络中断),那么锁一直的不断释放,其他的进程就一直获取不到锁,就出现了 “死锁”
  • 然而,锁超时时,我们不能简单地使用 DEL 命令删除键 lock.foo 以释放锁。考虑以下情况,进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。进程P2,P3正在不断地检测锁是否已释放或者已超时,执行流程如下:
    1 . P2和P3进程读取键 lock.foo 的值,检测锁是否已超时(通过比较当前时间和键 lock.foo 的值来判断是否超时)
    2.P2和P3进程发现锁 lock.foo 已超时
    3.P2执行 DEL lock.foo命令
    4.P2执行 SETNX lock.foo命令,并返回1,即P2获得锁
    5.P3执行 DEL lock.foo命令将P2刚刚设置的键 lock.foo 删除(这步是由于P3刚才已检测到锁已超时)
    6.P3执行 SETNX lock.foo命令,并返回1,即P3获得锁
    7.P2和P3同时获得了锁

从上面的情况可以得知,在检测到锁超时后,进程不能直接简单地执行 DEL 删除键的操作以获得锁。

为了解决上述算法可能出现的多个进程同时获得锁的问题,我们再来看以下的算法。
我们同样假设进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。接下来的情况:

进程P4执行 SETNX lock.foo 以尝试获取锁
由于进程P1已获得了锁,所以P4执行 SETNX lock.foo 返回0,即获取锁失败
P4执行 GET lock.foo 来检测锁是否已超时,如果没超时,则等待一段时间,再次检测
如果P4检测到锁已超时,即当前的时间大于键 lock.foo 的值,P4会执行以下操作
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
由于 GETSET 操作在设置键的值的同时,还会返回键的旧值,通过比较键 lock.foo 的旧值是否小于当前时间,可以判断进程是否已获得锁
假如另一个进程P5也检测到锁已超时,并在P4之前执行了 GETSET 操作,那么P4的 GETSET 操作返回的是一个大于当前时间的时间戳,这样P4就不会获得锁而继续等待。注意到,即使P4接下来将键 lock.foo 的值设置了比P5设置的更大的值也没影响。
另外,值得注意的是,在进程释放锁,即执行 DEL lock.foo 操作前,需要先判断锁是否已超时。如果锁已超时,那么锁可能已由其他进程获得,这时直接执行 DEL lock.foo 操作会导致把其他进程已获得的锁释放掉。

程序代码

用以下python代码来实现上述的使用 SETNX 命令作分布式锁的算法。

LOCK_TIMEOUT = 3
lock = 0
lock_timeout = 0
lock_key = 'lock.foo'# 获取锁
while lock != 1:now = int(time.time())lock_timeout = now + LOCK_TIMEOUT + 1lock = redis_client.setnx(lock_key, lock_timeout)if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)):breakelse:time.sleep(0.001)# 已获得锁
do_job()# 释放锁
now = int(time.time())
if now < lock_timeout:redis_client.delete(lock_key)

java代码

while (timeout >= 0) {long expires = System.currentTimeMillis() + expireMsecs + 1;String expiresStr = String.valueOf(expires); //锁到期时间if (this.setNX(lockKey, expiresStr)) {// lock acquiredlocked = true;return true;}String currentValueStr = this.get(lockKey); //redis里的时间if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的// lock is expiredString oldValueStr = this.getSet(lockKey, expiresStr);//获取上一个锁到期时间,并设置现在的锁到期时间,//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {//防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受//[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁// lock acquiredlocked = true;return true;}}timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;/*延迟100 毫秒,  这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.使用随机的等待时间可以一定程度上保证公平性*/Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);}

http://blog.csdn.net/lihao21/article/details/49104695
http://www.cnblogs.com/0201zcr/p/5942748.html

作者:进击的老码农
链接:https://www.jianshu.com/p/c970cc71070b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

使用 Redis的SETNX命令实现分布式锁相关推荐

  1. 场景应用:Redis使用setnx命令实现分布式锁

    文章目录 何时需要分布式锁? 如何实现分布式锁? 探究单节点锁:setnx命令 1. 加锁: 2. 解锁: 带来的问题 探究分布式锁:RedLock算法 补充:setnx命令学习 何时需要分布式锁? ...

  2. 基于Redis(setnx)实现分布式锁

    什么是分布式锁? 分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性. 分布式锁需要具 ...

  3. Redis分布式锁/Redis的setnx命令如何设置key的失效时间(同时操作setnx和expire)

    Redis的setnx命令是当key不存在时设置key,但setnx不能同时完成expire设置失效时长,不能保证setnx和expire的原子性.我们可以使用set命令完成setnx和expire的 ...

  4. redis实现轮询算法_【07期】Redis中是如何实现分布式锁的?

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面 ...

  5. Redis 实现优惠券秒杀、分布式锁、消费队列

    文章目录 一.全局唯一ID 1.1 为啥需要全局唯一ID 1.2 全局ID生成器 1.3 全局唯一ID生成策略 二.实现优惠卷秒杀下单 2.1 优惠卷表结构 2.2 秒杀功能实现 三.超卖问题 四.实 ...

  6. 【redis】redis基础命令,分布式锁,缓存问题学习大集合

    写在前面 Redis是一个高速的内存数据库,它的应用十分广泛,可以说是服务端必学必精的东西.然而,学以致用,无用则无为.学了的东西必须反复的去用,去实践,方能有真知.这篇文章记录了我在redis学习过 ...

  7. 【面试题】Redis中是如何实现分布式锁的

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. Redis的分布式锁 Redis要实现分布式锁,以下条件应该得到满足 互斥性:在任意时刻, ...

  8. Redis系列教程(八):分布式锁的由来、及Redis分布式锁的实现详解

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢?今天来探讨分布式锁这个话题. ...

  9. Redis 基础 - 优惠券秒杀《分布式锁(初级)》

    参考 Redis基础 - 基本类型及常用命令 Redis基础 - Java客户端 Redis 基础 - 短信验证码登录 Redis 基础 - 用Redis查询商户信息 Redis 基础 - 优惠券秒杀 ...

最新文章

  1. Java常用工具类---IP工具类、File文件工具类
  2. FPM傅里叶叠层衍射成像笔记
  3. 免费指纹浏览器破解方法之开源代码
  4. 中国电梯行业动态调研与未来发展趋势展望报告2022-2028年版
  5. 中国移动系统集成公司2020春招技术综合在线编程题第二题
  6. 【软考-软件设计师】程序设计的基本成分
  7. [线段树][树上差分] Jzoj P3397 雨天的尾巴
  8. kill -9都杀不掉的进程
  9. 首发骁龙898!小米12系列屏幕方案曝光:双曲面屏+全新封装工艺
  10. w3wp oracle连接数高,分析案例:應用服務器W3WP進程CPU持續超過百分之九十(Oracle客戶端Bug)...
  11. 第151天:网页中插入百度地图方法(不需要密钥)
  12. 连续投影算法_腾讯优图 | 3D结构光摄像头深度算法综述
  13. 微信读书产品调研报告
  14. 数据库设计及案例分析
  15. Vue项目在页面添加水印功能
  16. 这篇文章记录平时学习、项目中遇到的问题(未解决的居多)
  17. 爬虫爬取豆瓣电影排行,保存到excel和SQLite
  18. hin2vec运行笔记代码导图笔记
  19. 多测师肖sir_高级讲师_第2个月第33讲解jenkins
  20. 祝贺 Influx Technology 成为北航 AERO 大学生方程式赛车队一级赞助商

热门文章

  1. Java中不常见的关键字 / 修饰符(volatile , native , transient)
  2. ai直线怎么变折线_如何在 AI中设计可编辑折线图
  3. Springboot与Ajax整合练习?
  4. centos php7 redis,CentOS7 yum快速安装php7.1+nginx+mysql+redis
  5. seata不兼容mysql8的解决方案
  6. java线程知识体系
  7. linux let s证书续期,BT(宝塔面板)Let’s Encrypt证书续签方法
  8. java多线程之yield
  9. maven学习(一)
  10. shell-script(command groups)