php start.php status
去排查busy的进程,发现结果都是在查询数据库这一块阻塞住了3.因为这个问题,所以把数据库切换为本地,发现这个问题不出现了,没有接口超时了,所以问题就集中在网络这一块,把数据库切换回阿里云数据库,然后在这个基础加了个数据库超时重连的机制卸载了基类模型上
return [
...,
'options' => [
\PDO::ATTR_TIMEOUT => 3, //设置与数据库通信的超时值(以秒为单位)
]
];
app/model/BaseModel.php
namespace app\model;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\QueryException;
use support\Db;
use support\Model;
class BaseModel extends Model
{
/** @var int 最大重试次数 */
protected int $retryAttempts = 3;
/** @var int 每次重试延迟(毫秒) */
protected int $retryDelay = 100;
/** @var array|string[] $defaultOrder 默认排序 */
protected array $defaultOrder = ['id' => 'desc'];
/** @var int DEFAULT_SOFT_DELETE 默认软删除值 */
const DEFAULT_SOFT_DELETE = 0;
/** @var string 创建时间 */
public const CREATED_AT = 'create_time';
/** @var string 更新时间 */
public const UPDATED_AT = 'update_time';
/**
* Indicates if the model should be timestamped.
* @var bool
*/
public $timestamps = false;
protected static function boot()
{
parent::boot();
//设置默认排序
static::addGlobalScope('order', function (Builder $builder) {
if(property_exists(static::class, 'defaultOrder')) foreach ((new static())->defaultOrder as $field => $order)
$builder->orderBy($field, $order);
});
static::creating(function (Model $model) {
if (self::hasColumn($model, static::CREATED_AT)) {
$model->{static::CREATED_AT} = time();
}
if (self::hasColumn($model, static::UPDATED_AT)) {
$model->{static::UPDATED_AT} = time();
}
});
static::updating(function ($model) {
if (self::hasColumn($model, static::UPDATED_AT)) {
$model->{static::UPDATED_AT} = time();
}
});
}
protected static function hasColumn($model, $column): bool
{
$schemaBuilder = $model->getConnection()->getSchemaBuilder();
$columns = $schemaBuilder->getColumnListing($model->getTable());
return in_array($column, $columns);
}
/**
* 封装数据库操作,捕获丢包并重连
* @param callable $callback
* @return mixed
* @throws Exception
*/
public function runQueryWithReconnect(callable $callback)
{
$attempts = 0;
while ($attempts < $this->retryAttempts) {
try {
return $callback();
} catch (QueryException $e) {
$attempts++;
if ($this->isLostConnectionError($e) && $attempts < $this->retryAttempts) {
echo "\033[31m[重试] 数据库连接丢失,尝试重新连接... 第 {$attempts} 次\033[0m\n";
Db::reconnect();
usleep($this->retryDelay * 1000); // 等待指定时间后重试
} else {
throw $e;
}
}
}
throw new Exception("\033[31m尝试 {$this->retryAttempts} 次后仍无法连接到数据库。\033[0m");
}
/**
* 判断是否为连接丢失错误
*/
protected function isLostConnectionError($exception): bool
{
$errorMessage = $exception->getMessage();
$lostConnectionMessages = [
'server has gone away',
'no connection to the server',
'Lost connection to MySQL server',
'is dead or not enabled',
'Error while sending',
'decryption failed or bad record mac',
'server closed the connection unexpectedly',
];
foreach ($lostConnectionMessages as $message) {
if (stripos($errorMessage, $message) !== false) {
return true;
}
}
return false;
}
/**
* 全局封装动态方法调用
* @param $method
* @param $parameters
* @return mixed
* @throws Exception
*/
public function __call($method, $parameters)
{
return $this->runQueryWithReconnect(function () use ($method, $parameters) {
return parent::__call($method, $parameters);
});
}
/**
* 全局封装静态方法调用
* @param $method
* @param $parameters
* @return mixed
* @throws Exception
*/
public static function __callStatic($method, $parameters)
{
$instance = new static();
return $instance->runQueryWithReconnect(function () use ($instance, $method, $parameters) {
return $instance->$method(...$parameters);
});
}
}
但是发现接口还是会超时,还是在请求数据库出现阻塞
6.问题知道了,那就寻找解决
方案一:在socket上每个一段时间就把socket连接踢出,客户端那边是已经做了重连机制的
app\socket\BaseSocket.php
namespace app\socket;
use Workerman\Timer;
use Workerman\Worker;
class BaseSocket
{
/** @var int 设置心跳间隔 */
const HEARTBEAT_TIME = 50;
/**
* 启动服务执行
* @param Worker $worker
* @return void
*/
public function onWorkerStart(Worker $worker)
{
Timer::add(10, function () use ($worker) {
$time_now = time();
foreach ($worker->connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > self::HEARTBEAT_TIME) {
$connection->close();
}
}
});
}
}
这个方案确实减少了很多阻塞的问题,但是还是存在阻塞,治标不治本
方案二:设置pdo读写超时时间,查了很多文档也没查到,最后问了一下chatgpt还真找到了个写法,
config/database.php
return [
...,
'options' => [
\PDO::ATTR_TIMEOUT => 3, //设置与数据库通信的超时值(以秒为单位)
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_TIMEOUT => 3, //设置与数据库通信的超时值(以秒为单位)
\PDO::MYSQL_ATTR_INIT_COMMAND => '
SET SESSION wait_timeout = 30;
SET SESSION net_read_timeout = 5;
SET SESSION net_write_timeout = 5;
', //设置等待超时 读超时 写超时
'modes' => [
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
'NO_ENGINE_SUBSTITUTION',
],
]
];
实测了几天再也没发现数据库阻塞的问题,这个问题得已解决
如有出现相关的问题的小伙伴,可以参考一下
最后得方法1 与方法 2可以再解释的通俗一点嘛