zoukankan      html  css  js  c++  java
  • PHP 基于redis的分布式锁

    <?php
    class ProcessRedisLock
    {
        /**
         * redis key 前缀
         */
        const KEY_PREFIX = 'PROCESS_REDIS_LOCK:';
    
        /**
         * 默认超时时间(秒)
         */
        const DEFAULT_TIMEOUT = 5;
    
        /**
         * 最大超时时间(秒)
         */
        const MAX_TIMEOUT_SETTING = 60;
    
        /**
         * 随机数的最小值
         */
        const MIN_RAND_NUM = 0;
    
        /**
         * 随机数的最大值
         */
        const RAND_MAX_NUM = 100000;
    
        /**
         * 每次取锁间隔毫秒数
         */
        const GET_LOCK_SLEEP_MICRO_SECONDS = 0.1;
    
        /**
         * @var mixed redis 实例
         */
        private $redisIns;
    
        /**
         * @var string 锁名
         */
        private $lockName;
    
        /**
         * @var int 超时时间,不可超过 self::MAX_TIMEOUT_SETTING
         */
        private $timeout;
    
        /*
        单元测试步骤
        --------------------------------------------------------------------------------------
        1.分别用一个chrome和一个ie,模拟并发请求:
            1)http://xxx/test?queryName=task1&handlerTime=10&lockName=myLocktest&timeout=10
            2)http://xxx/test?queryName=task2&handlerTime=10&lockName=myLocktest&timeout=5
        2.如上
            task1处理耗时10s,取锁超时时间10s
            task2处理耗时10s,取锁超时时间5s
        3.日志结果
            2019-11-22 17:16:30 [task1]: 尝试取锁,超时时间设定10秒
            2019-11-22 17:16:30 [task1]: 获取到锁,唯一标志:PROCESS_REDIS_LOCK:5dd7a76e1bafe37915
            2019-11-22 17:16:32 [task2]: 尝试取锁,超时时间设定5秒
            2019-11-22 17:16:35 [task1]: 释放锁
            2019-11-22 17:16:35 [task2]: 获取到锁,唯一标志:PROCESS_REDIS_LOCK:5dd7a770d0c2b41355
            2019-11-22 17:16:45 [task2]: 释放锁
        --------------------------------------------------------------------------------------
    
        单元测试代码
        --------------------------------------------------------------------------------------
                // 浏览器模拟并发请求
                // 注意:这里测试的时候如果session以文件存储的话,要避免两个窗口共用一个会话id,因为同样的会话id在session_start()时会锁文件
                // 这样会造成请求阻塞,模拟不了请求并发的情况,所以应该使用两个不同的浏览器(如一个chrome,一个firefox)(同样的浏览器共享cookie也会导致拿到同样的会话id)
                public function testAction()
                {
                    $params = $this->getRequest()->getParams();
                    $this->requestTask($params['queryName'], $params['handlerTime'], $params['lockName'], $params['timeout']);
                }
    
                // 模拟取锁、耗时操作、释放锁
                public function requestTask($queryName, $handlerTime, $lockName, $timeout)
                {
                    try {
                        // 获取redis实例
                        $processRedisLock = new ProcessRedisLock(redis(), $lockName, $timeout);
    
                        $this->echoAndSaveInfo($queryName, "尝试取锁,超时时间设定{$timeout}秒");
    
                        // 取锁
                        $id = $processRedisLock->lock();
                        // 如果到了超时时间还未取到会返回false,则直接抛异常
                        if($id === false){
                            throw new Exception('获取锁失败');
                        }
    
                        $this->echoAndSaveInfo($queryName, "获取到锁,唯一标志:".$id);
    
                        // 模拟耗时操作
                        sleep($handlerTime);
    
                        // 释放锁
                        $processRedisLock->unlock($id);
                        $this->echoAndSaveInfo($queryName, "释放锁");
                    } catch (Exception $e) {
                        $this->echoAndSaveInfo($queryName, $e->getMessage());
                        // do something
                    }
                }
    
                // 输出并且记录日志
                public function echoAndSaveInfo($queryName, $content)
                {
                    $info = date('Y-m-d H:i:s') . " [{$queryName}]: {$content}" . PHP_EOL;
                    echo $info;
                    file_put_contents('test.txt', $info . PHP_EOL, FILE_APPEND);
                }
        --------------------------------------------------------------------------------------
        */
    
        /**
         * ProcessRedisLock constructor.
         * @param $redisIns
         * @param $lockName
         * @param int $timeout
         * @throws Exception
         */
        public function __construct($redisIns, $lockName, $timeout = self::DEFAULT_TIMEOUT)
        {
            if (!$redisIns) {
                new Exception('The redis instance is empty');
            }
    
            if(!$lockName){
                throw new Exception('Lock name invalid');
            }
    
            // 校验超时时间
            $timeout = intval($timeout);
            if (!($timeout > 0 && $timeout <= self::MAX_TIMEOUT_SETTING)) {
                throw new Exception('The timeout interval is (0,' . self::MAX_TIMEOUT_SETTING . ']');
            }
    
            $this->redisIns = $redisIns;
            $this->lockName = $lockName;
            $this->timeout = $timeout;
        }
    
        /**
         * 加锁
         * @return bool
         * @Date 2019/11/22
         */
        public function lock()
        {
            // redis key
            $key = $this->getRedisKey();
    
            // 唯一标志
            $id = $this->getId();
    
            // 超时时间
            $endTime = time() + $this->timeout;
    
            // 循环取锁
            while (time() < $endTime) {
                // 尝试加锁,若给定的 key 已经存在,则 SETNX 不做任何动作。
                if ($this->redisIns->setnx($key, $id)) {
                    // 设置过期时间,防止程序异常退出没有解锁导致死锁
                    $this->redisIns->expire($key, $this->timeout);
                    // 返回唯一标志,用于解锁
                    return $id;
                }
                usleep(self::GET_LOCK_SLEEP_MICRO_SECONDS);
            }
    
            return false;
        }
    
    
        /**
         * 解锁
         * @param string $id 唯一标志,加锁成功时返回
         * @return bool
         * @Date 2019/11/22
         */
        public function unlock($id)
        {
            $key = self::getRedisKey();
    
            // 如果锁的值与没有被修改
            if ($this->redisIns->get($key) == $id) {
                // 开始事务
                $this->redisIns->multi();
                // 释放该锁
                $this->redisIns->del($key);
                // 执行
                $this->redisIns->exec();
                return true;
            } else {
                return false;
            }
        }
    
        /**
         * 获取redis key
         * @return string
         * @Date 2019/11/22
         */
        public function getRedisKey()
        {
            return self::KEY_PREFIX . $this->lockName;
        }
    
        /**
         * 获取唯一标志位
         * @Date 2019/11/22
         */
        public function getId()
        {
            return uniqid(self::KEY_PREFIX) . mt_rand(self::MIN_RAND_NUM, self::RAND_MAX_NUM);
        }
    }
  • 相关阅读:
    华为2019软件题
    图像的存储格式转化(python实现)
    windows+两个ubuntu系统的引导启动问题
    《视觉SLAM十四讲》课后习题—ch6
    视觉SLAM十四讲课后习题—ch8
    LINQ根据时间排序问题(OrderBy、OrderByDescending)
    Element的扩展
    CSharp
    jQuery函数使用记录
    日记越累
  • 原文地址:https://www.cnblogs.com/lzs-888/p/11912973.html
Copyright © 2011-2022 走看看