背景:
在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分是解决方案基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。
项目实践
任务队列用到分布式锁的情况比较多,在将业务逻辑中可以异步处理的操作放入队列,在其他线程中处理后出队,此时队列中使用了分布式锁,保证入队和出队的一致性。关于redis队列这块的逻辑分析,我将在下一次对其进行总结,此处先略过。
接下来对redis实现的分布式锁的逻辑代码进行详细的分析和理解:
1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放.
2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire
3、系统级的锁当进程无论因为任何原因出现crash,操作系统会自己回收锁,所以不会出现资源丢失。
4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁,导致其他进程或进程重启后无法进入加锁区域。
<"Lock:$name"; while(true) { $result = $this->redis->setnx($redisKey, (string)$expireAt); if($result !== false) { //对$redisKey设置生存时间 $this->redis->expire($redisKey, $expire); //将最大生存时刻记录在一个数组里面 $this->lockedNames[$name] = $expireAt; return true; } //以秒为单位,返回$redisKey 的剩余生存时间 $ttl = $this->redis->ttl($redisKey); // TTL 小于 0 表示 key 上没有设置生存时间(key 不会不存在, 因为前面 setnx 会自动创建) // 如果出现这种情况, 那就是进程在某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用. 这时可以直接设置 expire 并把锁纳为己用 if($ttl < 0) { $this->redis->set($redisKey, (string)$expireAt, $expire); $this->lockedNames[$name] = $expireAt; return true; } // 设置了不等待或者已超时 if($timeout <= 0 || microtime(true) > $timeoutAt) break; // 挂起一段时间再试 usleep($waitIntervalUs); } return false; } /** * 给当前锁增加指定的生存时间(秒), 必须大于 0 * * @param string 锁的标识名 * @param int 生存时间(秒), 必须大于 0 */ public function expire($name, $expire) { if($this->isLocking($name)) { if($this->redis->expire("Lock:$name", max($expire, 1))) { return true; } } return false; } /** * 判断当前是否拥有指定名称的锁 * * @param mixed $name */ public function isLocking($name) { if(isset($this->lockedNames[$name])) { return (string)$this->lockedNames[$name] == (string)$this->redis->get("Lock:$name"); } return false; } /** * 释放锁 * * @param string 锁的标识名 */ public function unlock($name) { if($this->isLocking($name)) { if($this->redis->deleteKey("Lock:$name")) { unset($this->lockedNames[$name]); return true; } } return false; } /** 释放当前已经获取到的所有锁 */ public function unlockAll() { $allSuccess = true; foreach($this->lockedNames as $name => $item) { if(false === $this->unlock($name)) { $allSuccess = false; } } return $allSuccess; } }
此类很多代码都写上了注释,只要认真理解下,就很容易懂得如何在redis实现分布式锁了。
Redis,分布式锁
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。