本文作者:DurkBlue

记录PHP对于高并发最好的加锁方式-Redis分布式锁推荐

记录PHP对于高并发最好的加锁方式-Redis分布式锁摘要:             PHP是采取多进程模式,php-fp...

            PHP是采取多进程模式,php-fpm程序为每一个进程都会创建一个worker。也会预先创建多余的几个worker一起来为客户端的请求做处理,但是高并发往往集中在那么些个时间段。虽然worker已经可以很大程度上缓解高并发的压力,但是几次测试下来还会有极限的时候遇到worker不足的情况。于是思索良久,结合网上的教程,于是了解到了redis锁这一块,在吃透其中redis锁的原理之后。做个统一的分享。以后自己在写有关于解决高并发的问题的时候可以有个记录查看。这就是个人博客带来最大一部分价值。话不多说。上分享


            1.准备工作

            PHP目前没有内置redis模块,但是redis官方提供了基于php的扩展。所以需要在php.ini开启redis的扩展

            记录PHP对于高并发最好的加锁方式-Redis分布式锁  第1张

            

            [Redis]

            extension=redis

            看看自己的php.ini是不是有这行配置,如果没有,创建它,如果有,前面有注释分号;取消掉它即可

            2.代码

            
<?php
/**
 * redis操作类
 */
