题目
ThinkPHP 6 中如何实现一个高性能的分布式缓存方案,并解决缓存穿透、击穿和雪崩问题?
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
分布式缓存设计,缓存问题解决方案(穿透、击穿、雪崩),ThinkPHP缓存机制深入,高并发场景优化
快速回答
在ThinkPHP 6中实现高性能分布式缓存方案需要:
- 使用Redis集群或代理模式(如Twemproxy)实现分布式缓存
- 缓存穿透:布隆过滤器+空值缓存策略
- 缓存击穿:互斥锁(Redis SETNX)+热点数据永不过期
- 缓存雪崩:随机过期时间+多级缓存+熔断机制
- 通过自定义驱动扩展ThinkPHP缓存组件
一、核心问题分析
缓存穿透:恶意查询不存在的数据,绕过缓存直击数据库。
缓存击穿:热点key过期瞬间,大量请求穿透到数据库。
缓存雪崩:大量缓存同时失效,数据库压力激增。
二、分布式缓存架构设计
Redis集群配置(config/cache.php):
return [
'default' => 'redis',
'stores' => [
'redis' => [
'type' => 'redis',
'host' => [
'redis1:6379',
'redis2:6379',
'redis3:6379'
],
'password' => 'your_password',
'select' => 0,
'timeout' => 2, // 超时控制
'persistent' => true // 长连接
]
]
];自定义驱动扩展(解决原生驱动不足):
// 扩展src/cache/driver/RedisCluster.php
class RedisCluster extends \think\cache\driver\Redis
{
protected function getCacheKey(string $key): string
{
// 使用一致性哈希分片
$slot = crc32($key) % count($this->options['host']);
$this->handler->selectNode($slot);
return parent::getCacheKey($key);
}
}三、三大缓存问题解决方案
1. 缓存穿透解决方案
布隆过滤器+空值缓存:
public function getProduct(int $id)
{
$key = "product_{$id}";
// 布隆过滤器检查
if (!$this->bloomFilter->exists('products', $id)) {
return null; // 直接拦截
}
$data = Cache::get($key);
if ($data === null) {
$data = Db::name('product')->find($id);
if (empty($data)) {
// 空值缓存(短时间)
Cache::set($key, 'NIL', 60); // 60秒防穿透
return null;
}
Cache::set($key, $data, 3600);
} elseif ($data === 'NIL') {
return null; // 空值标识
}
return $data;
}2. 缓存击穿解决方案
Redis互斥锁(SETNX):
public function getHotProduct(int $id)
{
$key = "hot_product_{$id}";
$lockKey = $key."_lock";
$data = Cache::get($key);
if ($data === null) {
// 尝试获取分布式锁
if (Cache::store('redis')->set($lockKey, 1, ['nx', 'ex' => 10])) {
$data = Db::name('product')->find($id);
Cache::set($key, $data, 3600);
Cache::delete($lockKey); // 释放锁
} else {
// 未抢到锁时短暂等待
usleep(200000); // 200ms
return $this->getHotProduct($id); // 重试
}
}
return $data;
}3. 缓存雪崩解决方案
随机过期时间+多级缓存:
// 设置缓存时添加随机因子
$expire = 3600 + mt_rand(-600, 600); // 1小时±10分钟
Cache::set('catalog_data', $data, $expire);
// 多级缓存(本地+分布式)
public function getCatalog()
{
$localData = $this->localCache->get('catalog');
if ($localData) return $localData;
$redisData = Cache::get('catalog');
if ($redisData) {
$this->localCache->set('catalog', $redisData, 60); // 本地缓存1分钟
return $redisData;
}
// 数据库查询...
}四、最佳实践与注意事项
- 熔断机制:集成Swoole熔断器,当DB请求失败率>50%时自动熔断
- 监控指标:通过Prometheus监控缓存命中率、DB查询QPS
- 热点数据:
- 使用
think-annotation标记热点方法 - 后台定时异步刷新永不过期热点数据
- 使用
- 常见错误:
- 互斥锁未设置超时导致死锁(必须加EX参数)
- 布隆过滤器未初始化造成误拦截
- 本地缓存与分布式缓存不一致
五、扩展知识
- 一致性哈希:减少节点变动时的缓存迁移量
- 缓存预热:在服务启动时加载热点数据
- ThinkPHP事件监听:通过
CacheMiss事件统一处理穿透逻辑 - Redis新特性:Redis 6.0的Client-side caching替代多级缓存