Redis与数据库数据一致性
普通情况下的解决方案(Cache Aside Pattern)
- 读的时候先读缓存,如果没有,再读数据库,最后刷新缓存
- 更新的时候,如果该数据会被再次频繁地访问到,我们就要把数据刷新回缓存,如果不怎么被访问,我们可以删除缓存,再更新数据库,这种事Lazy计算的一个思想。
28法则
20%的数据占用了 80%的访问量,高并发主要在20%的数据当中,另外的那些不经常被访问的数据,如果我们要更新的话,我们可以直接删除缓存,再更新数据库。等到要用的时候,我们才会将缓存重新存回到redis当中。因为经常刷新缓存是十分浪费redis资源的。
高并发下的缓存与数据库双写不一致的问题的解决方案与设计
1. 先修改数据库再删除缓存,但是万一缓存删除失败呢?
解决方案:先删除缓存,再更新数据库,即使数据库更新失败,缓存是空的,那么数据也不会不一致,此时读到没有缓存,则会读取数据库中的旧数据,然后更新到缓存当中。
2. A请求修改数据,先删除缓存再修改数据库,此时还没修改,B请求过来去读缓存发现缓存是空的,去查数据库,查到了修改前的旧数据,放回了缓存当中。之后数据库又被A修改了,缓存放的是旧数据,而数据库放的是新的数据,造成了不一致的问题。
解决方案:数据库与缓存的更新进行异步串行化
更新数据的时候,根据数据的唯一标识(hash路由),将操作路由之后,发送到JVM的一个内部队列当中,然后每个内存队列对接一个后台线程,后台线程再对缓存跟数据库进行处理。
A请求的操作有A1 (删除缓存) A2(更新数据库) ,B请求的操作有B1(读取数据) ,那么A1跟A2先后进去内存队列当中,然后执行,当A1 或者A2正在执行的时候,B1来了,则会先进去队列当中,排到了A2后面,此时B1在内部实际上是等待A1, A2完成后才执行,这样一来就能保证缓存与数据库数据一致性的问题。
关键是需要使用hash路由,使得对同一个数据的操作落到同一个消费者上面串行化。
不过,要注意的是,如果短时间内多个请求都要读取同一个数据,会有很多个相同的读操作压入队列当中,从而阻塞队列,所以我们在读请求压入队列之前先判断,队列的尾部如果也是读请求,而且是读同一个数据,则不压入队列,而是选择等待缓存被更新,一定时间过后(通常是你的系统最低响应时间)直接读缓存而不用经过消费者,相反如果是写操作,才压入队列。当然,如果有非常多的写操作积压在队列中,就会导致读操作的响应时间非常慢,所以这时候就要增加更多的机器,这样可以减少积压,通过hash路由后,每个队列都能分散到压力。