摘要:
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.最后附上测试效果:





