详解MySQL和Redis的数据一致性问题
5、、先删除缓存,再更新数据库
前言:
在数据读多写少的情况下作为缓存来使用,恐怕是Redis使用最普遍的场景了。当使用Redis作为缓存的时候,一般流程是这样的。
- 如果缓存在
Redis
中存在,即缓存命中,则直接返回数据
- 如果Redis中没有对应缓存,则需要直接查询数据库,然后存入Redis,最后把数据返回
通常情况下,我们会为某个缓存设置一个key值,并针对key值设置一个过期时间,如果被查询的数据对应的key过期了,则直接查询数据库,并将查询得到的数据存入Redis,然后重置过期时间,最后将数据返回,伪代码如下:
如果线程1和线程2同时进行更新操作,但是每个线程的执行顺序如上图所示,此时就会导致数据不一致,因此从这个角度上我们推荐直接使用删除缓存的方式。
此外,推荐使用「删除缓存」还有两点原因。
- 如果写数据库的场景比读数据场景多,采用这种方案就会导致缓存就被频繁写入,浪费性能;
- 如果缓存要经过一系列复杂的计算才能得到,那么每次写入数据库后,都再次计算写入的缓存无疑也是浪费性能的。
明确这个问题之后,摆在我们面前的就只有两个选择了:
- 先更新数据库,再删除缓存
- 先删除缓存,再更新数据库
2、先更新数据库,再删除缓存
这种方式可能存在以下两种异常情况
- 更新数据库失败,这时可以通过程序捕获异常,直接返回结果,不再继续删除缓存,所以不会出现数据不一致的问题
- 更新数据库成功,删除缓存失败。导致数据库是最新数据,缓存中的是旧数据,数据不一致
第2种情况应该怎么办呢?我们有两种方式:失败重试和异步更新。
3、失败重试
如果删除缓存失败,我们可以捕获这个异常,把需要删除的 key 发送到消息队列。自己创建一个消费者消费,尝试再次删除这个 key,直到删除成功为止。
这种方式有个缺点,首先会对业务代码造成入侵,其次引入了消息队列,增加了系统的不确定性。
4、异步更新缓存
因为更新数据库时会往 binlog
中写入日志,所以我们可以启动一个监听 binlog变化的服务(比如使用阿里的 canal开源组件),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。
总结
总之,对于删除缓存失败的情况,我们的做法是不断地重试删除操作,直到成功。无论是重试还是异步删除,都是最终一致性的思想。
5、、先删除缓存,再更新数据库
这种方式可能存在以下两种异常情况:
- 删除缓存失败,这时可以通过程序捕获异常,直接返回结果,不再继续更新数据库,所以不会出现数据不一致的问题
- 删除缓存成功,更新数据库失败。在多线程下可能会出现数据不一致的问题
这时,Redis
中存储的旧数据,数据库的值是新数据,导致数据不一致。这时我们可以采用延时双删的策略,即更新数据库数据之后,再删除一次缓存。
用伪代码表示就是:
public void update(String key, Object data) {
// 首先删除缓存
redisCache.delKey(key);
// 更新数据库
db.updateData(data);
// 休眠一段时间,时间依据数据的读取耗费的时间而定
Thread.sleep(500);
// 再次删除缓存
redisCache.delKey(key);
}
到此这篇关于详解详解详解MySQL和Redis的数据一致性问题的文章就介绍到这了,更多相关MySQL和Redis内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!