当前位置:首页 > 问答 > 正文

Redis缓存刷新又来了,重新起航怎么搞才靠谱呢,缓存更新那些事儿别忘了

Redis缓存刷新又来了,重新起航怎么搞才靠谱呢,缓存更新那些事儿别忘了,这事儿说起来简单,不就是把旧数据清掉换新的嘛,但真要在实际业务里把它玩转,不让用户抱怨、不出线上事故,里头门道可多了,咱们今天就不整那些高大上的术语,聊点实在的。

为啥要刷新缓存?最常见的就是数据变了,比如电商网站的商品价格调整了,后台数据库里的价格已经改过来了,但缓存里存的还是老价格,用户看到的就是错的,这时候就得让缓存里的数据“重新起航”,和数据库保持一致。

那怎么搞才靠谱呢?最傻但也最直接的办法就是删除缓存,你不是说缓存可能旧了吗?好,我直接把这件商品的缓存键(可以简单理解成数据的钥匙)删掉,下次再有用户来查询这个商品信息时,系统发现缓存里没有(这叫缓存未命中),就会老老实实地去数据库里把最新的价格查出来,一方面返回给用户,另一方面再把这个新结果塞回缓存里,这样后续的查询就快了。

这个方法,在《凤凰架构》这本书里提到过,它有个名字叫“Cache-Aside”模式,可以理解为缓存靠边站,让数据库先来,它的好处是简单,不容易出大错,但缺点也挺明显:在删除缓存后、新数据被加载进缓存前的那一刻,如果突然涌进来大量请求,所有这些请求都会直接砸到数据库上,数据库压力会瞬间增大,严重点可能就把数据库搞挂了,这就是常说的“缓存击穿”风险。

为了避免这种“群殴”数据库的情况,有人就想出了另一个招数:更新缓存,也就是在更新数据库的同时,不是简单地删除缓存,而是直接把新的数据值计算好,主动写到缓存里,这样缓存里始终是最新数据,用户查询永远命中缓存,数据库就没压力了。

听着挺美是吧?但这事儿更得小心,你得考虑并发问题,比如两个请求同时到来,A请求要把库存从10改成9,B请求要把库存从10改成8,如果它们更新数据库和缓存的顺序没控制好,可能就会出现数据库里库存是8,但缓存里却是9的脏数据情况,这就引出了另一个麻烦事:先更新数据库还是先更新缓存?无论哪个在先,在并发场景下都可能出问题,所以这种做法对代码逻辑的要求更高,搞不好就会埋下坑。

还有一种策略,在《Redis实战》这本书里被描述过,叫“Write-Through”(直写),这就像是给缓存和数据库请了个“双胞胎管家”,应用系统写数据的时候,只跟缓存说,缓存呢,责任心特别强,它一方面把数据存好,另一方面立刻同步去更新数据库,这样能保证缓存和数据库的强一致性,但代价就是每次写入操作都会涉及缓存和数据库两个地方,速度会比只写数据库慢一些,对缓存服务的稳定性要求也极高。

那有没有更折中省心的办法呢?有,这就是基于数据库的binlog日志同步,像阿里巴巴的Canal这类中间件就是干这个的,它的原理很巧妙:应用程序根本不用操心缓存更新,它只管写数据库,MySQL数据库自己不是有记录所有变更的binlog日志吗?Canal就伪装成MySQL的一个从库,偷偷地监听这些日志变化,一旦发现有针对某张表的数据更新,它立马解析出这个变更,然后悄悄地跑去把Redis里对应的缓存数据删除或更新掉,这样做的好处是,把缓存更新的逻辑从业务代码里完全剥离出来了,业务代码变得清爽,而且保证了数据最终的一致性,这套架构搭建和维护起来有点复杂度,算是用运维的复杂性换来了开发的简便性。

所以你看,说到头来,没有一种方法是完美的“银弹”。“删除缓存”简单但可能引发数据库压力;“更新缓存”要小心并发陷阱;“Write-Through”保证了强一致但性能有损耗;“binlog同步”很优雅但对架构有要求。

那到底该怎么选?得看你的业务场景,要是你对数据一致性要求不是那么实时(比如几分钟内看到旧价格也能接受),那用简单的“删除缓存”策略,然后给缓存设置一个合理的过期时间,让它自动失效,这是最省心的,要是你对数据一致性要求非常高(比如库存、秒杀),那就得考虑更复杂的方案,可能得结合多种策略,甚至引入锁机制来保证万无一失。

别忘了,缓存更新的目标就一个:在保证数据尽量正确的前提下,尽可能地提升系统性能,别让它成为瓶颈,每次“重新起航”前,都想清楚你的业务能接受多大的延迟一致性,你的团队能驾驭多复杂的技术方案,从小处着手,选择最适合自己的那条路,然后监控好缓存命中率和数据库压力,这才是最靠谱的搞法,缓存那些事儿,说到底就是个权衡的艺术。


Redis缓存刷新又来了,重新起航怎么搞才靠谱呢,缓存更新那些事儿别忘了