曾经做过的一个保费试算的项目,业务场景是根据用户基本信息+费率做计算,得到一个保费值,如果不了解这个业务场景的话,可以看做【用户输入】+【数据库数据】+【一定的计算】=【结果】;这些数据复杂而且多,使用频繁,但是每个产品的费率数据,几乎是不会改变的(新增修改删除都不会有):这些数据被大家放在Redis中,而且设置成永不失效;因为数据不会修改,那么数据库和缓存中的数据肯定是一样的。
现在做的项目中的一个功能,简单描述业务场景:系统从N个业务系统中抽取数据,并做加工整合,加工完成后更新到数据库中;这个场景也比较有特点,数据时效性要求很低(数据加工到数据库已经晚了一天),数据变化的时间固定且唯一(批处理每天固定时间进行,只有一个批处理线程在跑);于是大家采用了一个比较保守的策略,就是设置数据在Redis中的过期时间;等数据过期后,当查询的时候发现数据不在缓存中,再从数据库中查询出来后放入缓存。
现在想一想Redis中数据的更新策略有哪些(或者说缓存更新策略)?
- 给缓存设置过期时间,是偶认为比较好的方式(这里不讨论缓存雪崩的问题),这个【比较好】是在实现难度和数据一致性之间找到了一个平衡点;实现起来非常简单,但是可能在数据变化后,缓存失效前的这段时间,数据库和缓存之间的数据是不一致的(但最终会一致)。
先更新数据库,再更新缓存:有两个比较大的问题,一是多线程的时候,可能会造成数据库和缓存数据的不一致,而且这个不一致可能会是长期的。二是如果更新操作比较多的话,会频繁地更新缓存。
- A线程:set数据库key=1;
- B线程:set数据库key=100;
- B线程:set缓存key=100;
- A线程:set缓存key=1;(实际上,应该等于100,这样就造成了缓存和数据库中的数据不一致,并且如果数据不再更新,那么会使长期不一致)
先删缓存,再更新数据库:这个会出现问题,还是A/B两个线程,如果A删除了缓存之后,在更新数据库之前,B过来查询不到缓存,于是就查询了数据库(A还没更新),把数据写入缓存,这时候A才把数据库更新掉,这可能就会造成永久性的不一致。
先更新数据库,再删缓存:一样的道理,大家可以自己想一想这个过程。
有些人可能觉得这个概率很小,怎么可能有这么巧的事情,恰好都是A做到一半,B插进来把全部的事情做完,A再去做后一半的操作;但是概率小,也是可能发生的,不得不考虑。
还有很多复杂的实现方案,比如更新完数据库之后,把更新操作发给有序的消息队列(不过过程越复杂,不一致的时间会越长),由另外的线程从队列里面顺序执行更新或删除缓存的操作;
不过偶认为,架构越复杂也越容易出错,还不如根据实际的场景和团队的开发水平,合理选择缓存的使用,并给缓存设置合理的过期时间。