Redis 7.x多级缓存架构设计与雪崩穿透防护实战

为什么单层Redis缓存不够用

单层Redis缓存在高并发场景下有三个瓶颈:一是Redis本身成为热点(大量请求打到同一分片);二是Redis故障时缓存全部失效,请求瞬间压垮数据库;三是网络延迟——即使Redis响应1ms,跨机房场景下单次请求的Redis往返可能达到5-10ms。

多级缓存通过在应用层(JVM本地缓存)和分布式缓存(Redis)之间增加一层,将热点数据的读取延迟降低到纳秒级,同时降低Redis集群压力。

多级缓存架构设计

典型的三级缓存架构:

请求 → 本地缓存(L1) → Redis缓存(L2) → 数据库(L3)
         Caffeine         Redis Cluster    MySQL/PG
         延迟: ~ns         延迟: ~1ms       延迟: ~5ms
         容量: ~GB         容量: ~100GB      容量: ~TB

L1本地缓存选型:

Caffeine是Java生态性能最好的本地缓存,基于W-TinyLFU淘汰算法:

// Caffeine配置
Cache<String, Product> localCache = Caffeine.newBuilder()
    .maximumSize(10_000)           // 最大条目数
    .expireAfterWrite(Duration.ofMinutes(10))  // 写入10分钟后过期
    .refreshAfterWrite(Duration.ofMinutes(5))  // 写入5分钟后异步刷新
    .recordStats()                // 开启统计
    .build();

// 多级缓存读取
public Product getProduct(Long productId) {
    String key = "product:" + productId;

    // L1: 本地缓存
    Product product = localCache.getIfPresent(key);
    if (product != null) {
        return product;
    }

    // L2: Redis缓存
    product = redisTemplate.opsForValue().get(key);
    if (product != null) {
        localCache.put(key, product);  // 回填L1
        return product;
    }

    // L3: 数据库
    product = productMapper.selectById(productId);
    if (product != null) {
        redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
        localCache.put(key, product);
    }
    return product;
}

缓存雪崩防护

缓存雪崩指大量缓存Key在同一时刻过期,请求全部穿透到数据库。

方案一:过期时间加随机偏移

def set_cache_with_jitter(key, value, base_ttl_minutes=30):
    """设置缓存时加入随机偏移,避免同时过期"""
    jitter = random.randint(0, base_ttl_minutes // 3)
    ttl = base_ttl_minutes * 60 + jitter * 60
    redis.setex(key, ttl, value)

方案二:互斥锁重建缓存

缓存失效时只允许一个线程重建,其他线程等待:

public Product getProductWithMutex(Long productId) {
    String key = "product:" + productId;
    String lockKey = "lock:" + key;

    // 尝试从缓存获取
    Product product = getFromCache(key);
    if (product != null) return product;

    // 缓存未命中,尝试获取互斥锁
    if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
        try {
            // 双重检查
            product = getFromCache(key);
            if (product != null) return product;

            // 查数据库并重建缓存
            product = productMapper.selectById(productId);
            setCacheWithJitter(key, product);
            return product;
        } finally {
            redisTemplate.delete(lockKey);
        }
    } else {
        // 未获取到锁,短暂等待后重试
        try { Thread.sleep(50); } catch (InterruptedException e) {}
        return getProductWithMutex(productId);
    }
}

方案三:逻辑过期

不设置TTL,而是在Value中存储逻辑过期时间。过期后返回旧数据,异步更新:

// 逻辑过期数据结构
public class CacheData<T> {
    private T data;
    private LocalDateTime expireTime;
}

// 异步刷新
public Product getProductLogicalExpire(Long productId) {
    String key = "product:" + productId;
    CacheData<Product> cacheData = redisTemplate.opsForValue().get(key);

    if (cacheData == null) {
        return loadAndCache(productId, key);
    }

    if (cacheData.getExpireTime().isAfter(LocalDateTime.now())) {
        return cacheData.getData();
    }

    // 逻辑过期,异步刷新
    CACHE_REFRESH_EXECUTOR.submit(() -> loadAndCache(productId, key));

    // 返回旧数据
    return cacheData.getData();
}

