PHP用shell_exec启动PHP脚本后Redis连接不释放

问题描述

有两个PHP脚本,start.phpprocesses2.php
这两个脚本都连接一台相同的 Redis

执行过程:
假设此时 redis 的连接数为 0。我是用redis-cli info | grep connected_clients查看的。
命令行执行 path/to/php start.php,在这个脚本中会做以下两件事:

  1. 连接 Redis
  2. 用shell_exec启动脚本processes2.php

然后start.php退出
processes2.php中只会做一件事,那就是连接Redis

问题点:
执行start.php前用redis-cli info | grep connected_clients查看连接数为0。
执行start.php,连接Redis后但未启动脚本processes2.php时查看Redis连接数1。
start.php启动了processes2.php并退出,当processes2.php连接Redis后查看连接数为2。
processes2.php执行完后,再查看连接为又恢复为0。
也就是说进程1启动了进程2,进程1运行完退出了,进程2在运行中但1中的连接却没释放!
start.php执行完后,用ps -ef | grep php已经找不到了,是真的退出了。

我这里表达的连接数是指Redis服务端显示的已连接的客户端的数量,两个进程是不是使用的同一个连接连接的
我没确认,本人不才,主要是不知道怎么确认-_-||。

我试过的方式

  • 用函数exec()system()也是一样的效果
  • start.php的最后调用下exit()die也是一样效果
  • 我试了用Redis插件的的connetc和pconnect都是一样的结果
  • 设置Redis连接空闲一定时间后就关闭config set timeout 10,可以让连接数慢慢下降。但这不是我想要的
  • 在启动processes2.php之前手动调用close关闭连接,这样也可以,但也不是我想要的。因为之后的业务可能要用到Redis那我不是还要连一次。

求解答

  • 为啥会这样,背后的原理是啥?
  • 有没有啥解决方案,让start.php退出后,它的连接啥的也就自动释放掉、

模拟代码

代码可以复制到本地后运行,本来准备上传的发现不能上传文件,就只能这样贴了

配置文件

<?php
return [
    'host' => '127.0.0.1',
    'port' => 6379
];

start.php

<?php

$config = include __DIR__ . '/config.php';

echo "process 1 START" . PHP_EOL;
echo "连接Redis..." . PHP_EOL;

//连接 Redis
$cacheInstance = new \Redis();
try {
    $cacheInstance->connect($config['host'], $config['port'], 0);
} catch (\Exception $exception) {
    echo sprintf("连接redis失败host:%s, port:%s, timeout:%s", $config['host'], $config['port'], 0) . PHP_EOL;
    exit();
}
if (!empty($config['password'])) {
    try {
        $cacheInstance->auth($config['password']);
    } catch (\Exception $exception) {
        echo 'redis授权失败password:' . $config['password'] . PHP_EOL;
        exit();
    }
}

echo "Redis 连接成功" . PHP_EOL;

//取 Redis 当前连接数
//用php-redis插件的info()方法获取到的不太准所以直接用shell获取
$clientNum = `redis-cli info | grep connected_clients`;
$clientNum = preg_replace('/[^\d]/', '', $clientNum);
echo "\033[32m 进程1连接Redis后的连接数:\033[37m" . $clientNum . PHP_EOL;

//shell 启动第二个进程
echo "启动2个进程2..." . PHP_EOL;
$logFile2 = __DIR__ . '/process2.log';
$processFile = __DIR__ . '/processes2.php';
`nohup /app/php/bin/php {$processFile} > {$logFile2} 2>&1 &`;

echo "启动进程2结束" . PHP_EOL;
echo "进程2的日志将写入{$logFile2}" . PHP_EOL;

$desc = <<<desc

此后进程1会结束
但连接的 Reids 不会释放,可查看进程2的日志对比得出
假设没运行进程1时。redis 连接的客户端数为n,则
进程1启动后为n+1
进程2启动后为n+2
进程2结束后为n
desc;
echo $desc . PHP_EOL;
echo 'exit!' . PHP_EOL;

processes2.php

<?php

$config = include __DIR__ . '/config.php';

echo "进程2启动" . PHP_EOL;
echo "连接 Redis..." . PHP_EOL;

$cacheInstance = new \Redis();

//连接 Redis
try {
    $cacheInstance->connect($config['host'], $config['port'], 0);
} catch (\Exception $exception) {
    echo sprintf("连接redis失败host:%s, port:%s, timeout:%s", $config['host'], $config['port'], 0) . PHP_EOL;
    exit();
}
if (!empty($config['password'])) {
    try {
        $cacheInstance->auth($config['password']);
    } catch (\Exception $exception) {
        echo 'redis授权失败password:' . $config['password'] . PHP_EOL;
        exit();
    }
}

echo "进程2连接 Redis 成功" . PHP_EOL;

$startTime = time();

//取 Redis 当前连接数
echo "开始获取 Redis 连接的客户端 60秒后退出" . PHP_EOL;
$writeFileName = __DIR__ . '/output_from_process2.log';
do {
    //用php-redis插件的info()方法获取到的不太准所以直接用shell获取
    $clientNum = `redis-cli info | grep connected_clients`;
    $clientNum = preg_replace('/[^\d]/', '', $clientNum);
    echo '当前客户端数:' . $clientNum . ' waite 1s...' . PHP_EOL;
    sleep(1);
} while ( time() - $startTime < 60 );

$desc = <<<desc

与进程一输出的 Redis 连接数对比
可以看到进程1已经退出了,但是连接并没有释放,仍然占用着的连接数

进程2结束后,可用命令 redis-cli info | grep connected_clients 查看 Redis 上连接的客户端数
应该可以看到被这两个进程占用连接数已经释放
desc;
echo $desc . PHP_EOL;

echo 'exit!' . PHP_EOL;
阅读 2.6k
1 个回答

PHP的redis扩展底层是有连接池机制的,你打开的连接,在底层不会立即关闭,而是放到连接池中,等待下一次使用。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题