在 Laravel 中当 MySQL 异常宕机时强制返回空数据
起因
- 之前线上遇到一个问题, 就是当
MySQL
挂了, 然后导致整个服务崩塌,Redis
在前面完全没分担任何压力
. - 业务常规的查询逻辑如下:
1. 从redis
中获取数据, 有则返回
2. 当第一步redis
无数据, 去MySQL
查询数据
3. 把第二步查询到的数据写入redis
4. 返回数据
问题分析
redis
当然不会有问题, 问题是在第二步的时候- 去
MySQL
查询数据,数据库服务已经宕机, 这时候请求阻塞住 - 阻塞超时,然后抛出异常,导致无法走到第三步
- 下一次请求来, 又继续去连接
MySQL
,无限阻塞,把业务服务器也拖垮
解决方案
- 这是我们的解决方案, 不一定适合所有业务. 当
MySQL
宕机强制缓存空数据到redis
,允许部分页面为空.而不是无法提供服务
解决思路
- 设置好合理的
MySQL
连接超时时间mysqlnd.net_read_timeout = 3
- 当数据库连接超时之后, 抛出异常
- 新建一个基础模型
BaseModel
, 其它所有模型继承这个模型, 并重写newEloquentBuilder
方法
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
public function newEloquentBuilder($query)
{
return new MysqlCustomBuilder($query);
}
}
- 新建一个查询构造器类
MysqlCustomBuilder
<?php
namespace App\Models\Database;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class MysqlCustomBuilder extends Builder
{
public function get($columns = ['*'])
{
try {
return parent::get($columns);
} catch (\Exception $e) {
// 根据 laravel 重连的错误码
$message = $e->getMessage();
if (Str::contains($message, [
'server has gone away',
'no connection to the server',
'Lost connection',
'is dead or not enabled',
'Error while sending',
'decryption failed or bad record mac',
'server closed the connection unexpectedly',
'SSL connection has been closed unexpectedly',
'Error writing data to the connection',
'Resource deadlock avoided',
])) {
// 记录日志, 通知xxx
// Log::error($e);
// 强制返回空集合
return Collection::make();
}
// 如果不在处理的范围内, 继续抛出异常
throw $e;
}
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
我建议改一下,做个热备的数据库,如果出现这些错误且存在热备数据库配置就直接通过脚本把环境变量一改,
php artisan config:cache
下就切过去了。