好睿思指南
霓虹主题四 · 更硬核的阅读氛围

缓存失效策略与一致性:让数据不“闹脾气”

发布时间:2025-12-15 15:43:51 阅读:17 次

你有没有遇到过这种情况:在电商App里刚下单成功,刷新一下页面,订单却不见了?等了几秒再刷,又突然冒出来。这背后很可能就是缓存数据库之间的“沟通”出了问题。

缓存为啥会“过期”

缓存就像你家楼下的便利店,离得近、拿东西快。数据库则是城郊的大仓库,货全但路远。为了提升访问速度,系统通常把常用数据先放进缓存。可一旦数据库里的数据变了,比如用户改了手机号,缓存里的旧数据就成了“过期食品”,得及时处理。

这时候就得靠缓存失效策略——告诉系统什么时候该扔掉旧缓存,下次请求时重新从数据库加载新数据。

常见的失效方式

最简单的办法是设置过期时间(TTL)。比如商品详情页缓存10分钟,10分钟后自动失效。这种方式简单粗暴,适合对数据实时性要求不高的场景,比如博客文章。

但有些场景等不了这么久。比如银行余额,必须尽可能准确。这时就会用到“主动失效”:每当数据库更新,立刻删除或更新对应的缓存。

// 伪代码示例:更新数据库后删除缓存
updateUserEmail(userId, newEmail);
delCache("user:profile:" + userId);

删了缓存,就万事大吉了?

别高兴太早。设想这个顺序:

  1. 线程A要更新数据库,先把缓存删了;
  2. 还没来得及改数据库,线程B进来查数据;
  3. B发现缓存没了,就去数据库读了个旧值,又塞回缓存;
  4. A终于改完数据库,结果缓存里又被塞了旧数据。

这就叫“缓存不一致”,而且特别难查,因为问题只在特定时序下出现。

怎么减少“不一致”的尴尬

一种做法是“先更新数据库,再删缓存”。这样即使中间有并发读,顶多是短暂读到旧缓存,等缓存删了,下一次就读到最新的了。虽然不能完全避免,但概率大大降低。

还有一种更狠的:双删机制。更新前先删一次缓存,更新完再删一次。虽然多了一次操作,但能有效应对上面那种“B插队读”的情况。

// 双删伪代码
delCache("user:data:" + userId);
updateInDB(userId, newData);
delCache("user:data:" + userId);

异步消息来帮忙

如果怕直接操作影响性能,可以用消息队列。数据库一更新,就往MQ发个通知,由下游消费者负责清理缓存。这样主流程更快,也实现了“最终一致”。

当然,消息可能延迟,甚至丢失。所以得配合重试机制和监控,确保每条“删缓存”的指令都落地。

缓存重建别忘了加锁

当缓存失效,多个请求同时发现“没数据”,都去查数据库,可能导致数据库瞬间压力飙升。这就是“缓存击穿”。

解决办法是在查数据库前加个本地锁或分布式锁,只让一个请求去重建缓存,其他等着用新的就行。

if (!getFromCache(key)) {
    if (tryLock()) {
        data = queryFromDB();
        setCache(key, data);
        unlock();
    } else {
        // 等一小会儿,重新查缓存
        sleep(50);
        return getFromCache(key);
    }
}

没有银弹,只有权衡

没有哪种策略能通吃所有场景。电商库存可能用延迟双删+消息队列,社交App的点赞数也许直接用缓存过期就够了。关键看你的业务能不能容忍短暂不一致,以及对性能的要求有多高。

有时候,用户根本不在乎数据是不是差了一两秒。与其花大力气追求强一致,不如把资源用在刀刃上。