本文目录导读:

在 Laravel 中,使用空值缓存(缓存空对象)是应对缓存穿透的常用且有效方案,但不能仅依赖此一种措施。
下面为你详细拆解 Laravel 中缓存穿透的处理策略,包括空值缓存的具体实现、优缺点以及配套方案。
什么是缓存穿透?
现象:请求查询一个数据库中也不存在的数据,每次请求都会绕过缓存,直接打到数据库上。
危害:如果有大量此类请求(如恶意攻击),数据库会承受巨大压力,甚至宕机。
示例:查一个 id = -1 的用户,缓存中无此 key,数据库中也无此记录,每次请求都会查库。
核心解决方案:缓存空值
这是实现最简单、效果最直接的方式。
核心逻辑:当从数据库查询结果为空(null)时,仍然将这个“空结果”存入缓存,并设置一个较短的过期时间(如 60 秒)。
1 在 Laravel 中的实现示例
use Illuminate\Support\Facades\Cache;
use App\Models\User;
public function findUser(int $id): ?User
{
// 1. 尝试从缓存获取,key 存在且值为 null,也会返回 null。
$user = Cache::get('user:' . $id);
if ($user !== null) {
// 缓存命中(包括缓存了“空”的情况?不,如果我们缓存了 null,这里 $user null)
// 注意:Cache::get key 不存在返回 null,key 存在但值是 null,也返回 null。
// 所以需要区分“缓存未命中”和“缓存了空值”。
return $user;
}
// 2. 检查是否缓存了“空值”的标记
if (Cache::has('user:empty:' . $id)) {
return null; // 命中空值缓存
}
// 3. 缓存未命中,查询数据库
$user = User::withTrashed()->find($id); // 假设允许查软删除的
// 4. 无论数据库是否查到,都存入缓存
if ($user) {
// 查到数据,正常缓存,过期时间可以长一些
Cache::put('user:' . $id, $user, 3600); // 1小时
} else {
// 没有查到,缓存一个“空值标记”,过期时间很短
Cache::put('user:empty:' . $id, true, 60); // 60秒
// 或者直接缓存 null 并用特殊逻辑判断,但推荐用额外标记
}
return $user;
}
更简洁的写法(直接缓存 null):
public function findUser(int $id): ?User
{
// 使用 remember 方法:如果缓存中 key 存在,直接返回;否则执行闭包,并将结果缓存
return Cache::remember('user:' . $id, $expireTime, function () use ($id) {
$user = User::find($id);
// 即使为 null,也返回,Laravel 会缓存 null
// 但问题来了:如果数据库新增了这条记录,缓存中的 null 还在,会查不到。
// 所以推荐使用下面带空值标记的方案
return $user;
});
}
推荐做法:使用空值标记 + 短过期时间
public function findUser(int $id): ?User
{
return Cache::remember('user:' . $id, now()->addHour(), function () use ($id) {
$user = User::find($id);
if ($user) {
return $user;
}
// 缓存一个特殊的“空值”对象(避免直接存 null,因为 null 会与“key不存在”混淆)
// 也可以存一个布尔值 true
Cache::put('user:empty:' . $id, true, now()->addMinutes(5)); // 5分钟空值缓存
return null; // 注意:这里返回 null,remember 会缓存 null,但可能有问题
// 更好的方式是 return (object)[] 这样缓存一个空对象,但处理时再转换
});
}
更健壮的做法是手动处理缓存逻辑,而不是完全依赖 remember。
空值缓存方案的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,容易理解 | 需要额外缓存空间(但空值通常是极少数) |
| 能有效防止大量不存在数据的查询打穿 | 如果数据刚被删除或新增,在缓存过期前会出现短暂的数据不一致(命中空值) |
| 对代码侵入性小 | 必须设置较短的有效期,否则真实数据写入后读不到 |
配套措施:更完整的防穿透体系
单独使用空值缓存并不完美,建议结合以下方案:
1 布隆过滤器 (Bloom Filter)
- 原理:将数据库中的所有有效 ID 预先加载到一个二进制向量中,请求来时,先检查布隆过滤器。
- 返回“不存在”:直接拒绝,不查缓存和数据库。
- 返回“可能存在”:继续查缓存或数据库(可能有误判,但概率低)。
- 优势:防止大量无效 ID 直接冲击缓存层。
- Laravel 实现:可使用
bloomfilter扩展包或Redis的BF模块。 - 适用场景:数据总量稳定且可预知的场景(如用户 ID 范围)。
2 请求限流与参数校验
- 前端校验:拦截明显不合法的参数(如负数 ID、超长字符串)。
- 后端限流:对同一 IP、同一用户、同一 API 进行访问频率限制(Laravel 内置
throttle中间件)。
3 热点数据降级与熔断
- 当数据库压力过大时,直接返回降级内容(如页面缓存或错误提示),不再查库。
4 缓存空值 + 异步更新
- 对于被缓存为空的数据,当真实数据写入时,通过事件或队列主动删除空缓存,让下一次查询回源。
什么时候用空值缓存?
| 场景 | 建议 |
|---|---|
| 内部系统,数据量不大,攻击风险低 | 空值缓存 + 短过期时间 足够了,简单好用 |
| 高并发、对外接口、数据量大 | 优先使用 布隆过滤器 拦截无效请求,再配合空值缓存做保底 |
| 实时性要求极高 | 使用空值缓存 + 数据变更主动删除缓存(结合 Cache::forget 或事件监听) |
空值缓存是 Laravel 应对缓存穿透的基操,但非银弹。 你需要为其设置短过期时间,并视业务规模用布隆过滤器或主动更新来弥补其不足。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播