LockHelper.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace common\helpers;
  3. class LockHelper
  4. {
  5. /**
  6. * 加锁
  7. * @param $key
  8. * @param $identification
  9. * @param int $expire
  10. * @param int $wait_time
  11. * @return bool
  12. */
  13. public static function lock($key,$identification,$expire=30,$wait_time = 10){
  14. $redis = \Yii::$app->redis;
  15. $hasWait = 0;//已经等待的时间:毫秒
  16. while(true){
  17. $res = $redis->set($key,$identification,'NX','EX',$expire);
  18. if ($res){
  19. return true;
  20. break;
  21. }else{
  22. if($hasWait > $wait_time*1000){
  23. //等待超时
  24. return false;
  25. }
  26. //sleep(10);
  27. usleep(300 * 1000);//等待300ms
  28. $hasWait += 300;
  29. continue;
  30. }
  31. }
  32. return false;
  33. }
  34. /**
  35. * 解锁
  36. * @param $key
  37. * @param $identification
  38. */
  39. public static function uLock($key,$identification){
  40. $redis = \Yii::$app->redis;
  41. $script = '
  42. if redis.call("get",KEYS[1]) == ARGV[1] then
  43. return redis.call("del",KEYS[1])
  44. else
  45. return 0
  46. end';
  47. $redis->eval($script,1,$key,$identification);
  48. }
  49. /**
  50. * 批量解锁
  51. * @DateTime 2021-11-10 17:06:37
  52. * @copyright: Copyright (c) 2020 广东七件事集团
  53. * @param [type] $lock_arr[$key=>$identification]
  54. * @return void
  55. */
  56. public static function batchUlock($lock_arr){
  57. foreach($lock_arr as $key => $identification){
  58. self::uLock($key,$identification);
  59. }
  60. }
  61. /**
  62. * @desc:阻塞读锁
  63. * @Author: hua
  64. * @Date: 2021/12/3
  65. * @Time: 19:24
  66. * @Copyright: copyright (c) 2021 广东七件事集团
  67. * @param $key
  68. * @param $wait_time
  69. * @return bool
  70. */
  71. public static function readLock($key,$wait_time = 30){
  72. $redis = \Yii::$app->redis;
  73. $hasWait = 0;//已经等待的时间:毫秒
  74. while(true){
  75. $res = $redis->get($key);
  76. if (!$res){
  77. return true;
  78. break;
  79. }else{
  80. if($hasWait >= $wait_time*1000){
  81. //等待超时
  82. return false;
  83. }
  84. usleep(300 * 1000);//等待300ms
  85. $hasWait += 300;
  86. continue;
  87. }
  88. }
  89. return false;
  90. }
  91. /**
  92. * 获取频繁锁使用Key
  93. *
  94. * @param string $key
  95. * @return string
  96. */
  97. protected static function getFkey(string $key)
  98. {
  99. return sprintf('fff%s', $key);
  100. }
  101. /**
  102. * Redis频繁锁
  103. * 使用Lua脚本设置一个过期值
  104. * 解锁可以使用unFLock
  105. * 返回false时候为可以继续执行, 返回true需要返回停止程序返回繁忙信息
  106. * 使用方式
  107. *
  108. * if (LockHelper::fLock($key, $seconds)) return $this->error('忙碌');
  109. * ---业务逻辑---
  110. * static::unFLock($key);
  111. *
  112. * @param string $key
  113. * @param integer $seconds 正常操作5秒锁够用 如果业务逻辑比较慢可以增加锁的时间
  114. * @return boolean
  115. */
  116. public static function fLock(string $key, int $seconds = 5)
  117. {
  118. $redis = \Yii::$app->redis;
  119. $script = <<<SCRIPT
  120. local _val = 1
  121. local _key = KEYS[1]
  122. local _seconds = KEYS[2]
  123. if redis.call("get", _key) then
  124. return 1
  125. else
  126. redis.call("setex", _key, _seconds, _val)
  127. return 0
  128. end
  129. SCRIPT;
  130. return (bool)$redis->eval($script, 2, static::getFkey($key), $seconds);
  131. }
  132. /**
  133. * 解除Redis频繁锁
  134. *
  135. * @param string $key
  136. * @return boolean
  137. */
  138. public static function unFLock(string $key)
  139. {
  140. $redis = \Yii::$app->redis;
  141. $script = <<<SCRIPT
  142. local _key = KEYS[1]
  143. return redis.call("del", _key)
  144. SCRIPT;
  145. return (bool)$redis->eval($script, 1, static::getFkey($key));
  146. }
  147. /**
  148. * 直接使用回调函数进行操作
  149. * 使用方式
  150. * try {
  151. * LockHelper::callFLock($key, function() use (...) {
  152. * ---业务逻辑---
  153. * }, $seconds);
  154. * } catch (\Exception $e) {
  155. * return $this->error('忙碌');
  156. * }
  157. *
  158. * @param string $key
  159. * @param callable $func
  160. * @param integer $seconds
  161. * @return void
  162. * @throws \Exception
  163. */
  164. public static function callFLock(string $key, callable $func, int $seconds = 5)
  165. {
  166. if (static::fLock($key, $seconds)) throw new \Exception('请勿频繁操作');
  167. call_user_func($func);
  168. static::unFLock($key);
  169. }
  170. }