转载Redis 上实现的分布式锁

由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客。这一个月里面接触到很多新知识,同时也遇到很多技术上的难点,在这我将对每一个有用的技术点做一个小小的分析理解和总结。每天去学会总结,才会有进步。

  本次对我在工作上的项目中用到的技术---在redis上实现分布式锁,进行一个分析和总结。

  先了解下什么时分布式锁,在百度上是这么定义的:

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

  简单的理解就是:分布式锁是一个在很多环境中非常有用的原语,它是不同的系统或是同一个系统的不同主机之间互斥操作共享资源的有效方法。

  背景:

    在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分是解决方案基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。

  我们的项目:

  我们现在的项目中,任务队列用到分布式锁的情况比较多,在将业务逻辑中可以异步处理的操作放入队列,在其他线程中处理后出队,此时队列中使用了分布式锁,保证入队和出队的一致性。关于redis队列这块的逻辑分析,我将在下一次对其进行总结,此处先略过。

  接下来对redis实现的分布式锁的逻辑代码进行详细的分析和理解:

  1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放.
  2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire
  3、系统级的锁当进程无论因为任何原因出现crash,操作系统会自己回收锁,所以不会出现资源丢失。
  4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁,导致其他进程或进程重启后无法进入加锁区域。

  1. 1 <?php
  2. 2
  3. 3 require_once 'RedisFactory.php';
  4. 4
  5. 5 /**
  6. 6 * 在 Redis 上实现的分布式锁
  7. 7 */
  8. 8 class RedisLock {
  9. 9 //单例模式
  10. 10 private static $_instance = null;
  11. 11 public static function instance() {
  12. 12 if(self::$_instance == null) {
  13. 13 self::$_instance = new RedisLock();
  14. 14 }
  15. 15 return self::$_instance;
  16. 16 }
  17. 17
  18. 18 //redis对象变量
  19. 19 private $redis;
  20. 20 //存放被锁的标志名的数组
  21. 21 private $lockedNames = array();
  22. 22
  23. 23 public function __construct() {
  24. 24 //获取一个 RedisString 实例
  25. 25 $this->redis = RedisFactory::instance()->getString();
  26. 26 }
  27. 27
  28. 28 /**
  29. 29 * 加锁
  30. 30 *
  31. 31 * @param string 锁的标识名
  32. 32 * @param int 获取锁失败时的等待超时时间(秒), 在此时间之内会一直尝试获取锁直到超时. 为 0 表示失败后直接返回不等待
  33. 33 * @param int 当前锁的最大生存时间(秒), 必须大于 0 . 如果超过生存时间后锁仍未被释放, 则系统会自动将其强制释放
  34. 34 * @param int 获取锁失败后挂起再试的时间间隔(微秒)
  35. 35 */
  36. 36 public function lock($name, $timeout = 0, $expire = 15, $waitIntervalUs = 100000) {
  37. 37 if(empty($name)) return false;
  38. 38
  39. 39 $timeout = (int)$timeout;
  40. 40 $expire = max((int)$expire, 5);
  41. 41 $now = microtime(true);
  42. 42 $timeoutAt = $now + $timeout;
  43. 43 $expireAt = $now + $expire;
  44. 44
  45. 45 $redisKey = "Lock:$name";
  46. 46 while(true) {
  47. 47 $result = $this->redis->setnx($redisKey, (string)$expireAt);
  48. 48 if($result !== false) {
  49. 49 //对$redisKey设置生存时间
  50. 50 $this->redis->expire($redisKey, $expire);
  51. 51 //将最大生存时刻记录在一个数组里面
  52. 52 $this->lockedNames[$name] = $expireAt;
  53. 53 return true;
  54. 54 }
  55. 55
  56. 56 //以秒为单位,返回$redisKey 的剩余生存时间
  57. 57 $ttl = $this->redis->ttl($redisKey);
  58. 58 // TTL 小于 0 表示 key 上没有设置生存时间(key 不会不存在, 因为前面 setnx 会自动创建)
  59. 59 // 如果出现这种情况, 那就是进程在某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用. 这时可以直接设置 expire 并把锁纳为己用
  60. 60 if($ttl < 0) {
  61. 61 $this->redis->set($redisKey, (string)$expireAt, $expire);
  62. 62 $this->lockedNames[$name] = $expireAt;
  63. 63 return true;
  64. 64 }
  65. 65
  66. 66 // 设置了不等待或者已超时
  67. 67 if($timeout <= 0 || microtime(true) > $timeoutAt) break;
  68. 68
  69. 69 // 挂起一段时间再试
  70. 70 usleep($waitIntervalUs);
  71. 71 }
  72. 72
  73. 73 return false;
  74. 74 }
  75. 75
  76. 76 /**
  77. 77 * 给当前锁增加指定的生存时间(秒), 必须大于 0
  78. 78 *
  79. 79 * @param string 锁的标识名
  80. 80 * @param int 生存时间(秒), 必须大于 0
  81. 81 */
  82. 82 public function expire($name, $expire) {
  83. 83 if($this->isLocking($name)) {
  84. 84 if($this->redis->expire("Lock:$name", max($expire, 1))) {
  85. 85 return true;
  86. 86 }
  87. 87 }
  88. 88 return false;
  89. 89 }
  90. 90
  91. 91 /**
  92. 92 * 判断当前是否拥有指定名称的锁
  93. 93 *
  94. 94 * @param mixed $name
  95. 95 */
  96. 96 public function isLocking($name) {
  97. 97 if(isset($this->lockedNames[$name])) {
  98. 98 return (string)$this->lockedNames[$name] == (string)$this->redis->get("Lock:$name");
  99. 99 }
  100. 100 return false;
  101. 101 }
  102. 102
  103. 103 /**
  104. 104 * 释放锁
  105. 105 *
  106. 106 * @param string 锁的标识名
  107. 107 */
  108. 108 public function unlock($name) {
  109. 109 if($this->isLocking($name)) {
  110. 110 if($this->redis->deleteKey("Lock:$name")) {
  111. 111 unset($this->lockedNames[$name]);
  112. 112 return true;
  113. 113 }
  114. 114 }
  115. 115 return false;
  116. 116 }
  117. 117
  118. 118 /** 释放当前已经获取到的所有锁 */
  119. 119 public function unlockAll() {
  120. 120 $allSuccess = true;
  121. 121 foreach($this->lockedNames as $name => $item) {
  122. 122 if(false === $this->unlock($name)) {
  123. 123 $allSuccess = false;
  124. 124 }
  125. 125 }
  126. 126 return $allSuccess;
  127. 127 }
  128. 128 }

  此类很多代码都写上了注释,只要认真理解下,就很容易懂得如何在redis实现分布式锁了。

  另外,我在网上找到另一篇关于redis实现分布式锁的文章,我感觉挺不错的,推荐给大家:

  网址: http://www.oschina.net/translate/redis-distlock

  结合我所总结的和我推荐的文章做对比,基本上能理解清楚是如何在redis实现分布式锁的了。

  如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。

