Redis缓存击穿七层防御体系:构建坚不可摧的缓存护甲(PHP版实例)

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

实例代码: 

<?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);
    }

}

实战总结

七层防御体系并非需要全量部署,而是根据业务场景灵活组合:

  1. 基础必选:空值缓存 + 互斥锁(90%场景适用)
  2. 热点场景:布隆过滤器 + 永不过期策略(比如明星婚讯、秒杀商品)
  3. 系统加固:熔断降级 + 多级缓存(高并发金融系统)
  4. 极致优化:请求合并(长尾请求优化)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值