现在的web架构一般都用redis作为缓存层来减轻数据库的压力,数据在此架构下的读取问题,一般都是先判断redis缓存是否有数据,如果有,直接返回,否则读取数据库的数据,写入redis,返回数据,这是大致的读取流程。
那么为什么会出现redis和数据库不一致的问题呢?举个例子,如果你的缓存里现在缓存了用户的年龄的数据,每次用户访问的时候,如果缓存里有这个数据,直接返回,那么如果用户更新了自己的年龄,你的操作步骤是什么?
- 先更新(删除)redis缓存,再更新数据库
- 先更新数据库,再更新(删除)用户缓存
上面两种不同的操作顺序,不论哪种方法,理论上如果两个步骤都成功了,那没问题,如果一个更新成功了,另一个更新失败了,那么就会导致数据库和缓存不一致的问题,这就要求操作必须具备原子性,不论谁先执行,只要一个步骤出现错误,就因该回滚,谁都不能成功。
更新缓存or删除缓存
这里简单说下,更新缓存可能要消耗更多的cpu资源,所以建议直接删除缓存。
举个例子:缓存money=100,数据库money=100,现在要求money=99
下面讨论并发下两种策略可能带来的问题
先删除缓存成功,后更新数据库失败
- 线程A删除缓存,更新数据库money中,还没更新完
- 线程B过来发现缓存没有了,去数据库读取,读到的money=100
- 线程B,将读到的money=100写入缓存,现在缓存中的money=100
- 线程A将数据库的money更新完成,money=99
- 最终redis缓存的数据是100,数据库是99
- 适合原子性要求高的,不适合高并发
造成这个问题的原因就是线程A还没干完事情,线程B就插进来了。解决这个问题的办法,就是串行化,让操作顺序执行,不能交替进行。顺序应该是删除、更新、读取。
先更新数据库,再删除缓存
- 线程A过来查数据,缓存没有数据,读取数据库money=100
- 线程B更新数据库money=99,并删除缓存
- 线程A把读到的money=100,写到缓存里
- 最终redis缓存是100,数据库是99
- 适合高并发,不适合原子性高的
造成这个问题的原因就是第二步骤,其实缓存并不存在,删除是没有用的,应该等到A把缓存写入之后,再尝试删除缓存,这时候建议再第二步骤一直尝试删除缓存,知道删除成功。