精华内容
下载资源
问答
  • PHP Redis分布式锁

    2020-04-29 15:14:17
    PHP 使用 Redis 实现分布式锁 php redis 分布式锁 更新于 2019-06-05 约 10 分钟 Last-Modified: 2019年6月5日15:59:34 参考链接 PHP使用Redis+Lua脚本操作注意事项 《Redis官方文档》用Redis构建分布式...

    PHP 使用 Redis 实现分布式锁

    参考链接

    锁实现的注意点

    1. 互斥: 任意时刻, 只能有一个客户端获得锁
    2. 不会死锁: 客户端持有锁期间崩溃, 没有主动解除锁, 能保证后续的其他客户端获得锁
    3. 锁归属标识: 加锁和解锁的必须是同一个客户端, 客户端不能解掉非自己持有的锁(锁应具备标识)

    如果是Redis集群, 还得考虑具有容错性: 只要大部分Redis节点正常运行, 客户端就可以加锁和解锁.

    以下只考虑 Redis单机部署的 场景.

    如果是Redis集群部署, 可以使用

    加锁

    php 加锁示例

    $redis = new Redis();
    $redis->pconnect("127.0.0.1", 6379);
    $redis->auth("password");    // 密码验证
    $redis->select(1);    // 选择所使用的数据库, 默认有16个
    
    $key = "...";
    $value = "...";
    $expire = 3;
    
    // 参数解释 ↓
    // $value 加锁的客户端请求标识, 必须保证在所有获取锁清秋的客户端里保持唯一, 满足上面的第3个条件: 加锁/解锁的是同一客户端
    // "NX" 仅在key不存在时加锁, 满足条件1: 互斥型
    // "EX" 设置锁过期时间, 满足条件2: 避免死锁
    $redis->set($key, $value, ["NX", "EX" => $expire])

    执行上面代码结果:

    1. $key 对应的锁不存在, 进行加锁操作
    2. $key 对应的锁已存在, 什么也不做

    加锁容易错误的点:

    • 使用 setnxexpire 的组合

      原因: 若在 setnx 后脚本崩溃会导致死锁

    $value 客户端标识的:

    • 简单点就用 毫秒级unix时间戳 + 客户端标识(大部分情况下够用了)
    • 使用其他算法确保生成唯一随机值

    connect 与 pconnect

    在php中, 若使用 pconnect 连接redis, 则在当前脚本声明周期结束后, 与redis建立的连接仍会保留, 直到对应fpm进程的生命周期结束, 同时在下一次请求时, fpm会重用该连接.

    即该连接的生命周期是 fpm 进程的生命周期, 而非一次php脚本的执行.

    若代码使用 pconnect, close 的作用仅是使当前php脚本不能再进行redis请求, 并没有真正关闭与redis的连接, 连接在后续请求中仍然会被重用.

    pconnect函数在线程版本中不能被使用

    clipboard.png

    clipboard.png

    上图中, php-fpm 与redis建立的连接并未随请求结束后马上断开

    解锁

    php解锁示例: 使用lua脚本

    $key = "...";
    $identification = "...";
    // KEYS 和 ARGV 是lua脚本中的全局变量
    $script = <<< EOF
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    else
        return 0
    end
    EOF;
    # $result = $redis->eval($script, [$key, $identification], 1);
    // 返回结果 >0 表示解锁成功
    // php中参数的传递顺序与标准不一样, 注意区分
    // 第2个参数表示传入的 KEYS 和 ARGV, 通过第3个参数来区分, KEYS 在前, ARGV 在后
    // 第3个参数表示传入的 KEYS 的个数
    $result = $redis->eval($script, [$key, $identification], 1);    

    使用Lua脚本的原因:

    • 避免误删其他客户端加的锁

      eg. 某个客户端获取锁后做其他操作过久导致锁被自动释放, 这时候要避免这个客户端删除已经被其他客户端获取的锁, 这就用到了锁的标识.
    • lua 脚本中执行 getdel 是原子性的, 整个lua脚本会被当做一条命令来执行
    • 即使 get 后锁刚好过期, 此时也不会被其他客户端加锁
    eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。

    由于 script 执行的原子性, 所以不要在script中执行过长开销的程序,否则会验证影响其它请求的执行。

    解锁容易错误的点:

    • 直接 del 删除键

      原因: 可能移除掉其他客户端加的锁(在自己的锁已过期情况下)

    • get判断锁归属, 若符合再 del

      原因: 非原子性操作, 若在 get 后锁过期了, 此时别的客户端进行加锁操作, 这里的 del 就会错误的将其他客户端加的锁解开.

    Redis 中使用 Lua 脚本的注意点

    ↓ 这一段内容转载自 https://blog.csdn.net/zhouzme...

    注意点:

    1. Redis 会把所有执行过的脚本都缓存在内存中
    2. Redis 在重启的时候会释放掉之前保存的脚本
    3. Lua 脚本中所需要用到的键名以及参数一定要使用 KEYS 和 ARGV 来替换,千万不要写死在代码中,除非你百分百确定每次请求时他们是固定不变的值,特别是涉及到 时间,随机数的,一定要用参数代入,因为 Redis 每次使用 script 都会校验脚本缓存中是否已存在相同脚本,否则就会存储到缓存中,如果你的脚本很长,且每次请求存在不同的变量值,则会生成无数多个脚本缓存,你将会发现Redis占用的内存会唰唰唰的往上涨,我一开始因为key 和 参数太多,分开写太麻烦了,就图省事方便,直接把变量拼接到脚本里面,结果发现内存不停的涨,很是抓狂,找了好久才发现是这么个原因。

      clipboard.png

    义变量一定要使用局部变量, 即 local var = 1, 局部变量只在所定义的块(指控制结构, 函数或chunk等)内有效, 使用局部变量可以避免命名冲突 并且访问更快(lua中局部变量和全局变量存储方式是不一样的)

    1. 如果Lua脚本写的比较长,非本地或局域网的情况下,建议使用 SHA 签名的方法来调用,这样节省带宽,但对性能似乎没什么直接的提升。这里对小白普及下我理解的原理就是 Redis 会把每个脚本都生成唯一签名,把脚本作为函数体,并使用该签名作为脚本的函数名放到缓存中,所以后面调用就只需要传一个 SHA 签名就可以调用该函数了,精简很多了。同一个脚本生成的签名都是相同的,所以SHA签名可以先在本地生成,然后在服务器上 script load 一次脚本,程序中只需保存和使用该签名即可。另外需要注意的是,脚本如果被改动哪怕一个换行或一个空格(这些容易被忽略或误操作)都必须重新 load 来获取新的 SHA

      注意:获取 SHA 签名是单独的功能,不要放在你的正常流程中,当本地开发时就可以生成SHA,把字符串写死在流程中。同样的脚本,Reids是始终生成相同的签名的。

      clipboard.png

    2. 通过 eval 带入的 ARGV 参数如果原来是数字的,会被转换为字符串,如果你的逻辑中需要判断该变量 > 0 或 < 0 之类的数字判断则必须进行字符串到数字的转换,使用 tonumber() 方法 if (tonumber(ARGV[1]) > 0) then return 1; end;
    3. 我测试了几个 lua script 与 PIPELINE 处理对比,发现 script 的效率一般比 PIPELINE 高 30% ~ 40% 左右

      clipboard.png

    Redis集群分布式锁

    Redis 集群相对单机来说, 需要考虑一个 容错性, 设计上更为复杂

    由于这个我也从未实践过, 先贴一个官方的教程贴压压惊

    https://github.com/antirez/re...

    对应的翻译: http://ifeve.com/redis-lock/

    RedLock 算法

    官方给出了一个 RedLock 算法

    情景: 当前有N个完全独立的Redis master节点, 分别部署在不同的主机上

    客户端获取锁的操作:

    1. 使用相同key和唯一值(作为value)同时向这N个redis节点请求锁, 锁的超时时间应该 >> 超时时间(考虑到请求耗时), 若某个节点阻塞了了应尽快跳过
    2. 计算步骤1消耗的时间, 若总消耗时间超过超时时间, 则认为锁失败. 客户端需在大多数(超过一半)的节点上成功获取锁, 才认为是锁成功.
    3. 如果锁成功了, 则该锁有效时间就是 锁原始有效时间 - 步骤1消耗的时间
    4. 如果锁失败了(超时或无法获取超过一半 N/2 + 1 实例的锁), 客户端会到每个节点释放锁(是每个, 即使之前认为加锁失败的节点
    展开全文
  • PHP-Redis实现分布式乐观,悲观

    千次阅读 2021-04-23 16:38:01
    乐观悲观使用场景请看这篇 乐观悲观使用场景 一: 悲观 特性: 1:互斥性,一个线程获取后其他不能获取,等待或者返回false 2:不能死锁,超时应该自动释放 3:应该只能由加锁人解锁 下面是redis 实现伪代码 /...

    乐观锁悲观锁使用场景请看这篇
    乐观锁悲观锁使用场景

    一: 悲观锁

    特性:
    1:互斥性,一个线程获取后其他不能获取,等待或者返回false
    2:不能死锁,超时应该自动释放
    3:锁应该只能由加锁人解锁

    下面是redis 实现的伪代码

    /**
     * Class RedisLock
     * @package app\common\model
     */
    class RedisLock  {
        private $redis;
        private $lock;
        public function __construct($redis)
        {
            $this->redis=$redis;
        }
    
        /**
         * @param $key 锁名
         * @param int $overtime 锁超时时间单位秒
         * @param int $waittime 等待超时时间,可能会阻塞线程,单位秒
         */
        public function lock($key,$overtime=5,$waittime=0){
            $time=microtime(true);
            //用随机字符串防止被别的线程删除
            $this->lock=rand_string(16);
    
    
            //参数 nx 代表key不存在才操作, ex 缓存有效时间
            $ok = $this->redis->set($key, $this->lock, array('nx', 'ex' => $overtime));
            if ($ok) {
                return $this->lock;
            }
    
            //循环获取,默认不等待
            while (microtime(true)-$time>$waittime){
                //睡眠1ms
                usleep(1000);
                //参数 nx 代表key不存在才操作, ex 缓存有效时间
                $ok = $this->redis->set($key, $this->lock, array('nx', 'ex' => $overtime));
                if ($ok) {
                    return $this->lock;
                }
            }
            return false;
    
        }
    
        /**
         * @param $key 加锁的key
         * @return bool
         */
        public function unlock($key){
            if (!empty($this->lock)){
                $lock=$this->redis->get($key);
                if (!empty($lock)&&$lock==$this->lock){
                   return $this->redis->del($key);
                }
            }
            return false;
        }
    
    }
    

    PS:Redis 可以使用lua 脚本实现多个命令的原子性,详情自行搜索

    二:乐观锁

    特性:
    1:不锁定资源
    2:在提交时才检验数据是否被修改

    乐观锁实现伪代码

    public function lgLock($key){
    		//获取原始数据
            $data=$redis->get($key);
            //添加观测
            $redis->watch($key);
            //事务
            $redis->multi();
            //do something
    
            //简单粗暴,原始数据没有被修改直接更新,修改了直接失败
           return $redis->exec();
    
        }
    

    redis乐观锁使用注意看这里
    Redis 乐观锁watch,multi 顺序

    展开全文
  • zookeeper和redis实现分布式锁的对比: 1、redis分布式锁,其实需要自己不断去尝试获取,比较消耗性能;zk分布式锁,获取不到,注册个监听器即可,不需要不断主动尝试获取,性能开销较小 2、如果是redis获取...

    php实现zookeeper分布式锁
    半人猛犸 2019-08-15 19:23:58 438 收藏 3
    展开

    php实现zookeeper分布式锁

    zookeeper和redis实现分布式锁的对比:
    1、redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
    2、如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁。

    分布式锁原理
    这个主要得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

    1、保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

    2、控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时顺序节点。Zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

    获取锁
    方法1:创建一个临时节点
        在需要获取排他锁时,所有的客户端都会试图通过调用 create -e 接口,在/distribute_lock节点下创建临时子节点
        /distribute_lock/lock。 ZooKeeper会保证在所有的客户端中,最络只有一个客户端能够创建成功,那么就可以认为该客户端
        获取了锁。同时,所有没有获取到锁的客户端就需要对 /distribute_lock/lock 节点上注册一个Watcher监听,以便实时监听到
        lock节点的变更情况。如果节点被使用完删除了,zookeeper要向所有监听者发送通知,这会阻塞其他操作,并且会导致所有客户端来
        争抢锁,这种情况称为“羊群效应”,试想一下,如果监听者众多的话,会拖累性能。

    方法2:创建临时顺序节点
        create -s -e /distribute_lock/lock- data
        1、每个试图加锁的客户端都会创建一个临时顺序节点 /distribute_lock/lock-xxxxx,并且zk可以保证序号连续且唯一;
        2、然后获取 /distribute_lock/ 下的所有子节点,并按从小到大排序list;
        3、判断最小节点是不是自己,如果是,证明你就获取锁了,可以去处理业务逻辑了;
        4、如果不是,获取到list中你的上一个节点名称(不一定是 -1 的那一个,因为此时它对应的客户端有可能主动放弃了),对其实施
        监听操作 get /distribute_lock/lock-xxxxx watch 如果get监听失败了,说明节点已经别清除了,重复 2,3 直到监听成功
        或者获取锁,如果监听成功,就在这里阻塞,等待通知;
        5、如果通知过来了,重复 2,3,4 的步骤,直到获取锁,因为上一个节点被释放的原因并不一定是它得到锁-使用完-释放,有可能
        是客户端断开连接了;
        6、锁用完后记得主动清除,不然要等到心跳检测的时候才会清除。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

    对比可以看出,方法2虽然比方法1麻烦一点,但是更加合理。
    代码实现:
    test-zookeeper.php

    <?php

    /*
     * zookeeper 类属性常量参考
     * https://www.php.net/manual/zh/class.zookeeper.php#zookeeper.class.constants.perms
     */

    class zkCli {
        protected static $zk;
        protected static $myNode;
        protected static $isNotifyed;
        protected static $root;

        public static function getZkInstance($conf, $root){
            try{

                if(isset(self::$zk)){
                    return self::$zk;
                }

                $zk = new \Zookeeper($conf['host'] . ':' . $conf['port']);
                if(!$zk){
                    throw new \Exception('connect zookeeper error');
                }

                self::$zk = $zk;
                self::$root = $root;

                return $zk;
            } catch (\ZookeeperException $e){
                die($e->getMessage());
            } catch (\Exception $e){
                die($e->getMessage());
            }
        }

        // 获取锁
        public static function tryGetDistributedLock($lockKey, $value){
            try{
                // 创建根节点
                self::createRootPath($value);
                // 创建临时顺序节点
                self::createSubPath(self::$root . $lockKey, $value);
                // 获取锁
                return self::getLock();

            } catch (\ZookeeperException $e){
                return false;
            } catch (\Exception $e){
                return false;
            }
        }

        // 释放锁
        public static function releaseDistributedLock(){
            if(self::$zk->delete(self::$myNode)){
                return true;
            }else{
                return false;
            }
        }

        public static function createRootPath($value){
            $aclArray = [
                [
                    'perms'  => Zookeeper::PERM_ALL,
                    'scheme' => 'world',
                    'id'     => 'anyone',
                ]
            ];
            // 判断根节点是否存在
            if(false == self::$zk->exists(self::$root)){
                // 创建根节点
                $result = self::$zk->create(self::$root, $value, $aclArray);
                if(false == $result){
                    throw new \Exception('create '.self::$root.' fail');
                }
            }

            return true;
        }

        public static function createSubPath($path, $value){
            // 全部权限
            $aclArray = [
                [
                    'perms'  => Zookeeper::PERM_ALL,
                    'scheme' => 'world',
                    'id'     => 'anyone',
                ]
            ];
            /**
             * flags :
             * 0 和 null 永久节点,
             * Zookeeper::EPHEMERAL临时,
             * Zookeeper::SEQUENCE顺序,
             * Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE 临时顺序
             */
            self::$myNode = self::$zk->create($path, $value, $aclArray, Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE);
            if(false == self::$myNode){
                throw new \Exception('create -s -e '.$path.' fail');
            }
            echo 'my node is ' . self::$myNode.'-----------'.PHP_EOL;

            return true;
        }

        public function getLock(){
            // 获取子节点列表从小到大,显然不可能为空,至少有一个节点
            $res = self::checkMyNodeOrBefore();
            if($res === true){
                return true;
            }else{
                self::$isNotifyed = false;// 初始化状态值
                // 考虑监听失败的情况:当我正要监听before之前,它被清除了,监听失败返回 false
                $result = self::$zk->get($res, [zkCli::class, 'watcher']);
                while(!$result){
                    $res1 = self::checkMyNodeOrBefore();
                    if($res1 === true){
                        return true;
                    }else{
                        $result = self::$zk->get($res1, [zkCli::class, 'watcher']);
                    }
                }

                // 阻塞,等待watcher被执行,watcher执行完回到这里
                while(!self::$isNotifyed){
                    echo '.';
                    usleep(500000); // 500ms
                }
                
                return true;
            }
        }

        /**
         * 通知回调处理
         * @param $type 变化类型 Zookeeper::CREATED_EVENT, Zookeeper::DELETED_EVENT, Zookeeper::CHANGED_EVENT
         * @param $state
         * @param $key 监听的path
         */
        public static function watcher($type, $state, $key){
            echo PHP_EOL.$key.' notifyed ....'.PHP_EOL;
            self::$isNotifyed = true;
            self::getLock();
        }

        public static function checkMyNodeOrBefore(){
            $list = self::$zk->getChildren(self::$root);
            sort($list);
            $root = self::$root;
            array_walk($list, function(&$val) use ($root){
                $val = $root . '/' . $val;
            });

            if($list[0] == self::$myNode){
                echo 'get locak node '.self::$myNode.'....'.PHP_EOL;
                return true;
            }else{
                // 找到上一个节点
                $index = array_search(self::$myNode, $list);
                $before = $list[$index - 1];
                echo 'before node '.$before.'.........'.PHP_EOL;
                return $before;
            }
        }
    }


    function zkLock($resourceId){
        $conf = ['host'=>'127.0.0.1', 'port'=>2181];
        $root = '/lockKey_' . $resourceId;
        $lockKey = '/lock_';
        $value = 'a';

        $client = zkCli::getZkInstance($conf, $root);
        $re = zkCli::tryGetDistributedLock($lockKey, $value);

        if($re){
            echo 'get lock success'.PHP_EOL;
        }else{
            echo 'get lock fail'.PHP_EOL;
            return ;
        }

        try {

            doSomething();

        } catch(\Exception $e) {

            echo $e->getMessage() . PHP_EOL;

        } finally {

            $re = zkCli::releaseDistributedLock();
            if($re){
                echo 'release lock success'.PHP_EOL;
            }else{
                echo 'release lock fail'.PHP_EOL;
            }

            return ;
        }
    }

    function doSomething(){
        $n = rand(1, 20);
        switch($n){
            case 1:
                sleep(15);// 模拟超时
                break;
            case 2:
                throw new \Exception('system throw message...');// 模拟程序中止
                break;
            case 3:
                die('system crashed...');// 模拟程序崩溃
                break;
            default:
                sleep(13);// 正常处理过程
        }
    }

    // 执行
    zkLock(0);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        167
        168
        169
        170
        171
        172
        173
        174
        175
        176
        177
        178
        179
        180
        181
        182
        183
        184
        185
        186
        187
        188
        189
        190
        191
        192
        193
        194
        195
        196
        197
        198
        199
        200
        201
        202
        203
        204
        205
        206
        207
        208
        209
        210
        211
        212
        213
        214
        215
        216
        217
        218
        219
        220
        221
        222
        223
        224
        225

    分别开启三个窗口 php test-zookeeper.php
    1、等待顺序执行完。
    2、将第二个ctrl+c 挂掉。
    ————————————————
    版权声明:本文为CSDN博主「半人猛犸」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/raoxiaoya/article/details/99651669

    展开全文
  • php 实现redis分布式锁

    2019-02-19 17:14:50
    虽然网上已经有各种介绍Redis分布式锁实现博客,然而他们实现却有着各种各样问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。   可靠性 首先,为了确保分布式锁可用,我们至少要...

    前言

    分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。

     

    可靠性

    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

    1. 互斥性。在任意时刻,只有一个客户端能持有锁。
    2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

    代码实现

     

    <?php
    class RedLock
    {
        private $retryDelay;
        private $retryCount;
        private $clockDriftFactor = 0.01;
        private $quorum;
        private $servers = array();
        private $instances = array();
        function __construct(array $servers, $retryDelay = 200, $retryCount = 3)
        {
            $this->servers = $servers;
            $this->retryDelay = $retryDelay;
            $this->retryCount = $retryCount;
            $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
        }
        public function lock($resource, $ttl)
        {
            $this->initInstances();
            $token = uniqid();
            $retry = $this->retryCount;
            do {
                $n = 0;
                $startTime = microtime(true) * 1000;
                foreach ($this->instances as $instance) {
                    if ($this->lockInstance($instance, $resource, $token, $ttl)) {
                        $n++;
                    }
                }
                # Add 2 milliseconds to the drift to account for Redis expires
                # precision, which is 1 millisecond, plus 1 millisecond min drift
                # for small TTLs.
                $drift = ($ttl * $this->clockDriftFactor) + 2;
                $validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift;
                if ($n >= $this->quorum && $validityTime > 0) {
                    return [
                        'validity' => $validityTime,
                        'resource' => $resource,
                        'token'    => $token,
                    ];
                } else {
                    foreach ($this->instances as $instance) {
                        $this->unlockInstance($instance, $resource, $token);
                    }
                }
                // Wait a random delay before to retry
                $delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay);
                usleep($delay * 1000);
                $retry--;
            } while ($retry > 0);
            return false;
        }
        public function unlock(array $lock)
        {
            $this->initInstances();
            $resource = $lock['resource'];
            $token    = $lock['token'];
            foreach ($this->instances as $instance) {
                $this->unlockInstance($instance, $resource, $token);
            }
        }
        private function initInstances()
        {
            if (empty($this->instances)) {
                foreach ($this->servers as $server) {
                    list($host, $port, $timeout) = $server;
                    $redis = new \Redis();
                    $redis->connect($host, $port, $timeout);
                    $this->instances[] = $redis;
                }
            }
        }
        private function lockInstance($instance, $resource, $token, $ttl)
        {
            return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
        }
        private function unlockInstance($instance, $resource, $token)
        {
            $script = '
                if redis.call("GET", KEYS[1]) == ARGV[1] then
                    return redis.call("DEL", KEYS[1])
                else
                    return 0
                end
            ';
            return $instance->eval($script, [$resource, $token], 1);
        }
    }

     

    使用示例

     

    <?php
    require_once __DIR__ . '/../src/RedLock.php';
    $servers = [
        ['127.0.0.1', 6379, 0.01],
        ['127.0.0.1', 6389, 0.01],
        ['127.0.0.1', 6399, 0.01],
    ];
    $redLock = new RedLock($servers);
    while (true) {
        $lock = $redLock->lock('test', 10000);
        if ($lock) {
            print_r($lock);
        } else {
            print "Lock not acquired\n";
        }
    }

     

    参考文档

    https://www.cnblogs.com/linjiqin/p/8003838.html

    https://github.com/ronnylt/redlock-php

    参考文献:

    https://www.cnblogs.com/syhx/p/9753433.html

    展开全文
  • php实现zookeeper分布式锁

    千次阅读 2019-08-15 19:23:58
    php实现zookeeper分布式锁 zookeeper和redis实现分布式锁的对比: 1、redis分布式锁,其实需要自己不断去尝试获取,比较消耗性能;zk分布式锁,获取不到,注册个监听器即可,不需要不断主动尝试获取,性能开销...
  • RedLock – PHPRedis分布式锁 基于 该库实现了基于Redis的分布式锁管理器算法。 要创建管理器: $ servers = [ [ '127.0.0.1' , 6379 , 0.01 ], [ '127.0.0.1' , 6389 , 0.01 ], [ '127.0.0.1' , 6399...
  • php 实现Redis分布式锁

    2019-07-25 11:34:45
    分布式锁可以通过DB,Redis,Zk等方式实现,本节主要介绍php使用Redis实现分布式锁 基于set命令 setnx key value 设置一个值,当key已经存在时,返回flase,代表失败 使用setnx实现分布有个缺陷,setnx操作...
  • php 基于redis的分布式锁应用

    千次阅读 2018-06-30 12:00:55
    我选择了redis分布式锁,have a look at the code:composer require signe/redlock-php引入这个包之后,代码里面可以这样写:private function lock($key, $time = 1000, $retry = 3) { //默认锁定时...
  • redis分布式锁php

    2020-04-06 20:41:28
    在单进程系统中,当遇到并发情况下,会出现一些数据异常问题,但是如果这些数据是需要保证...因为多线程之间是可以共享内存,但是多进程之间是不行,所以这个时候需要用到分布式锁分布式锁常用实现方...
  • Redis实现分布式锁 php

    2019-09-14 18:49:29
    一、分布式锁的作用: redis写入时不带锁定功能,为防止多个进程同时进行一个操作,出现意想不到结果,so…对缓存进行插入更新操作时自定义加锁功能。 二、RedisNX后缀命令 Redis有一系列命令,其特点是以NX...
  • redis来实现分布式锁的原理就是将程序中一个唯一key写入redis中,当有其他分布式应用要访问时候此key时,就去redis中读取,读取到了则说明此数据正在被处理,读取不到则说明可以进行处理; 但是,想将分布式锁...
  • Redis分布式锁

    2016-05-04 11:14:48
    分布式锁仅仅供思路参考,实际上该方法用在生产环境用还是有一定问题,请使用原生redisson分布式锁来解决生产环境中分布式应用锁的问题。https://github.com/redisson/ 以下仅供参考思路,不要应用在生产环境...
  • php实现redis分布式锁

    2019-04-27 17:09:31
    前段时间学习有了解到高并发redis分布式锁,刚好有看到博客上看到,认为还不错,所以在这里分享一下,不多说,直接上代码 <?php namespace app\index\controller; use think\Db; use think\Controller; class ...
  • [PHP] 基于redis的分布式锁防止高并发重复请求
  • redis 分布式锁 php

    2021-05-05 17:24:27
    php test(); function test(){ $redis = new redis(); $redis->connect("localhost", 6379); $redis->auth("redis"); // 密码验证 // echo "Server is running: " . $redis->ping(); $redis->...
  • 在昨天 review 队友代码过程中,发现了我们组分布式锁的写法似乎有点问题,实现代码如下: 加锁部分 解锁部分 主要原理是使用了 redis setnx 去插入一组 key-value,其中 key 要上锁的标识(在项目中是死...
  • Redis分布式锁--PHP

    2020-11-19 10:30:44
    解决问题一:采用内存型数据库提高系统的qps解决问题二:就要用到经常会遇到的锁,例如MySQL 有读锁、写锁、排他锁、悲观锁、乐观锁。不过这里只讨论redis来实现锁 简单版设置锁 $redis = new Redis(); $
  • 基于redis实现分布式锁 php

    千次阅读 2018-12-03 15:04:25
    为何需要分布式锁? 在单进程系统中,当遇到并发情况下,会出现一些数据异常问题,但是如果这些数据是需要...因为多线程之间是可以共享内存,但是多进程之间是不行,所以这个时候需要用到分布式锁。  ...
  • PHP实现Redis分布式锁

    2021-01-23 13:28:01
    在我们日常开发可谓用得比较多。通常用来解决资源并发问题。特别是多机集群情况下,资源争抢问题。但是,很多新手在锁的处理上常常会犯一些问题。今天我们来深入理解。 一、Redis 错误使用之一 我曾经...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 155
精华内容 62
关键字:

php分布式的锁