使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法。

SETNX命令简介

命令格式

SETNX key value 

将 key 的值设为 value,当且仅当 key 不存在。 
若给定的 key 已经存在,则 SETNX 不做任何动作。 
SETNX 是SET if Not eXists的简写。

返回值

返回整数,具体为 
- 1,当 key 的值被设置 
- 0,当 key 的值没被设置

例子

redis> SETNX mykey “hello”
(integer) 1
redis> SETNX mykey “hello”
(integer) 0
redis> GET mykey
“hello”
redis>

  

使用SETNX实现分布式锁

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

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

  

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

解决死锁

考虑一种情况,如果进程获得锁后,断开了与 Redis 的连接(可能是进程挂掉,或者网络中断),如果没有有效的释放锁的机制,那么其他进程都会处于一直等待的状态,即出现“死锁”。

上面在使用 SETNX 获得锁时,我们将键 lock.foo 的值设置为锁的有效时间,进程获得锁后,其他进程还会不断的检测锁是否已超时,如果超时,那么等待的进程也将有机会获得锁。

然而,锁超时时,我们不能简单地使用 DEL 命令删除键 lock.foo 以释放锁。考虑以下情况,进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。进程P2,P3正在不断地检测锁是否已释放或者已超时,执行流程如下:

  • P2和P3进程读取键 lock.foo 的值,检测锁是否已超时(通过比较当前时间和键 lock.foo 的值来判断是否超时)
  • P2和P3进程发现锁 lock.foo 已超时
  • P2执行 DEL lock.foo命令
  • P2执行 SETNX lock.foo命令,并返回1,即P2获得锁
  • P3执行 DEL lock.foo命令将P2刚刚设置的键 lock.foo 删除(这步是由于P3刚才已检测到锁已超时)
  • P3执行 SETNX lock.foo命令,并返回1,即P3获得锁
  • 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 + 1
lock = 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)):
break
else:
time.sleep(0.001) # 已获得锁
do_job() # 释放锁
now = int(time.time())
if now < lock_timeout:
redis_client.delete(lock_key)

  

参考资料

使用 Redis的SETNX命令实现分布式锁的更多相关文章

  1. 使用Redis SETNX 命令实现分布式锁

    基于setnx和getset http://blog.csdn.net/lihao21/article/details/49104695 使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其 ...

  2. 使用Redis SETNX 命令实现分布式锁(转载)

    使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法. SETNX命令简介 命令格式 SETNX key value 将 key 的值设为 value,当且仅当 key 不存在. 若 ...

  3. java-spring基于redis单机版(redisTemplate)实现的分布式锁+redis消息队列,可用于秒杀,定时器,高并发,抢购

    此教程不涉及整合spring整合redis,可另行查阅资料教程. 代码: RedisLock package com.cashloan.analytics.utils; import org.slf4 ...

  4. python使用redis实现协同控制的分布式锁

    python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...

  5. 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  6. 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  7. Redis中是如何实现分布式锁的?

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Red ...

  8. 为什么Redis可以方便地实现分布式锁

    1.Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系. 2.Redis的SETNX命令可以方便的实现分布式锁. setNX(SET if  ...

  9. Redis的“假事务”与分布式锁

    关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...

随机推荐

  1. 今天看到的一篇文章:一位资深程序员大牛给予Java初学者的学习路线建议

    一位资深程序员大牛给予Java初学者的学习路线建议 持续学习!

  2. jquery图片滚动jquery.scrlooAnimation.js

    ;(function ($, window, document, undefined) { var pluginName = "scrollAnimations", /** * T ...

  3. Git 项目提交代码及一些常用命令

    在dev_ysg分支 : git add . //把项目添加到仓库 git commit -m "test" // 提交加注释 git push //推到dev_ysg分支上去 g ...

  4. 解决url传递过程中加号变空格的问题

    url传递过程中加号变空格 在接收url参数的过程中,会发现如果参数中存在‘+’号,接收后会变成空格. 如11+22接收后变成11 22. 要解决这个问题,需要将加号替换为%2B进行传递. 如11%2 ...

  5. Elasticsearch 常用API

    1.   Elasticsearch 常用API 1.1.数据输入与输出 1.1.1.Elasticsearch 文档   #在 Elasticsearch 中,术语 文档 有着特定的含义.它是指最顶 ...

  6. Python基础02

    6.变量定义的规则: # 变量,只能由 字母 数字 下划线 特例:不能用数字开头 Python关键字,也不能使用 'and', 'as', 'assert', 'break', 'class', 'c ...

  7. 07 json与os模块(进阶)

    json和os模块 阶段一 .数据交换 1.json的基本介绍 JSON全名是JavaScript Object Notation(即:JavaScript对象标记)它是JavaScript的子集. ...

  8. 人人都会设计模式:观察者模式--Observer

    https://segmentfault.com/a/1190000012295887 观察者模式是抽像通知者和观察者,达到具体通知者跟具体观察者没有偶合.能达到不管是切换通知者,或者是切换观察者,都 ...

  9. (数据科学学习手札04)Python与R在自定义函数上的异同

    自编函数是几乎每一种编程语言的基础功能,有些时候我们需要解决的问题可能没有完全一致的包中的函数来进行解决,这个时候自编函数就成了一样利器,而Python与R在这方面也有着一定的差别,下面举例说明: P ...

  10. RedHat6.4 安装yum源

    主要参考: http://blog.itpub.net/25313300/viewspace-708509/ http://blog.sina.com.cn/s/blog_50f908410101ct ...