侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Redis上实现分布式锁以提高性能的方案研究

Redis  /  管理员 发布于 5年前   339

背景:

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

项目实践

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


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

1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放.

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

<?php require_once 'RedisFactory.php'; /*** 在 Redis 上实现的分布式锁*/class RedisLock {  //单例模式  private static $_instance = null;  public static function instance() {    if(self::$_instance == null) {      self::$_instance = new RedisLock();    }    return self::$_instance;  }   //redis对象变量  private $redis;  //存放被锁的标志名的数组  private $lockedNames = array();   public function __construct() {    //获取一个 RedisString 实例    $this->redis = RedisFactory::instance()->getString();  }   /**   * 加锁  *  * @param string 锁的标识名  * @param int 获取锁失败时的等待超时时间(秒), 在此时间之内会一直尝试获取锁直到超时. 为 0 表示失败后直接返回不等待  * @param int 当前锁的最大生存时间(秒), 必须大于 0 . 如果超过生存时间后锁仍未被释放, 则系统会自动将其强制释放  * @param int 获取锁失败后挂起再试的时间间隔(微秒)  */  public function lock($name, $timeout = 0, $expire = 15, $waitIntervalUs = 100000) {    if(empty($name)) return false;     $timeout = (int)$timeout;    $expire = max((int)$expire, 5);    $now = microtime(true);    $timeoutAt = $now + $timeout;    $expireAt = $now + $expire;     $redisKey = "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实现分布式锁了。


  • 上一条:
    redislive监控redis服务的图文教程
    下一条:
    redis与memcached的区别
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在Redis中能实现的功能、常见应用介绍(0个评论)
    • 2024年Redis面试题之一(0个评论)
    • 在redis缓存常见出错及解决方案(0个评论)
    • 在redis中三种特殊数据类型:地理位置、基数(cardinality)估计、位图(Bitmap)使用场景介绍浅析(2个评论)
    • Redis 删除 key用 del 和 unlink 有啥区别?(1个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-12
    • 2020-03
    • 2020-05
    • 2021-04
    • 2022-03
    • 2022-05
    • 2022-08
    • 2023-02
    • 2023-04
    • 2023-07
    • 2024-01
    • 2024-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客