摘要:
PHP是采取多进程模式,php-fp...
PHP是采取多进程模式,php-fpm程序为每一个进程都会创建一个worker。也会预先创建多余的几个worker一起来为客户端的请求做处理,但是高并发往往集中在那么些个时间段。虽然worker已经可以很大程度上缓解高并发的压力,但是几次测试下来还会有极限的时候遇到worker不足的情况。于是思索良久,结合网上的教程,于是了解到了redis锁这一块,在吃透其中redis锁的原理之后。做个统一的分享。以后自己在写有关于解决高并发的问题的时候可以有个记录查看。这就是个人博客带来最大一部分价值。话不多说。上分享
1.准备工作
PHP目前没有内置redis模块,但是redis官方提供了基于php的扩展。所以需要在php.ini开启redis的扩展
[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.最后附上测试效果: