缓存与数据库数据一致性问题
1、 问题起源
我们都知道Redis在业务系统与数据库中充当缓存,冗余一部分数据在缓存中,可以减少数据库处理请求的压力,提高响应的速度。但是,由于缓存和数据库中各有一份数据,如何保持缓存中的数据与数据库的数据一致性值得研究。
对于一致性来说,可分为强一致性和弱一致性。强一致性保证写入数据后立即读取保持一致,弱一致性则不保证立即可以读取写入后的值,而是尽可能的保证在经过一定时间后可以读取到,在弱一致性中应用最为广泛的模型则是最终一致性模型,即保证在一定时间之后写入和读取达到一致的状态。对于应用缓存的大部分场景来说,都是追求最终一致性。
2、 缓存的典型使用方式
旁路缓存是最常用的缓存方法,缓存位于一侧,业务系统直接与缓存和数据库交互。
工作原理:
- 业务系统首先检查缓存。
- 如果数据在缓存中找到,那么缓存命中,数据被读取并返回给客户端。
- 如果数据在缓存中找不到,缓存没命中。应用程序需要做一些额外的工作。通过查询数据库读取数据,将数据返回给客户端并将数据存储在缓存中,因此后续对相同数据的读取将在缓存进行。
适用场景:旁路缓存(Cache-aside)缓存策略非常通用,最适合读操作量大的工作负载。Memcached和Redis中广泛使用在这种情况。
3 、如何保证缓存与数据库数据一致性
前面提到的旁路缓存中写操作,是直接将数据写入数据库中,并删除缓存中的数据。在这个过程,另一线程同时读取数据,缓存删除后,又将旧数据更新到缓存中,此时缓存与数据库可能不一致。为了保证数据的一致性,需要做到许多保证:
-
给key值设定过期时间。
每次放入缓存的时候,设置一个过期时间。后续的写数据只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取,更新缓存。如果对于一致性要求不是很高的情况,可以采用这种方案。
-
延迟双删策略。
延迟双删策略使用在先删除缓存,后更新数据库的缓存方法中。为了避免更新数据库的时候,其他线程从数据库中读取到旧的数据更新到缓存中,需要在更新完数据库之后,再sleep一段时间,然后再次删除缓存。需要注意的是sleep的时间要对业务读写缓存的时间做出估计,sleep时间大于读写缓存的时间即可。
缺点:延迟时间难以确认,无法绝对保障数据的一致性。
-
采用消息队列(MQ).
更新数据库,成功后需要往消息队列发消息,等消息消费结束后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果。
缺点:引入消息队列这个中间件后,需要保证信息不丢失、不重复消费等。
-
订阅数据库变更日志。
通过后台任务使用更新时间戳或者版本作为对比获取数据库的增量数据更新至缓存中,这种方式在小规模数据的场景可以起到一定作用,但其扩展性、稳定性都有所欠缺。
方案选择:
- 考虑缓存和数据库一致性的问题,建议先更新数据库,删除缓存。
- 并发场景下“先更新数据库,删除缓存”无法保证一致性,使用延迟双删可以,但是延迟时间很难评估。
- 在先更新数据库,再删除缓存的方案下,为了保证两步都能执行成功,需要配合消息队列或订阅变更日志的方案来做,其本质是通过重试的方式保证数据一致性。