3、业务场景与缓存策略:读多写少场景、热点数据场景、时间窗口场景

大家好,我是老张。今天咱们聊聊缓存命中率偏低时,最常遇到的三个业务场景。说实话,我见过太多团队一上来就堆Redis,结果命中率不到30%,白白浪费内存。你想想看,问题出在哪?

其实很简单——缓存策略没选对。不同的业务场景,就像不同的锁,你得用对应的钥匙去开。下面我结合自己的踩坑经历,把这三个典型场景掰开揉碎了讲清楚。

3.1 读多写少场景:缓存最舒服的“舒适区”

读多写少,说白了就是数据不怎么变,但大家反复查。比如商品详情页、用户配置信息、系统字典表。这种场景下,缓存命中率天然就高,但前提是你别自己搞砸了。

核心策略:Cache-Aside + 较长的过期时间

我个人习惯用Cache-Aside模式。读的时候先查缓存,没命中就查数据库,然后回填缓存。写的时候直接更新数据库,再删除缓存。

这里有个坑:过期时间设多长? 我建议设成业务可接受的最大延迟时间。比如商品信息允许5分钟延迟,那就设300秒。别设成永久,否则数据更新后,用户看到的还是旧数据。

// 读多写少场景的伪代码示例
public Product getProduct(Long id) {
    // 1. 先查缓存
    Product product = cache.get("product:" + id);
    if (product != null) {
        return product;
    }
    // 2. 缓存没命中,查数据库
    product = dao.getProductById(id);
    if (product != null) {
        // 3. 回填缓存,过期时间300秒
        cache.set("product:" + id, product, 300);
    }
    return product;
}

我的经验: 读多写少场景下,缓存击穿是最大威胁。我曾经遇到一个商品详情页,某个爆款商品上线后,缓存刚过期,瞬间几千个请求打到数据库,直接挂了。解决方案很简单:加一个互斥锁,或者用“永不过期+后台异步更新”的策略。

3.2 热点数据场景:流量洪峰下的“保命符”

热点数据,就是那些被大量请求同时访问的数据。比如双十一的秒杀商品、微博热搜、直播间弹幕。这种场景下,缓存命中率可能很高,但缓存雪崩和缓存击穿的风险也极高。

为什么会这样?你想想看,一个热点Key,如果过期了,所有请求同时去查数据库,数据库瞬间就扛不住了。我记得有一次,某大促活动,一个热点商品Key过期,导致数据库连接池被打满,整个服务都瘫痪了。

核心策略:本地缓存 + 分布式缓存 + 限流降级

我建议的做法是:在应用层加一层本地缓存(比如Caffeine或Guava Cache),把热点数据缓存在JVM内存里。这样即使Redis挂了,本地缓存还能顶一阵。

具体怎么识别热点数据?可以用LFU(Least Frequently Used)算法,统计每个Key的访问频率。当某个Key的QPS超过阈值时,自动把它加入本地缓存。

// 热点数据识别与本地缓存示例
public class HotKeyDetector {
    private static final int HOT_THRESHOLD = 1000; // QPS阈值
    private Cache<String, Object> localCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.SECONDS)
            .build();

    public Object getData(String key) {
        // 1. 先查本地缓存
        Object value = localCache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        // 2. 查Redis
        value = redis.get(key);
        if (value != null) {
            // 3. 判断是否为热点数据
            if (isHotKey(key)) {
                localCache.put(key, value);
            }
            return value;
        }
        // 4. 查数据库
        value = dao.getData(key);
        if (value != null) {
            redis.set(key, value, 300);
        }
        return value;
    }

    private boolean isHotKey(String key) {
        // 统计QPS的逻辑,略
        return true;
    }
}

注意: 本地缓存虽然快,但会占用应用内存。我建议把本地缓存的大小控制在1000个条目以内,过期时间不超过10秒。否则,内存溢出可不是闹着玩的。

3.3 时间窗口场景:数据“过期不候”

时间窗口场景,指的是数据只在某个时间段内有效。比如:验证码5分钟有效、限时抢购活动、每日签到数据。这种场景下,缓存命中率往往不高,因为数据过期很快。

嗯,这里要注意:不要用固定的过期时间。比如所有验证码都设300秒过期,那如果用户在第299秒输入,缓存刚好过期,就会导致验证失败。用户体验极差。

核心策略:TTL(Time To Live)+ 滑动窗口

我建议的做法是:用Redis的EXPIRE命令设置过期时间,同时配合滑动窗口机制。比如验证码,每次验证时,如果验证码正确但缓存已过期,就返回“验证码已过期”,而不是直接报错。

另外,时间窗口场景下,缓存穿透是个常见问题。比如恶意用户不断请求一个不存在的验证码,每次都会穿透到数据库。解决方案是:用布隆过滤器,或者缓存空值(但过期时间要短,比如30秒)。

// 时间窗口场景的验证码校验示例
public boolean verifyCode(String phone, String code) {
    String key = "sms:code:" + phone;
    String cachedCode = redis.get(key);
    if (cachedCode == null) {
        // 缓存已过期,返回“验证码已过期”
        return false; // 或者抛出特定异常
    }
    if (cachedCode.equals(code)) {
        // 验证成功,删除缓存(防止重复使用)
        redis.del(key);
        return true;
    }
    return false;
}

我的经验: 时间窗口场景下,我建议把过期时间设置得比业务要求稍微长一点。比如验证码要求5分钟有效,我就设成310秒。为什么?因为网络延迟、时钟偏差等因素,可能会导致缓存提前几秒过期。多出来的10秒,就是容错空间。

3.4 缓存策略选择:一张表说清楚

好了,三个场景讲完了。最后我总结一张表,方便你对照选择。

业务场景 推荐策略 过期时间 注意事项
读多写少 Cache-Aside 较长(如300秒) 防止缓存击穿,加互斥锁
热点数据 本地缓存+分布式缓存 较短(如10秒) 识别热点Key,限流降级
时间窗口 TTL+滑动窗口 略长于业务要求 防止缓存穿透,缓存空值

其实,缓存策略没有银弹。我见过很多团队,不管什么场景都用同一种策略,结果命中率惨不忍睹。你想想看,是不是这个道理?

一句话总结: 读多写少用Cache-Aside,热点数据加本地缓存,时间窗口用TTL+滑动窗口。别偷懒,针对场景选策略,缓存命中率自然就上去了。

缓存策略选择决策树 业务场景分析 读多写少场景 策略:Cache-Aside 过期时间:较长(300秒) 热点数据场景 策略:本地+分布式缓存 过期时间:较短(10秒) 时间窗口场景 策略:TTL+滑动窗口 过期时间:略长于业务要求 核心原则:针对场景选策略,不要一刀切

好了,这一章就到这里。记住,缓存策略选对了,命中率自然就高了。下一章我们聊聊缓存穿透的实战排查方法,到时候见。

可可精品资料库