缓存不是万能的,用不好反而添乱
你有没有遇到过这种情况:在电商App里刚把商品加入购物车,刷新一下,购物车却空了?或者后台明明更新了用户信息,前端页面却还显示旧数据?问题很可能出在缓存上。缓存能提升性能,但一旦失效策略没设计好,数据一致性就成了大麻烦。
缓存的本质是“牺牲一点实时性,换速度”。可如果这“一点”变成了“一大截”,用户体验就崩了。所以,怎么让缓存该失效的时候及时失效,不该变的时候稳如老狗,是个技术活。
常见的缓存失效方式
最简单的做法就是设置一个固定过期时间,比如10分钟。到期后缓存自动清除,下次请求重新从数据库加载。这种叫TTL(Time To Live)策略,实现简单,适合对数据实时性要求不高的场景,比如文章浏览量、天气预报。
但有些数据不能等10分钟才更新。比如库存。你总不能让用户抢购时看到“有货”,点进去才发现“已售罄”吧?这时候就得主动干预——写操作发生时,立刻让相关缓存失效。这就是“主动失效”策略。
比如用户修改了个人资料,系统在更新数据库的同时,直接删除对应的缓存记录。下次读取时发现缓存没了,自然会回源查新数据。这种方式响应快,但要注意别误删了其他关联缓存。
双删机制:再保险一点
有时候,写操作和读操作几乎同时发生,容易出现“写完还没删,读请求先来了”的情况。这时候可以试试“先删一次,等写完成后再删一次”的双删策略。
// 伪代码示例:双删 + 延迟等待
deleteCache("user:123");
updateDatabase(user);
Thread.sleep(100); // 短暂延迟,让可能的并发读走老数据
deleteCache("user:123");虽然sleep听起来有点糙,但在高并发场景下,这点延迟换来的是更高的数据一致性,值得权衡。
缓存与数据库的顺序问题
先更新数据库还是先删缓存?这是个经典问题。如果你先删缓存再更新数据库,万一更新失败了,缓存已经没了,接下来所有读请求都会打到数据库,可能引发雪崩。
反过来,先更新数据库再删缓存,虽然更安全,但如果删缓存失败,就会留下脏数据。这时候可以引入消息队列,把“删缓存”作为一个异步任务发出去,失败了还能重试。
updateDatabase(user);
sendMessageToQueue("delete_cache", "user:123");哪怕缓存删除暂时失败,也能通过后续重试机制补救,比直接丢弃要可靠得多。
读写穿透:别让缓存拖后腿
缓存击穿是指某个热点key过期瞬间,大量请求同时涌入,直接压垮数据库。可以用互斥锁来控制:只有一个线程去数据库加载数据,其他线程等着用结果。
缓存穿透则是查询根本不存在的数据,每次都不命中,请求直奔数据库。可以布隆过滤器提前拦截,或者对空结果也缓存几分钟,避免反复查询。
这些细节处理好了,缓存才真正成了系统的“加速器”,而不是“定时炸弹”。