class RedisModel
{
    private static $redis; // 存储redis对象
    private static string $folder = 'www_hepuhua_cn:'; // 项目redis文件夹
    /**
     * @Notes: 数据封装
     * @Author:DurkBlue
     * @Time: 2024/3/20 11:54
     * @param int $code 错误代码
     * @param string $message 错误描述
     * @return array 数组
     * @Interface error
     */
    public static function error(int $code, string $message){
        return ['code'=>$code, 'message'=>$message];
    }
    /**
     * @Notes: redis类初始化
     * @Author:DurkBlue
     * @Time: 2024/3/20 11:55
     * @return Object|array
     * @Interface init
     */
    private static function init():Object|array{
        $redisObj = self::$redis;
        if(is_null($redisObj)){
            // 因为PHP官方并未内置redis模块,所以须检查PHP是否安装了redis扩展
            if (!extension_loaded('redis')) {
                return self::error(-1, 'PHP 未安装 redis 扩展');
            }
            $config = [
                'server' => '127.0.0.1', // redis宿主主机地址
                'port'   => 6379, // redis端口
                'password'=>'', // 是否设置了redis密码
                'timeout' => 30, //连接redis配置超时时间30s
            ];
            $redis_temp = new Redis();
            $connect = $redis_temp->connect($config['server'], $config['port'], $config['timeout']);
            if (!$connect) {
                return self::error(-1, 'redis 连接失败, 请检查Redis配置的参数设置');
            }
            if (!empty($config['password'])) {
                $redis_temp->auth($config['password']);
            }
            try {
                $ping = $redis_temp->ping();
            }
            catch (ErrorException $e) {
                return self::error(-1, 'redis 无法正常工作,请检查 redis 服务'. $e->getMessage());
            }
            if ($ping != '+PONG') {
                return self::error(-1, 'redis 无法正常工作,请检查 redis 服务');
            }
            $redisObj = self::$redis = $redis_temp;
        }else{
            try {
                $ping = $redisObj->ping();
            }
            catch (ErrorException $e) {
                self::$redis = NULL;
                self::init();
            }
            if ($ping != '+PONG') {
                self::$redis = NULL;
                self::init();
            }
        }
        return $redisObj;
    }
    /**
     * @Notes: 为当前进程获取锁
     * @Author:DurkBlue
     * @Time: 2024/3/20 13:08
     * @param string $lockName 锁名
     * @param int $expireS 锁自动释放时间(避免人为释放锁失败后死锁情况)
     * @param int $waitTimeMs 尝试重新上锁时间间隔
     * @param int $retryNums 重试获取锁次数
     * @return array
     * @Interface getLock
     */
    public static function getLock(string $lockName = '', int $expireS = 30, int $waitTimeMs = 100, int $retryNums = 1):array
    {
        $return = ['status' => 400, 'message'=>''];
        $redis = self::init(); // 获取redis对象
        if(is_array($redis)){
            $return['message'] = $redis['message'];
            return $return;
        }
        if (empty($lockName)){
            $return['message'] = "锁名为空";
            return $return;
        }
        $nowTime = time(); // 获取当前时间戳
        $expireAt = $nowTime + $expireS; // 程序设置锁自动释放时间
        $redisKey = self::$folder . "lock_{$lockName}";
        $lockValue = date('YmdHis') . mt_rand(1000, 9999);
        for($i = 0; $i < $retryNums; $i++){
            $result = $redis->setnx($redisKey, $lockValue); // 设置redis锁,并且记录该锁过期时间
            if ($result) {
                // 为true表示该锁已经被自动释放掉了或者人为释放掉了,无论怎么样,证明当前进程是有资格处理业务的
                // 设置锁的自动释放时间
                $redis->expire($redisKey, $expireS);
                $ttl = $redis->ttl($redisKey); // 获取该锁剩余生存时间数,官方介绍ttl()放回的是剩余秒数
                if($ttl < 0){
                    // 这只能是某个进程出现异常,导致没有设置释放时间
                    $redis->set($redisKey, $expireAt);
                }
                $return['status'] = 200;
                $return['data'] = $lockValue;
                return $return; // 上锁成功,中断循环
            }
            // 程序走到这一步代表setnx()方法没有创建成功
            //这种情况只能是已经有上个进程正在占用锁,并且锁没有被释放掉,按道理来讲之做过期时间判断就好
            if ($redis->get($redisKey) < $expireAt) {
                //设置key的失效时间
                $redis->expire($redisKey, 0);
            }
            if($waitTimeMs > 0){
                usleep($waitTimeMs); // 设置间隔请求时间
            }
        }
        $return['message'] = '网络繁忙,请稍后重试';
        return $return; // 当前进程没有获取到锁
    }
    /**
     * @Notes: 释放锁
     * @Author:DurkBlue
     * @Time: 2024/3/20 13:18
     * @param string $lockName 锁门
     * @param int $localValue 锁值
     * @return array
     * @Interface unlock
     */
    public static function unlock(string $lockName, int $localValue):array
    {
        $redis = self::init(); // 获取redis对象
        if(is_array($redis)) return ['status'=>400, 'message'=> $redis['message']];
        $redisKey = self::$folder . "lock_{$lockName}";
        //先判断是否存在此锁
        if($redis->exists($redisKey)){
            if($redis->get($redisKey) == $localValue){
                // 避免因为当前进程因为逻辑业务执行时间过长,超出了自动释放时间,进而其他进程成功获取到了锁,从而删除了其他进程的锁。从而导致锁失效的问题
                $redis->del($redisKey);
            }
        }
        return ['status' => 200];
    }
}
    /**
     * @Notes: redis上锁测试
     * @Author:DurkBlue
     * @Time: 2024/3/20 13:21
     * @Interface testRedisLock
     */
    function testRedisLock(){
        $result = RedisModel::getLock("test");
        if($result['status'] == 400) return $result['message'];
        // 获取到锁,做逻辑业务
        sleep(1); // 模拟耗时操作
        // 释放所
        RedisModel::unlock("test", $result['data']);
        return '执行成功';
    }
$result = testRedisLock();
echo $result;
?>

        3.最后附上测试效果:

        记录PHP对于高并发最好的加锁方式-Redis分布式锁  第2张


记录PHP对于高并发最好的加锁方式-Redis分布式锁  第3张



此篇短文由DurkBlue发布,写作不易转载请注明来处
文章投稿或转载声明

来源:DurkBlue版权归原作者所有,转载请保留出处。本站文章发布于 03-20
温馨提示:文章内容系作者个人观点,不代表DurkBlue博客对其观点赞同或支持。

赞(2)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

阅读
分享

发表评论取消回复

快捷回复:
AddoilApplauseBadlaughBombCoffeeFabulousFacepalmFecesFrownHeyhaInsidiousKeepFightingNoProbPigHeadShockedSinistersmileSlapSocialSweatTolaughWatermelonWittyWowYeahYellowdog

评论列表 (有 1 条评论,153人围观)参与讨论
网友昵称:别理我烦着呢
别理我烦着呢 游客 Google Chrome 122.0.0.0 Windows 10 x64 沙发
03-29 回复
还是redis实现锁功能有一套