private static final ExecutorService CACHE_REFRESH_EXECUTOR =
    Executors.newFixedThreadPool(10, r -> {
        Thread t = new Thread(r, "cache-refresh");
        t.setDaemon(true);
        return t;
    });

缓存穿透防护

缓存穿透指查询不存在的数据,缓存无法命中,请求直达数据库。

方案一:布隆过滤器

Redis 7.x原生支持RedisBloom模块:

# 加载布隆过滤器(启动时从数据库预热)
bf.reserve user_ids 0.001 1000000  # 误判率0.1%,初始容量100万

# 插入所有存在的用户ID
for user_id in all_user_ids:
    bf.add user_ids $user_id

# 查询前先检查布隆过滤器
def get_user(user_id):
    exists = redis.execute_command('BF.EXISTS', 'user_ids', user_id)
    if not exists:
        return None  # 布隆过滤器说不存在,一定不存在
    return get_user_from_cache_or_db(user_id)

方案二:空值缓存

对查询结果为空的Key也缓存,设置较短的TTL:

def get_user(user_id):
    key = f"user:{user_id}"

    cached = redis.get(key)
    if cached is not None:
        if cached == "NULL_MARKER":
            return None
        return json.loads(cached)

    user = db.query_user(user_id)
    if user:
        redis.setex(key, 1800, json.dumps(user))
    else:
        redis.setex(key, 60, "NULL_MARKER")

    return user

缓存击穿防护

缓存击穿指热点Key过期的瞬间,大量并发请求同时穿透到数据库。与雪崩的区别是:击穿是单个热点Key,雪崩是大量Key同时过期。

方案:热点Key永不过期 + 异步刷新

# 热点Key检测
class HotKeyDetector:
    def __init__(self, threshold=1000, window=60):
        self.counter = {}
        self.threshold = threshold
        self.window = window

    def check(self, key):
        count = self.counter.get(key, 0) + 1
        self.counter[key] = count
        return count >= self.threshold

    def reset(self):
        self.counter.clear()

# 定时任务:检测热点Key并设置永不过期
async def hotkey_protection():
    while True:
        await asyncio.sleep(60)
        for key in detector.get_hot_keys():
            ttl = redis.ttl(key)
            if 0 < ttl < 300:
                redis.persist(key)
                await schedule_refresh(key)

Redis 7.x新特性在缓存场景的应用

Function替代Lua脚本:

Redis 7.x引入Function,比Lua脚本更易管理和版本化:

# 注册Function
redis.function load REPLACE /path/to/cache_functions.lua

# cache_functions.lua
redis.register_function{
    name = 'get_or_set_cache',
    flags = { 'no-writes' },
    fn = function(keys, args)
        local key = keys[1]
        local value = redis.call('GET', key)
        if value then
            return value
        end
        local lock_key = 'lock:' .. key
        local locked = redis.call('SETNX', lock_key, '1')
        if locked == 1 then
            redis.call('EXPIRE', lock_key, 10)
            return nil
        else
            return 'RETRY'
        end
    end
}

Multi-part AOF——Redis 7.x的AOF文件分为base和incremental两部分,重启加载更快,缓存恢复时间缩短。

Client-side caching——Redis 7.x完善了客户端缓存协议,应用可直接在本地维护缓存子集,Redis推送失效通知:

# 开启客户端缓存
CLIENT TRACKING ON REDIRECT <client_id> BCAST PREFIX product:

# Redis自动推送product:前缀的失效通知
# 应用收到通知后清除本地缓存

缓存架构的设计没有银弹——多级缓存增加了复杂度,本地缓存和分布式缓存的一致性维护需要额外机制。选择几级缓存取决于流量规模和延迟要求:日PV百万级别单层Redis即可,千万级以上才需要多级缓存。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/redis7x-duo-ji-huan-cun-jia-gou-she-ji-yu-xue-beng-chuan/

(0)
小编小编
上一篇 14小时前
下一篇 14小时前

相关推荐