redis和数据库的一致性问题的解决方案
redis和数据库的一致性问题的解决方案
当前没有框架能够保证redis的数据和数据库的完全一致性,所以需要 我们自己在性能和一致性上作取舍。
使用到缓存的场景
这里讲到的是缓存和数据库的一致性问题。
当查询数据库数据的时候,才涉及到缓存的利用上,所以缓存的引入是为了让查询数据的时候提高效率;
而当发生增、删、改数据的时候,对于缓存来说是要让数据库和缓存发生一致性的改变,进而能让缓存在数据查询时候能继续起作用。
下图就是两种在redis缓存数据库内容时的使用。
对于缓存的一致性操作的选择
明确了缓存的使命------在查询数据库信息时,提前将数据进行缓存,既减少了数据库io,也提高了查询效率。
那么如一个节点的两个图:
缓存的生成,是在首次查询或者缓存过期时间到或者缓存被其他业务删除,进而需要在数据库查询完毕之后,将数据库内容同步到缓存中。
那当数据发生增删改时,涉及到两个方向的选择
1、是删除还是更新redis缓存
2、是先删除\更新缓存,再操作数据库;还是先操作数据库,再删除\更新缓存。
下面一一讨论
(1)数据发生变化,删除还是更新redis缓存
更新:数据发生增删改,把缓存的内容 重新用redis的set操作,赋予新值。
删除:在查询时,发现缓存已经不存在,去数据库查询之后,同步到redis缓存。
1、如何选择,可以使用排除方法,首先讨论更新。
(1)、redis更新是放在数据库操作的前。
线程1: redis更新 ---------------------------数据库更新
线程2:redis更新-------------------------数据库更新
(2)、redis更新是放在数据库操作的后。
线程1: 数据库更新 ---------------------------redis更新
线程2:数据库更新-------------------------redis更新
可以看到这样的场景,当线程1和2完成之后,此时缓存和数据库是不一致的。
2、写多读少:上面一直讨论缓存的目的是给读操作使用的,那么每修改一次就写入一次缓存,无疑大大的做无用功。
3、如果缓存是需要计算加工的数据:即查询到数据库值之后,缓存的值需要再做计算,那么每一次修改将很损耗性能。
总结:综合上面三点,删除要比更新的效率和避免数据不一致的效果更好。
(1)数据发生变化,先删除还是先操作数据库
1、 先更新数据库,再删除缓存
2、先删除缓存,再更新数据库
下面一一讨论:
(1)先更新数据库,再删除缓存
①如果更新数据库失败,那么程序捕获异常,之后不进行删除缓存操作。
②如果更新数据库成功,但是删除缓存失败。
针对②可以采用异步操作的办法,把删除失败的key给到队列当中,由线程池来执行失败重试。
或者可以利用canal 监听数据库修改 进而发生删除的操作。
(2)先删除缓存,再更新数据库
这种方式可能存在以下三种异常情况
1、删除缓存失败,这时可以通过程序捕获异常,直接返回结果,不再继续更新数据库,所以不会出现数据不一致的问题
2、删除缓存成功,更新数据库失败。此时不会发生数据不一致现象。
3、下图的场景,两个线程执行完成之后,会导致redis和数据库的不一致。
解决第三种情况可以使用 延时双删的策略,如下图:
这里讨论一下延时双删如何解决第三种异常情况,
1、线程2在查询数据库旧值之后,更新缓存,此时数据不一致,所以需要再次删除。
2、休眠时间,是为了在线程2写入缓存之后,线程1才发生删除缓存,不延时,可能导致线程2还没写入缓存,线程1就完成了删除缓存,那么最终的结果 还是数据不一致。
3、无论如何线程2并利用的是旧数据,这点没办法更改这点容错率还是支持的,毕竟要是发生在删除缓存前一秒还会利用缓存,此时数据还是最新数据,而删除缓存之后的一秒,才会到数据库去查最新数据,这个时间差的代价无论如何一定存在。
伪代码:
public void update(String key, Object data) {
// 首先删除缓存
redisCache.delKey(key);
// 更新数据库
db.updateData(data);
// 休眠一段时间,时间依据数据的读取耗费的时间而定
Thread.sleep(500);
// 再次删除缓存
redisCache.delKey(key);
总结
1、选择删除缓存 而不是更新
2、如果先修改数据库,再删除缓存,缓存删除成功的机制可以用异步的失败重试机制。
3、如果先删除缓存,再修改数据库,可以使用延时双删的机制。
文章摘录至https://blog.csdn.net/chanmufeng/article/details/122933214
更多推荐
所有评论(0)