技术交流28群

服务热线

135-6963-3175

微信服务号

redis缓存数据库一致性问题 更新时间 2022-3-5 浏览896次

背景

先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。

先说方案:更新的时候,先删除缓存,再更新数据库,然后再删除缓存。

为何先删除缓存而不是更新呢?比如:

1、对于比较复杂的缓存数据计算的场景,如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算。

2、A把缓存更新成功了,B操作需要读取缓存并读取到了A更新后的缓存,但是A更新数据库失败了,这时候B读取的缓存可能就是错误的(因为缓存和数据库操作不是一个原子操作)。

为何最后还要删除缓存呢?

因为可能在更新之前另一个请求线程又把旧的数据写入缓存了。

那么我们可以采取延迟双删方式。

redis.delKey(key);
db.updateData(data);
Thread.sleep(500);//因为可能别的请求线程刚读的旧数据还没写入成功或者
                  //更新数据库还没真正成功所以可延时等待
redis.delKey(key);


如果你用了mysql的读写分离架构怎么办?可以增加延时时间

降低吞吐量怎么?第二次删除可作为异步的。自己起一个线程,异步删除。

第二次删除失败怎么办?

方案:

老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出:

    失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

    命中:应用程序从cache中取数据,取到后返回。

    更新:先把数据存到数据库中,成功后,再让缓存失效

步骤:

(1)更新数据库数据;

(2)缓存因为种种问题删除失败

(3)将需要删除的key发送至消息队列

(4)自己消费消息,获得需要删除的key

(5)继续重试删除操作,直到成功

如果对一致性要求不是很高,直接在程序中另起一个线程通过消息队列实现,每隔一段时间去重试即可。但是会造成代码入侵,也可通过订阅数据库binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

步骤:

(1)更新数据库数据

(2)数据库会将操作信息写入binlog日志当中

(3)订阅程序提取出所需要的数据以及key

(4)另起一段非业务代码,获得该信息

(5)尝试删除缓存操作,发现删除失败

(6)将这些信息发送至消息队列

(7)重新从消息队列中获得该数据,重试操作。

订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。

待补充...