Redis缓存击穿七层防御体系:构建坚不可摧的缓存护甲

首页 编程分享 PHP丨JAVA丨OTHER 正文

有才叔 转载 编程分享 2025-06-28 22:06:05

简介 七层护甲:英勇黄铜、不屈白银、华贵铂金、璀璨钻石、超凡大师、傲世宗师、最强王者。驯服猛兽构建坚不可摧的缓存护甲


缓存击穿发生在高并发查询某个不存在或刚过期的热点Key时,大量请求直接穿透缓存冲击数据库。本文将为你构建一套七层防御体系,彻底驯服这只猛兽。


第一层护甲:空值缓存(英勇黄铜)

核心思想:即使数据库查无数据,也缓存空结果(如NULL或特殊标记),避免反复穿透。

// 伪代码示例:缓存空值策略
public Product getProduct(String id) {
    // 1. 尝试从Redis获取
    String cacheKey = "product:" + id;
    Product product = redis.get(cacheKey, Product.class);
    
    if (product != null) {
        // 2. 命中空值标记(如"NULL")直接返回
        if ("NULL".equals(product.getSpecialFlag())) 
            return null; 
        return product;
    }
    
    // 3. 查数据库
    product = db.query("SELECT * FROM products WHERE id = ?", id);
    
    if (product == null) {
        // 4. 数据库无数据,缓存空值(5分钟过期)
        redis.setex(cacheKey, 300, "NULL"); 
    } else {
        // 5. 数据库有数据,正常缓存
        redis.setex(cacheKey, 3600, product); 
    }
    return product;
}

第二层护甲:布隆过滤器(不屈白银)

核心思想:在缓存访问前增加一层过滤器,拦截绝对不存在的Key请求。

# Python使用pybloom库示例
from pybloom import ScalableBloomFilter

# 初始化可扩容布隆过滤器
bloom_filter = ScalableBloomFilter(initial_capacity=1000)

# 预热:将有效ID加入过滤器
valid_ids = ["prod_1001", "prod_1002", "prod_1005"]
for id in valid_ids:
    bloom_filter.add(id)

# 查询请求处理
def handle_request(product_id):
    if product_id not in bloom_filter:
        return "Invalid ID"  # 快速拦截非法请求
    # ...继续后续缓存查询逻辑

第三层护甲:互斥锁(华贵铂金)

核心思想:当缓存失效时,仅允许一个线程重建缓存,其他线程等待或轮询。

// 基于Redis分布式锁的实现
public Product getProductWithLock(String id) {
    String cacheKey = "product:" + id;
    Product product = redis.get(cacheKey, Product.class);
    if (product != null) return product;

    // 尝试获取分布式锁 (SET key value NX EX 实现原子操作)
    String lockKey = "lock:product:" + id;
    String clientId = UUID.randomUUID().toString(); // 唯一标识当前实例
    boolean locked = redis.set(lockKey, clientId, "NX", "EX", 5); // 锁5秒超时
    
    if (locked) {
        try {
            // 再次检查缓存(防止其他线程已更新)
            product = redis.get(cacheKey, Product.class);
            if (product != null) return product;
            
            // 查询数据库并重建缓存
            product = db.queryProduct(id);
            redis.setex(cacheKey, 3600, product);
        } finally {
            // 使用Lua脚本保证原子性释放锁
            String luaScript = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else return 0 end";
            redis.eval(luaScript, 1, lockKey, clientId);
        }
    } else {
        // 未获锁,短暂睡眠后重试
        Thread.sleep(100);
        return getProductWithLock(id); // 递归重试(需注意深度)
    }
    return product;
}

第四层护甲:热点数据永不过期(璀璨钻石)

核心思想:对极端热点Key,设置逻辑过期时间而非物理过期,异步刷新缓存。

// 逻辑过期时间示例
public class ProductWrapper {
    private Product product;
    private long expireTime; // 逻辑过期时间戳
    
    // 构造函数、getter、setter省略
}

public Product getHotProduct(String id) {
    ProductWrapper wrapper = redis.get("hot:"+id, ProductWrapper.class);
    if (System.currentTimeMillis() < wrapper.getExpireTime()) {
        return wrapper.getProduct(); // 未过期直接返回
    }
    
    // 触发异步更新(如提交到线程池)
    threadPool.submit(() -> rebuildHotProductCache(id));
    return wrapper.getProduct(); // 仍返回旧数据
}

private void rebuildHotProductCache(String id) {
    // 获取最新数据,更新缓存(带新过期时间)
    Product newProd = db.queryProduct(id);
    ProductWrapper newWrapper = new ProductWrapper(newProd, 
        System.currentTimeMillis() + 3600_000); // 1小时后过期
    redis.set("hot:"+id, newWrapper);
}

第五层护甲:熔断降级(超凡大师)

核心思想:当数据库压力过大时,主动熔断部分请求,返回兜底数据保护系统。

// 使用Resilience4j熔断器示例
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("dbCircuit");

public Product getProductWithCircuitBreaker(String id) {
    return circuitBreaker.executeSupplier(() -> {
        // 尝试走正常缓存/数据库逻辑
        return getProduct(id); 
    });
}

// 配置熔断规则(当失败率>50%时熔断10秒)
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofSeconds(10))
    .build();

第六层护甲:多级缓存(傲世宗师)

核心思想:在Redis前增加本地缓存(如Caffeine),进一步减少穿透概率。

// Caffeine + Redis 二级缓存
LoadingCache<String, Product> localCache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(30, TimeUnit.SECONDS)
    .build(key -> {
        // 当本地缓存未命中,从Redis加载
        Product p = redis.get("product:"+key, Product.class);
        if (p == null) {
            p = db.queryProduct(key); // Redis没有则查库
            redis.setex("product:"+key, 3600, p);
        }
        return p;
    });

// 最终获取产品方法
public Product getProductMultiLevel(String id) {
    return localCache.get(id);
}

第七层护甲:请求合并(最强王者)

核心思想:将短时间内对同一Key的多次请求合并为一次数据库查询。

// 使用Guava的LoadingCache合并请求
LoadingCache<String, Product> requestMergerCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .build(new CacheLoader<String, Product>() {
        @Override
        public Product load(String key) {
            // 真实查询数据库(多个线程会等待此结果)
            return db.queryProduct(key);
        }
    });

public Product getProductMerged(String id) {
    try {
        return requestMergerCache.get(id);
    } catch (Exception e) {
        // 异常处理
    }
}

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


实战总结

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

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

转载链接:https://juejin.cn/post/7519481236423852068


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云