起因
- 之前线上遇到一个问题, 就是当
MySQL
挂了, 然后导致整个服务崩塌, Redis
在前面完全没分担任何压力
.
- 业务常规的查询逻辑如下:
- 从
redis
中获取数据, 有则返回
- 当第一步
redis
无数据, 去MySQL
查询数据
- 把第二步查询到的数据写入
redis
- 返回数据
问题分析
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;
}
}
}
- 之后需要重点监控日志报错, 来确定页面为空是运营配置的问题还是数据库异常的问题