本文目录导读:

是的,Laravel 中可以使用互斥锁(Mutex)来解决缓存击穿问题,这通常比简单的 Cache::remember 更加可靠。
什么是缓存击穿
缓存击穿是指一个热点 key 在缓存过期瞬间,大量并发请求同时查询数据库,导致数据库压力剧增。
使用互斥锁解决缓存击穿
核心思路
当缓存过期时,只允许一个线程去重建缓存,其他线程等待或直接返回旧数据。
示例代码
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
public function getHotProduct($id)
{
$cacheKey = "hot_product_{$id}";
$lockKey = "lock:{$cacheKey}";
$ttl = 3600; // 缓存有效期
$lockTimeout = 10; // 锁超时时间(防止死锁)
// 1. 尝试获取缓存
$product = Cache::get($cacheKey);
if ($product !== null) {
return $product;
}
// 2. 尝试获取互斥锁(只有第一个请求能成功)
$lock = Cache::lock($lockKey, $lockTimeout);
if ($lock->get()) {
try {
// 3. 双重检测:在拿到锁后再次检查缓存
$product = Cache::get($cacheKey);
if ($product !== null) {
return $product;
}
// 4. 从数据库加载数据(只有一个线程执行)
$product = Product::find($id);
// 5. 写入缓存
Cache::put($cacheKey, $product, $ttl);
return $product;
} finally {
// 6. 释放锁
$lock->release();
}
}
// 7. 其他线程:等待或直接返回降级数据
// 方案A:等待锁释放后重试
sleep(50); // 微秒级等待
return Cache::get($cacheKey); // 此时应该已有缓存
// 方案B:直接返回旧数据(如果有)
// return Cache::get($cacheKey) ?? $this->fallbackData();
}
更优雅的封装
使用闭包的方式
use Illuminate\Cache\CacheManager;
use Illuminate\Contracts\Cache\LockTimeoutException;
class CacheService
{
public function rememberWithLock($key, $ttl, callable $callback, $lockTimeout = 10)
{
// 先尝试获取缓存
if ($cached = Cache::get($key)) {
return $cached;
}
$lock = Cache::lock("lock:{$key}", $lockTimeout);
try {
// 尝试获取锁,超时则抛出异常
$lock->block(5); // 最多等待5秒
// 双重检测
if ($cached = Cache::get($key)) {
return $cached;
}
// 执行回调重建缓存
$value = $callback();
Cache::put($key, $value, $ttl);
return $value;
} catch (LockTimeoutException $e) {
// 获取锁超时,返回旧数据或降级
return Cache::get($key) ?? $callback();
} finally {
optional($lock)->release();
}
}
}
// 使用
$product = app(CacheService::class)->rememberWithLock(
"product_{$id}",
3600,
fn() => Product::find($id)
);
其他解决方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 互斥锁 | 精确控制并发,数据一致性高 | 增加锁开销,可能降低吞吐量 |
| 二级缓存 | 读多写少场景性能好 | 实现复杂,内存占用大 |
| 异步重建 | 不影响用户请求 | 数据可能滞后 |
| 逻辑过期 | 简单高效 | 需要额外字段,可能读到过期数据 |
生产环境建议
使用 Redis 分布式锁
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
// 确保使用 Redis 驱动,支持分布式锁
设置合理的 TTL
// 热点数据:基础 TTL + 随机偏移,防止雪崩 $baseTtl = 3600; $randomOffset = rand(0, 300); Cache::put($key, $value, $baseTtl + $randomOffset);
熔断降级
try {
return cache()->lock('key', 10)->block(3, function () {
// 重建缓存
});
} catch (LockTimeoutException) {
// 降级返回默认值
return $this->getDefaultProduct();
}
监控告警
$start = microtime(true);
try {
// 业务代码
} finally {
$duration = (microtime(true) - $start) * 1000;
if ($duration > 500) {
Log::warning("Cache rebuild slow: {$duration}ms", ['key' => $key]);
}
}
完整示例:商品详情页
class ProductController
{
public function show($id)
{
$cacheKey = "product:detail:{$id}";
return Cache::remember($cacheKey, 3600, function () use ($id) {
// 使用互斥锁保护数据库
$lock = Cache::lock("product:lock:{$id}", 10);
return $lock->get(function () use ($id) {
// 双重缓存检测
$cached = Cache::get("product:lock:{$id}");
return Product::with(['category', 'skus'])
->where('status', 1)
->findOrFail($id);
});
});
}
}
最佳实践总结:
- 高并发场景:必须使用互斥锁 + 双重检测
- 普通场景:使用
Cache::remember即可 - 极端场景:配合 CDN、本地缓存、异步更新等多种策略
互斥锁是解决缓存击穿最直接有效的方式,但要处理好锁的超时和释放,避免死锁和性能问题。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播