前言
当我们使用Redis做缓存时,数据不一致问题是绕不过的问题。如果我们没有很好的处理数据一致性问题,就有可能影响用户体验,最严重的会造成业务损失。数据一致性的场景和解决方式都有哪些呢?让我们一探究竟。
缓存和数据库的数据不一致发生原因
数据一致性总结就是2种情况:
- 缓存中有数据,这时缓存的数据和数据库的相同;
- 缓存中没有数据,这时数据库的值是最新值。
因此,不符合这2种情况的就是数据不一致了。而缓存又包括读写缓存和只读缓存。 发生数据不一致的情况有各自的区别。
读写缓存
对数据增删改操作时,需要在缓存中进行。同时呢还得根据写回策略,看是否要同步写回到数据库。
- 同步直写策略:写缓存时,并同步写数据库。缓存和数据库的数据一致。
- 异步写回策略:写缓存时不同步写数据库,而是等数据淘汰触发时,再将数据写回数据库。此情况下,若未触发数据淘汰就出现故障,数据库就没有最新数据,造成数据不一致的问题。
总结一下,采用读写缓存时,要保证数据一致性问题,就要采用同步直写策略。若数据一致性要求不是很高,可采用异步写回策略。
只读缓存
当要新增数据,就直接写到数据库;当数据删除或修改时,修改数据库的值,并将缓存的数据清除。
分析一下数据一致性的问题:
- 新增数据:数据直接写到数据库,不对缓存操作。此时不存在一致性的问题。
- 删除和修改数据:这种情况下,既要更新数据库,也要删除缓存。若两个操作保证不了原子性,就会出现数据不一致问题。
只读缓存删改数据的不一致问题
- 先删除缓存,再更新数据库:若缓存删除成功,数据库更新失败。后面访问就会缓存缺失,然后访问数据库就会查到旧值。(但数据库更新失败,这种情况此操作就失败了,不能叫旧值)
- 先更新数据库,再删除缓存:若更新数据库成功,删除缓存失败。后续访问的就是缓存中的旧值。
看上图很好理解这个数据不一致的产生过程:
- a旧值=1,客户端要更新a的值为2;
- 先更新数据库成功,此时数据库a = 2;
- 再删除缓存,但失败了。缓存还在。此时缓存中a = 1, 数据库中a = 2;
- 再去查询a的值。因缓存命中,返回a = 1,和数据库不一致。
数据不一致的解决方案
处理方式一:先删除缓存,再更新数据库。(不推荐)
发生数据不一致的场景:
- 线程1删除缓存中的a数据后,还没来得及更新数据库。
- 此时线程2读取数据a,会发现缓存缺失,然后会去读数据库。
线程2去数据库读取这个动作会带来2个问题:
- 线程2读取的a是旧值(因为线程1已经改了a的值,只是还没更新到数据库)
- 线程2在缓存缺失时读了数据库的旧值,这也就算了,它还会将这个旧值回写到缓存中。后续读取a可能都会读到旧值。
这种处理方式很容易造成数据不一致的问题,而且可能会影响后续多个查询操作。很多教程有提供延迟双删的解决方案(线程1更新完数据库后,先sleep一段时间,再进行一次缓存删除操作),这种sleep的方式无法准确预估其他多个线程开始和结束的时间。还是可能会产生数据不一致问题。个人建议别使用这种处理方式。
处理方式二:先更新数据库,再删除缓存。(推荐)
发生数据不一致的场景:
- 若线程1删除了数据库的值a,但还没来得及删除缓存。
- 此时线程2读取a数据,就会命中缓存中a的值,这就是读到了旧值。
但此情况若并发读a的线程不多,影响就不大。因为线程1马上就会将缓存中的a删除。后续读取a时就会出现缓存缺失,然后去数据库读a的最新值。对业务的影响较小。
总结
在Redis的缓存和数据库的数据不一致问题上,可分为读写缓存和只读缓存来分析。
读写缓存有同步写回策略和异步写回策略。采用同步写回能保证数据一致性,若数据一致性要求不是很高,可采用异步写回策略。
只读缓存有2种处理方式:
- 先删除缓存,再更新数据库(不推荐):并发请求多的时候,容易产生缓存和数据库值不一致的情况。
- 先更新数据库,再删除缓存(推荐):并发请求多的时候,会存在短暂的数据不一致问题。