缓存的应用场景与读写模式

聊到缓存,很多人第一反应就是“快”。但缓存这东西,不是银弹。什么时候该用,什么时候不该用,用错了比不用还坑。我见过不少团队,上来就把所有数据往Redis里塞,结果内存爆了,业务反而更慢了。

我个人习惯是:先想清楚场景,再动手写代码。一个原则——数据热不热?热点数据才值得缓存。冷数据,你放进去就是浪费内存。

什么时候适合用缓存?

逻辑其实很直接。数据读得多、写得少,这种场景天然适合缓存。比如配置信息、用户头像URL、热门文章列表。

  • 读远多于写:比例在几十比一以上。典型的就是首页热点内容。
    我曾经接手过一个项目,后台配置几乎不变,但每次请求都去查数据库。加了一层本地缓存后,接口耗时从200ms降到了2ms。
  • 可容忍短暂不一致:缓存是最终一致的。如果你的业务要求强一致,比如银行转账,那最好别碰缓存,或者用很短的过期时间。
  • 计算开销大:比如复杂的SQL查询,或者需要调用多个外部服务聚合数据。我把它这种场景称为“慢到无法忍受的结果”,缓下来是真的香。

什么时候不适合?

反过来,数据写特别频繁,或者时刻变化的,缓存的意义就不大。你想想看,刚写进去就要失效,缓存一直在刷,白白浪费CPU和内存。

  • 高频写、低频读:比如用户频繁修改文档,但很少有人去看。
  • 强一致性要求:金融交易、库存扣减,这些场景必须实时落库。

读写模式:你总得选一种

说完了场景,我们再聊聊模式。这是我个人比较看重的部分,因为模式选错了,后面维护成本翻倍。

读模式

读模式相对简单,就是“先看缓存,再看数据库”的套路。但这里有一个经典问题——缓存穿透。

模式 核心步骤 注意点
Cache-Aside (旁路缓存) 应用先查缓存,命中直接返回;未命中则查数据库,写入缓存再返回。 这是开发中最常见、最容易理解的模式。大多数项目第一次上缓存都用这个。
Read-Through (穿透读) 应用只跟缓存交互,缓存负责去数据库查询并填充自己。 应用层更简洁,但缓存层要承担更多逻辑。依赖缓存中间件的支持。

避坑案例:我曾经遇到一个项目,大量请求查一个不存在的数据。缓存里没有,数据库也没有,每次请求都穿透到数据库。结果数据库被打挂了。解决这个问题,我们后来加了一层“空值缓存”。

// 伪代码示例:Cache-Aside + 空值缓存
Object value = cache.get("key");
if (value == null) {
    // 数据库查询
    value = database.find("key");
    if (value == null) {
        // 设置一个空值缓存,过期时间短一些
        cache.set("key", "EMPTY", 60);
    } else {
        cache.set("key", value, 3600);
    }
} else if ("EMPTY".equals(value)) {
    // 直接返回空,不去查数据库
    return null;
}
return value;

写模式

写模式就复杂多了。说白了,就是要不要保证数据库和缓存实时一致。我告诉你,大部分业务根本不需要。

模式 做法 典型场景
Write-Through (穿透写) 更新缓存的同时,同步更新数据库,要保证事务性。 关键数据写入,比如用户积分变更。
Write-Behind (异步写) 先更新缓存,再异步把数据写回数据库,合并写操作。 写频率高、数据精度要求不高的场景,如访问计数、点赞数。
Write-Around (绕开写) 直接更新数据库,同时删除缓存(或让缓存自然过期)。 读多写少的配置信息。我比较喜欢这种模式,简单干净,不容易出并发问题。

为什么我说大部分业务可以接受不一致?因为很多场景下,读操作并不要求最新。比如一个新闻列表,一秒钟前更新和一分钟前更新,对用户来说没区别。这时候用Write-Around就足够。

特别注意:在写模式中,并发场景下“先删缓存再更新数据库”容易出现脏数据。正确的顺序是“先更新数据库,再删缓存”——也就是“Cache Invalidation”策略。我亲眼见过一个线上事故,就是因为顺序写反,导致缓存里长期存着旧数据,用户看到的永远不是最新的。

一张图讲清场景与模式

我把上面的内容理了下,画了一张结构图。你看看就知道,场景决定模式,模式决定代码实现。

缓存应用场景与读写模式总览 缓存应用场景与读写模式总览 数据访问频率 读密集型 写密集型 Cache-Aside / Read-Through 空值缓存防穿透 Write-Through / Write-Behind Cache Invalidation 缓存失效与一致性 场景决定模式,模式决定实现。没有最好的策略,只有最适合的。

你从这张图能看到一个比较清晰的链路:数据访问频率决定了你是读密集型还是写密集型。读密集型,通常就是用旁路缓存或穿透读;写密集型,要考虑同步写还是异步写。最后,不管哪种模式,最终都要落到缓存失效与一致性上。

核心原则总结:没有最好的缓存策略,只有最适合的。读多写少用 Cache-Aside,写多读少用 Write-Behind,强一致性场景慎用缓存。脑子里的模型越清晰,写出来的代码就越稳。