防御体系全景图(思维导图)

实例代码:
<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Cache;
use think\Log;
use think\Db;
/**
* Redis缓存击穿七层防御体系:构建坚不可摧的缓存护甲 接口
*/
class Redisprotect extends Api
{
// 不需要登录即可访问的方法列表
protected $noNeedLogin = ['*'];
// 不需要权限验证即可访问的方法列表
protected $noNeedRight = ['*'];
// 第六层:本地缓存
// 使用静态数组来存储本地缓存,以减少对Redis的访问次数
protected static $localCache = [];
// 第七层:请求合并缓存
// 使用静态数组来合并请求,减少对数据库的查询次数
protected static $mergeCache = [];
/**
* 初始化方法,在类实例化时执行
* 主要用于构建布隆过滤器,将合法的产品ID标记到Redis位图中
*/
public function _initialize()
{
// 定义布隆过滤器的键名
$bloomKey = 'bloom:product';
// 获取Redis的底层操作实例
$redis = Cache::store('redis')->handler();
// 模拟从数据库中获取产品ID列表
$ids = Db::name('product')->column('id');
// 遍历产品ID列表,为每个ID计算偏移量并设置布隆过滤器中的对应位为1
foreach ($ids as $id) {
// 使用crc32哈希算法计算ID的哈希值,并取模得到偏移量
$offset = crc32($id) % 100000;
// 在Redis中设置位图的对应位置为1
$redis->setBit($bloomKey, $offset, 1);
// 同时将产品信息缓存到本地缓存和请求合并缓存中
self::$localCache[$id] = self::$mergeCache[$id] = Db::name('product')->find($id);
}
}
/**
* 第一层护甲:空值缓存(英勇黄铜)
* 当缓存中没有数据时,即使数据库中也没有数据,也缓存一个空值(如NULL或特殊标记)
* 以避免反复穿透缓存直接查询数据库
*/
public function first_layer_of_armor($id)
{
// 定义缓存键名,格式为:product:产品ID
$cacheKey = "product:$id";
// 尝试从缓存中获取产品信息
$product = Cache::get($cacheKey);
// 如果缓存中存在数据(包括空值)
if ($product !== false) {
// 如果缓存中存储的是空值标记('NULL'),则返回空值结果
if ($product === 'NULL') {
$this->success('缓存命中:空值', null);
}
// 如果缓存中存储的是真实产品信息,则直接返回
$this->success('缓存命中', $product);
}
// 如果缓存中没有数据,则尝试从数据库中查找产品信息
$product = Db::name('product')->find($id);
// 如果数据库中没有找到对应的产品信息
if (!$product) {
// 缓存一个空值标记,防止未来对该ID的无效查询反复穿透缓存
Cache::set($cacheKey, 'NULL', 300); // 空值缓存5分钟
$this->success('数据库无数据,已缓存空值');
}
// 如果数据库中找到了对应的产品信息,则缓存该信息,并返回给客户端
Cache::set($cacheKey, $product, 3600);
$this->success('数据库查询成功并缓存', $product);
}
/**
* 第二层护甲:布隆过滤器(不屈白银)
* 在缓存访问前增加一层过滤器,拦截绝对不存在的Key请求
*/
public function second_layer_of_armor($id)
{
// 定义布隆过滤器的键名
$bloomKey = 'bloom:product';
// 获取底层 Redis 实例
$redis = Cache::store('redis')->handler();
// 统一 hash 算法(预存和判断都必须一致)
// 计算ID的哈希值,并取模得到偏移量
$offset = crc32($id) % 100000;
// 使用 getBit 判断布隆过滤器是否命中
// 如果对应位为0,说明布隆过滤器判断该ID肯定不存在于数据库中
if (!$redis->getBit($bloomKey, $offset)) {
// 返回拦截结果
$this->success('布隆过滤器拦截,非法ID', ['id' => $id]);
}
// 如果对应位为1,可能表示ID存在于数据库中,允许继续查询
$this->success('通过布隆过滤器,允许查询', ['id' => $id]);
}
/**
* 第三层护甲:互斥锁(华贵铂金)
* 当缓存失效时,仅允许一个线程重建缓存,其他线程等待或轮询
*/
public function third_layer_of_armor($id)
{
// 定义缓存键名,格式为:product:mutex:产品ID
$cacheKey = "product:mutex:" . $id;
// 定义互斥锁键名,格式为:lock:product:产品ID
$lockKey = "lock:product:" . $id;
// 尝试从缓存中获取产品信息
$product = Cache::get($cacheKey);
// 如果缓存中存在数据,则直接返回该数据
if ($product) {
$this->success('缓存命中', $product);
}
// 如果缓存中没有数据,则尝试获取互斥锁
// 生成唯一的锁值
$lockValue = uniqid();
// 获取底层 Redis 实例
$redis = Cache::store('redis')->handler();
// 尝试设置互斥锁,如果设置成功则返回true
$gotLock = $redis->setex($lockKey, 5, $lockValue);
// 如果成功获取到互斥锁
if ($gotLock) {
try {
// 从数据库中查找对应的产品信息
$product = Db::name('product')->find($id);
// 将查询到的产品信息存入缓存
Cache::set($cacheKey, $product, 3600);
} finally {
// 释放互斥锁,使用Lua脚本保证原子性操作
$lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
$redis->eval($lua, [$lockKey, $lockValue], 1);
}
} else {
// 如果获取互斥锁失败,则等待1秒后重新尝试
sleep(1);
// 递归调用自身重新尝试获取锁
return $this->third_layer_of_armor($id);
}
// 如果重建缓存成功,则返回重建后的数据
$this->success('互斥锁更新缓存', $product);
}
/**
* 第四层护甲:热点数据永不过期(璀璨钻石)
* 对极端热点Key,设置逻辑过期时间而非物理过期,异步刷新缓存
*/
public function fourth_layer_of_armor($id)
{
// 定义缓存键名,格式为:hot:product:产品ID
$cacheKey = "hot:product:" . $id;
// 尝试从缓存中获取热点数据包装器(包含数据和过期时间)
$wrapper = Cache::get($cacheKey);
// 如果缓存中存在包装器,并且当前时间早于包装器中的逻辑过期时间
if ($wrapper && $wrapper['expire'] > time()) {
// 返回热点数据
$this->success('返回热点数据', $wrapper['data']);
}
// 如果缓存中没有数据,或者缓存数据已过期,则模拟异步刷新缓存
// 实际应用中,可以将此操作放入后台任务队列中异步执行
// 从数据库中查找对应的产品信息
$product = Db::name('product')->find($id);
// 创建包含实际数据和逻辑过期时间的数据包装器
$data = ['data' => $product, 'expire' => time() + 3600];
// 将数据包装器存入缓存,设置物理过期时间为逻辑过期时间之后的某个时间点
Cache::set($cacheKey, $data, $data['expire'] + 3600);
// 返回刷新后的热点数据
$this->success('逻辑过期已更新热点数据', $product);
}
/**
* 第五层护甲:熔断降级(超超大师)
* 当数据库压力过大时,主动熔断部分请求,返回兜底数据保护系统
*/
public function fifth_layer_of_armor($id)
{
// 模拟数据库操作的异常概率,假设20%的概率会触发异常
$failRate = rand(1, 100);
// 如果异常概率超过设定阈值(80%),则触发熔断机制
if ($failRate > 80) {
// 返回兜底数据,保护系统不会因数据库压力过大而崩溃
$this->success('数据库熔断,返回兜底数据', ['id' => $id, 'name' => '默认商品']);
}
// 如果没有触发异常,则正常查询数据库
$product = Db::name('product')->find($id);
// 返回正常查询结果
$this->success('正常返回数据', $product);
}
/**
* 第六层护甲:多级缓存(傲世宗师)
* 在Redis前增加本地缓存,进一步减少穿透概率
*/
public function sixth_layer_of_armor($id)
{
// 首先检查本地缓存中是否存在对应的产品信息
if (isset(self::$localCache[$id])) {
// 如果本地缓存中存在数据,则直接返回
$this->success('本地缓存命中', self::$localCache[$id]);
}
// 如果本地缓存中没有数据,则尝试从Redis缓存中获取
$redisKey = 'product:level2:' . $id;
$product = Cache::get($redisKey);
// 如果Redis缓存中没有数据,则从数据库中查找产品信息,并存入Redis缓存
if (!$product) {
$product = Db::name('product')->find($id);
Cache::set($redisKey, $product, 3600);
}
// 将从Redis缓存或数据库中获取的产品信息存入本地缓存
self::$localCache[$id] = $product;
// 返回多级缓存中的数据
$this->success('返回多级缓存数据', $product);
}
/**
* 第七层护甲:请求合并(最强王者)
* 将短时间内对同一Key的多次请求合并为一次数据库查询,减少数据库压力
*/
public function seventh_layer_of_armor($id)
{
// 首先检查请求合并缓存中是否存在对应的产品信息
if (isset(self::$mergeCache[$id])) {
// 如果请求合并缓存中存在数据,则直接返回
$this->success('请求合并缓存命中', self::$mergeCache[$id]);
}
// 如果缓存中没有数据,则从数据库中查询产品信息
$product = Db::name('product')->find($id);
// 将查询到的产品信息存入请求合并缓存
self::$mergeCache[$id] = $product;
// 返回数据库查询结果
$this->success('请求合并数据库查询', $product);
}
}
实战总结
七层防御体系并非需要全量部署,而是根据业务场景灵活组合:
- 基础必选:空值缓存 + 互斥锁(90%场景适用)
- 热点场景:布隆过滤器 + 永不过期策略(比如明星婚讯、秒杀商品)
- 系统加固:熔断降级 + 多级缓存(高并发金融系统)
- 极致优化:请求合并(长尾请求优化)

1994

被折叠的 条评论
为什么被折叠?



