本文目录导读:

我来详细讲解PHP缓存穿透的排查方法和解决方案。
缓存穿透的识别与排查
现象确认
// 异常特征: // - 缓存命中率骤降 // - 数据库QPS突然飙升 // - 大量请求查询不存在的数据
监控指标排查
// 1. 监控缓存命中率
$stats = $redis->info();
$hits = $stats['keyspace_hits'];
$misses = $stats['keyspace_misses'];
$hitRate = $hits / ($hits + $misses) * 100;
if ($hitRate < 50) {
// 可能存在穿透风险
logger::alert("缓存命中率过低: " . $hitRate . "%");
}
// 2. 监控数据库慢查询
// 查看数据库慢查询日志
// MySQL: SHOW VARIABLES LIKE 'slow_query_log';
// 找到特定时间段的慢查询记录
// 3. 监控异常请求
// 检查日志中的异常请求模式
$log = tail('/var/log/php/error.log');
if (preg_match('/SELECT.*WHERE id=(\d+)/', $log, $matches)) {
// 大量非连续ID查询
logger::warning("疑似缓存穿透请求");
}
日志分析
// 添加专门的穿透检测日志
class CacheService {
public function get($key) {
// 记录所有未命中的请求
$value = $this->redis->get($key);
if ($value === false) {
$this->logCacheMiss($key, $_SERVER['REQUEST_URI']);
}
return $value;
}
private function logCacheMiss($key, $uri) {
$log = [
'time' => date('Y-m-d H:i:s'),
'key' => $key,
'uri' => $uri,
'ip' => $_SERVER['REMOTE_ADDR']
];
file_put_contents('/var/log/cache_miss.log', json_encode($log) . "\n", FILE_APPEND);
}
}
常见原因分析
代码层面排查
// 检查是否有无效查询
class ProductService {
public function getProduct($id) {
// 问题代码
$data = $this->cache->get("product:" . $id);
if (!$data) {
// 直接查询数据库,没有空值缓存
$data = $this->db->query("SELECT * FROM products WHERE id = $id")->fetch();
// 如果不存在,每次都查数据库
}
return $data;
}
}
业务场景分析
// 高并发请求不存在的数据
// 示例:攻击者请求 -1 或超大ID
$id = $_GET['id']; // 可能是-1, 0, 999999999
$data = $cache->get("user:" . $id);
if (!$data) {
// 每次都穿透到数据库
$user = $db->query("SELECT * FROM users WHERE id = " . $id);
// 不存在则每次查询都落空
}
解决方案
空值缓存
class ProductService {
private $cache;
private $db;
public function getProduct($id) {
// 参数过滤
if (!is_numeric($id) || $id <= 0) {
return null;
}
$cacheKey = "product:{$id}";
$data = $this->cache->get($cacheKey);
// 检查是否为空值标记
if ($data === 'NULL_VALUE') {
return null;
}
if (!$data) {
$data = $this->db->query(
"SELECT * FROM products WHERE id = ?",
[$id]
)->fetch();
if (!$data) {
// 缓存空值,设置较短过期时间
$this->cache->set($cacheKey, 'NULL_VALUE', 60); // 1分钟
return null;
}
// 正常缓存
$this->cache->set($cacheKey, $data, 3600);
}
return $data;
}
}
布隆过滤器
class BloomFilter {
private $redis;
private $bfKey = 'product_bloom_filter';
// 初始化布隆过滤器
public function init() {
// 创建布隆过滤器,错误率0.01%,预期元素100万
$this->redis->rawCommand(
'BF.RESERVE',
$this->bfKey,
'0.0001',
'1000000'
);
}
// 添加商品ID到过滤器
public function add($id) {
$this->redis->rawCommand('BF.ADD', $this->bfKey, $id);
}
// 批量添加
public function addBatch($ids) {
$pipeline = $this->redis->pipeline();
foreach ($ids as $id) {
$pipeline->rawCommand('BF.ADD', $this->bfKey, $id);
}
$pipeline->exec();
}
// 检查是否存在(可能有误判)
public function mightContain($id) {
return $this->redis->rawCommand('BF.EXISTS', $this->bfKey, $id);
}
}
// 使用布隆过滤器
class ProductService {
private $bloomFilter;
public function getProduct($id) {
// 先检查布隆过滤器
if (!$this->bloomFilter->mightContain($id)) {
return null; // 一定不存在
}
// 可能存在的才查询缓存和数据库
$cacheKey = "product:{$id}";
$data = $this->cache->get($cacheKey);
if (!$data) {
$data = $this->db->query(
"SELECT * FROM products WHERE id = ?",
[$id]
)->fetch();
if ($data) {
$this->cache->set($cacheKey, $data, 3600);
}
}
return $data;
}
}
请求限流与IP限制
class RateLimiter {
private $redis;
private $limit = 10; // 每分钟最多10次
private $window = 60;
public function check($ip, $action) {
$key = "rate_limit:{$ip}:{$action}";
$count = $this->redis->incr($key);
if ($count === 1) {
$this->redis->expire($key, $this->window);
}
return $count <= $this->limit;
}
public function block($ip, $duration = 3600) {
$key = "blocked_ip:{$ip}";
$this->redis->set($key, 1, $duration);
}
public function isBlocked($ip) {
return $this->redis->exists("blocked_ip:{$ip}");
}
}
// 使用限流
class ApiController {
public function detail($id) {
$ip = $_SERVER['REMOTE_ADDR'];
$rateLimiter = new RateLimiter();
// 检查是否被封锁
if ($rateLimiter->isBlocked($ip)) {
return response(403, ['message' => 'IP已被临时封锁']);
}
// 检查访问频率
$action = "product_detail";
if (!$rateLimiter->check($ip, $action)) {
// 记录异常并封锁
logger::warning("高频访问: {$ip} - action: {$action}");
$rateLimiter->block($ip, 600);
return response(429, ['message' => '请求过于频繁']);
}
// 正常业务逻辑
$service = new ProductService();
return $service->getProduct($id);
}
}
参数校验与规范化
class RequestValidator {
public function validateProductId($id) {
// 1. 类型检查
if (!is_numeric($id)) {
throw new InvalidArgumentException('无效的商品ID');
}
// 2. 范围检查
$id = (int)$id;
if ($id < 1 || $id > 1000000) {
throw new OutOfRangeException('商品ID超出范围');
}
// 3. 格式规范化
// 防止 001 和 1 造成重复查询
return $id;
}
public function validateSearchParams($params) {
// 黑名单关键词
$blocked = ['union', 'select', 'where', 'and', 'or', '--'];
foreach ($params as $key => $value) {
foreach ($blocked as $word) {
if (stripos($value, $word) !== false) {
throw new SecurityException('非法搜索关键词');
}
}
}
return true;
}
}
性能监控与告警
class CacheMonitor {
private $redis;
private $db;
public function monitor() {
$metrics = [];
// 1. 缓存命中率
$metrics['hit_rate'] = $this->getCacheHitRate();
// 2. 数据库连接数
$metrics['db_connections'] = $this->getDbConnections();
// 3. 慢查询数量
$metrics['slow_queries'] = $this->getSlowQueries();
// 4. 异常请求比例
$metrics['abnormal_requests'] = $this->getAbnormalRequestCount();
// 检查是否超过阈值
if ($metrics['hit_rate'] < 80) {
$this->sendAlert('缓存命中率过低', $metrics);
}
return $metrics;
}
}
// 部署监控页面
// /monitor/cache.php
$monitor = new CacheMonitor();
$data = $monitor->monitor();
echo json_encode($data, JSON_PRETTY_PRINT);
实战排查流程
// 1. 查看今日缓存命中率 redis-cli INFO stats | grep hits // 2. 查看数据库慢查询 mysql> SHOW GLOBAL STATUS LIKE 'Slow_queries'; mysql> SHOW FULL PROCESSLIST; // 3. 分析PHP日志 tail -f /var/log/php/error.log | grep -i "cache miss\|穿透" // 4. 使用tcpdump分析网络请求 tcpdump -i eth0 -c 1000 port 3306 | grep SELECT // 5. 通过Xdebug分析具体代码 // 配置xdebug xdebug.start_with_request=yes xdebug.mode=debug
最佳实践建议
final class CacheSolution {
// 1. 分层缓存策略
public static function layeredCache($key, $callback) {
// L1: 本地内存缓存
$local = self::getFromLocal($key);
if ($local) return $local;
// L2: Redis缓存
$redis = self::getFromRedis($key);
if ($redis) {
self::setToLocal($key, $redis, 30); // 30秒
return $redis;
}
// L3: 查数据库
$data = $callback();
// 空值处理
if ($data === null) {
self::setToRedis($key, null, 60); // 1分钟
} else {
self::setToRedis($key, $data, 3600); // 1小时
}
return $data;
}
// 2. 预热机制
public static function warmUp() {
// 启动时加载热门数据
$hotProducts = DB::query("SELECT * FROM products WHERE hot = 1");
foreach ($hotProducts as $product) {
self::setToRedis("product:" . $product['id'], $product, 3600);
}
}
// 3. 自动降级
public static function degradeIfAbnormal() {
$hitRate = self::getHitRate();
if ($hitRate < 50) {
// 触发降级模式
Redis::disableWrite(); // 停止缓存写入
// 启用本地文件缓存
self::enableFileCache();
}
}
}
通过以上方法,你可以系统地排查和解决PHP缓存穿透问题,重点是要建立完善的监控体系,及时发现异常,并采取多层防护措施。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播