1、问题描述
在实际开发中,我都需要使用redis做缓存来减轻数据的压力,提高查询效率。
如果数据的数据进行了修改,这时候就会出现数据的数据和缓存数据不一致问题。
2、解决方案
2.1、Redis数据删除还是更新
这里我们先要补充一点,当存储的数据发生变化,Redis的数据也要更新的时候,我们有两种方案,一种就是
直接更新Redis数据,调用set;还有一种是直接删除Redis数据,让应用在下次查询的时候重新写入。
这两种方案怎么选择呢?这里我们主要考虑更新缓存的代价。
更新缓存之前,是不是要经过其他表的查询、接口调用、计算才能得到最新的数据,而不是直接从数据库拿到
的值。如果是的话,建议直接删除缓存,这种方案更加简单,而且避免了数据库的数据和缓存不一致的情况。
在一般情况下,我们也推荐使用删除的方案。
所以,更新操作和删除操作,只要数据变化,都用删除。
2.2、先更新数据库还是删除缓存?
2.2.1、先更新数据数据,在删除缓存
正常情况:
更新数据库,成功。
删除缓存,成功。
异常情况:
1、更新数据库失败,程序捕获异常,不会走到下一步,所以数据不会出现不一致。
2、更新数据库成功,删除缓存失败。数据库是新数据,缓存是旧数据,发生了不一致的情况。
这种问题怎么解决呢?我们可以提供一个重试的机制。比如:如果删除缓存失败,我们捕获这个异常,把需要删除的 key,发送到消息队列。让后自己创建一个消费者消费,尝试再次删除这个key。这种方式有个缺点,会对业务代码造成入侵。所以我们又有了第二种方案(异步更新缓存)︰因为更新数据库时会往 binlog写入日志,所以我们可以通过一个服务来监听binlog的变化(比如阿里的canal) ,然后在客户端完成删除key的操作。如果删除失败的话,再发送到消息队列。
2.2.3、先删除缓存,在更新数据库
正常情况:
删除缓存,成功。
更新缓存,成功。
异常情况:
1、删除缓存,程序捕获异常,不会走到下一步,所以数据不会出现不一致。
2、删除缓存成功,更新数据库失败。因为以数据库的数据为准,所以不存在数据不一致的情况。
看起来好像没问题,但是如果有程序并发操作的情况下:
1)线程A需要更新数据,首先删除了Redis缓存。
2)线程B查询数据,发现缓存不存在,到数据库查询旧值,写入redis返回。
3)线程A更新了数据库。
如下图所示:
这个时候,数据库是新值,redis是旧值,出现了数据不一致的问题。这是由于线程并发造成的。
解决方案:
使用延时双删的策略,在写入数据之后,再删除一次缓存。
A线程:
- 删除缓存。
- 更新数据库。
- 休眠500ms(这个时间,依据读取数据的耗时而定)。
- 再次删除缓存。
伪代码
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
3.总结
保证缓存一致性的做法是:
- 数据更新时先删除缓存,在更新数据库。
- 使用延迟双删,解决多线程并发造成的ABA问题。