解决缓存一致性问题

如何解决缓存一致性问题

引入缓存,我们的很大原因是为了让经常访问而不常修改的数据快速响应,提高系统性能。除此之外还有一些对及时性、数据一致性不高的场景。

使用缓存我们还有一个问题就是,缓存的数据一致性问题,即保证数据库的数据与我们缓存的数据一致,如何解决,我们常用的解决方式有以下两种。

1.双写模式

双写就是,写入数据库的时候,也更新缓存中的数据。如果细分析下来这两个步骤不同顺序执行也会不同效果。

数据一致性考虑主要两点:在不考虑并发问情况的异常问题,在并发情况下的不安全问题。

不考虑并发问情况出现异常

在不考虑并发问情况下我们考虑:

  1. 先更新缓存,后更新数据库
  2. 先更新数据库,后更新缓存

这都可能会出现业务问题。比如:更新了缓存,但更新数据库出错了,导致不一致。那如何解决?

解决的办法就是重试,详细在后面讲到。

我们还能思考一层就是更新缓存和数据库的操作容易出错吗?是否需要保证这一层的高可用?我们引入缓存是为了性能,强一致性的场景是否需要缓存呢?

并发问题情况

而双写在并发情况下会出现以下问题

解决缓存一致性问题_第1张图片

如果我们设置了缓存过期,即时出现上图的脏数据问题,数据不一致,等缓存过期,重新更新缓存,最终还是得到正确的数据,叫做最终一致性。这个过程时间不是立即的,所以适用于时效性不要求严格的场景

当然我们也可以双写的时候加锁,避免脏数据的问题。这里的锁保证数据一致。写写互斥,读和写也要互斥关系。

缓存利用率的角度来评估这个方案,也是不太推荐的。这是因为每次数据发生变更,都更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。

2.失效模式

指的是更新数据时,删除缓存。这样访问的时候先从数据库查再更新缓存。

不考虑并发问情况出现异常

  1. 先删除缓存,后更新数据库
  2. 先更新数据库,后删除缓存

这和双写模式一样,解决的办法还是重试,详细在后面讲到。

并发问题情况

1先删除缓存,后更新数据库

解决缓存一致性问题_第2张图片

出现不一致的情况,推荐先更新数据库后删除缓存因为数据库数据保证最新,那么缓存过了有效期也会最终一致。

2先更新数据库,后删除缓存

解决缓存一致性问题_第3张图片

这个问题也是一个脏数据问题,更新到第一个线程的数据,缓存数据不是最新的。这个问题如何解决呢?还是能够使用设置缓存过期,然后保证最终一致性。除此之外使用读写锁,把读和写锁住就能解决这个问题。

设置缓存的过期时间。缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的「热数据」,缓存利用率得以最大化。

不同顺序的解决方法都一样,如下:

  • 设置缓存过期,然后保证最终一致性。
  • 加锁[读写锁]

3.保证两步都成功

不考虑并发问情况出现异常,如何保证两部都成功?

使用重试,但重试需要思考下面问题

  • 立即重试很大概率「还会失败」
  • 「重试次数」设置多少才合理?
  • 重试会一直「占用」这个线程资源,无法服务其它客户端请求

更好的是异步重试,直接把缓存的操作放消息队列上通知操作,MQ保证消息可靠,异步释放当前线程。或者订阅数据库变更日志,再操作缓存

缓存数据一致性解决-Canal

使用canal从MySQL的binlog获取数据,更新到缓存。

解决缓存一致性问题_第4张图片

4.缓存数据一致性解决方案

无论是双写模式还是失效模式,都会导致缓存的不一致问题(脏数据)。即多个实例同时更新会出事。怎么办?

  • 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
  • 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
  • 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。强一致性可加锁。

如何保证强一致性

双写模式和失效模式都一样,加上锁就能保证强一致,比如加上读写锁,通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。由于写操作是排他锁,所以会损耗一定性能,写时读就会降低并发量。我们要考虑加上了锁之后代价是否大于加上缓存的性能提升

总结

  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可,这是一个保险方案。
  • 正常来说推荐失效模式使用先更新数据库。
  • 我们不应该过度设计,增加系统的复杂性。遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
  • 性能和一致性不能同时满足,性能与场景结合考虑,是否选择最终一致性,还是强一致性(加锁)。
  • 失败场景下要保证一致性,常见手段就是重试,同步重试会影响吞吐量,所以通常会采用异步重试的方案

文章

缓存和数据库一致性问题,看这篇就够了

你可能感兴趣的:(分布式,缓存,数据库,java,缓存一致性,redis)