Redis 上实现的分布式锁的更多相关文章

  1. 在 Redis 上实现的分布式锁

    由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客.这一个月里面接触到很多新知识,同时也遇到很多技术上的难点,在这我将对每一个有用 ...

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

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

  3. Redis整合Spring实现分布式锁

    spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis是对Redis的封装 <dependencies> <!-- 添加spring- ...

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

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

  5. 【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁

    一.背景 在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以 ...

  6. 基于 Redis 实现简单的分布式锁

    摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...

  7. 基于Redis实现简单的分布式锁【理论】

    摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...

  8. Redis、Zookeeper实现分布式锁——原理与实践

    Redis与分布式锁的问题已经是老生常谈了,本文尝试总结一些Redis.Zookeeper实现分布式锁的常用方案,并提供一些比较好的实践思路(基于Java).不足之处,欢迎探讨. Redis分布式锁 ...

  9. 基于redis实现可靠的分布式锁

    什么是锁 今天要谈的是如何在分布式环境下实现一个全局锁,在开始之前先说说非分布式下的锁: 单机 – 单进程程序使用互斥锁mutex,解决多个线程之间的同步问题 单机 – 多进程程序使用信号量sem,解 ...

随机推荐

  1. java 加载dll介绍(转)

    最近在做的工作要用到本地方法,需要在Java中加载不少动态链接库(以下为方便延用Windows平台下的简写dll,但并不局限于Windows).刚刚把程序跑通,赶紧把一些心得写出来,mark.也希望对 ...

  2. Android - 和其他APP交互 - 让其他app启动你的activity

    前面的两篇文章主要讲了一个方面:从app中启动其他app.但是如果你的app可以处理对其他app有用的操作,你的app也应该响应其他app的操作请求.例如,如果你创建了一个社交app可以分享信息和图片 ...

  3. MapReduce源代码分析MapTask分析

    前言 MapReduce该分析是基于源代码Hadoop1.2.1代码分析进行的基础上. 该章节会分析在MapTask端的详细处理流程以及MapOutputCollector是怎样处理map之后的col ...

  4. 解决IIS7中出现An error occurred on the server when processing the URL错误提示的方法

    在IIS7上配置一个asp程序,出现了一个错如提示: An error occurred on the server when processing the URL. Please contact t ...

  5. 无显示仍然发挥树莓派——VNCserver设定

    谁说没有显示器就不能玩树莓派的图形界面了.不要忘了VNCserver哦! VNC(Virtual Network Computing)属于一种网络显示系统,也就是说它能将完整的窗体界面通过网络传输到还 ...

  6. SQLServer 扫盲

    原文:SQLServer 扫盲 谨以本文记录本人成长历程,并分享给各位SQL Server数据库管理系统使用者.本系列包含个人认为一个DBA应该具有的各项素质,系列文章将以下面列表展示,将持续更新,敬 ...

  7. ubuntu安装jdk eclipse mysql等

    linux ubuntu下安装java web开发环境,需要安装包: jdk7 eclipse(选择java EE developer) apche-tomcat mysql(workbench可视化 ...

  8. NSIS:使用FileFunc.nsh头文件判断文件版本

    原文 NSIS:使用FileFunc.nsh头文件判断文件版本 这里,轻狂拿WMP10做一个例子.关于WMP10的原始安装文件,可以下载后通过/C /T:D:\Windows Media Player ...

  9. Git是个好工具(转)

    Git是分布式版本控制系统,我们常用的版本控制工具还有SVN.这里就得区分下什么是分布式版本控制系统,什么是集中化的版本控制系统. 集中化的版本控制系统 集中化的版本控制系统( Centralized ...

  10. TinyMCE实现简单的本地上传

    TinyMCE这个东西很多地方再用,不过我以前一直没用过,最近才接触,因为有一套现成的metro风格的皮肤,仅此而已,不过最终如何调用还是我得来实现.其他的都好说,网上的资料一大把一大把的,唯独这个本 ...