Redis与数据库的双写一致性问题如何保证


在分布式系统中,一致性(Consistency)、可用性(Availability)以及分区容忍性(Partition tolerance)这个三个要素最多只能同时保证两者,而分区容忍性是基本要求,所以分布式数据系统就要在一致性和可用性之间取一个平衡。对于大部分应用,并不需要强一致性,通常会采取牺牲一致性换取高可用性。牺牲一致性,只是不再要求关系型数据库中的强一致性,只要系统能达到最终一致性即可,同时尽量对用户透明。事实上,数据库与缓存没办法做到绝对一致性,所以追求绝对一致性的业务场景,不适合引入缓存。

三种经典缓存更新模式

1. Cache Aside Pattern
旁路缓存模式,是最常用的模式。

读:先从缓存中读数据,若命中,直接返回;若未命中,从数据库中读数据并放到缓存中。
写:先更新数据库再删除缓存。

问题1:为什么先更新数据库,再删除缓存?
两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的。那先更新数据库再操作缓存就不会存在不一致吗?不是的,毕竟不是原子操作,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。理论上确实会出现,但实际可能性很小,因为这要求读在写之前发生,并且更新缓存读要在写之后,而写操作比读慢得多。

问题2:为什么是删除缓存,不是更新缓存?
删除缓存替代更新缓存不会出现读脏问题,假设两个线程都发起写操作,但是可能由于网络等原因,后写的线程先更新了缓存,先写的线程后更新,那就存在脏数据了。

2. Read/Write Through Pattern
读写穿透,服务端把缓存作为主要数据存储,但缓存和数据库是同步更新的。

Read Through:跟Cache Aside很像,区别是如果没命中,Cache Aside是由应用来将数据从数据库加载到缓存,而Read Through是由缓存自己来加载。
Write Through:当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库。

3. Write Behind Caching Pattern
异步缓存写入,只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。这种方式下,缓存和数据库的一致性不强,而且可能会丢失。但是它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制就使用到这种模式。

如何保证一致性

通常推荐使用先更新数据库 + 再删除缓存这种方案来操作数据库和缓存,但是如果删除缓存失败就会导致数据库和缓存不一致。那如何保证删除一定成功呢?重试 or 异步重试。

1. 重试

保证删除缓存成功是解决一致性的关键,所以失败后可以重试,但是重试也存在一些问题:重试次数设为多少比较合理?重试会一直占用线程资源,所以这种方案不太好。

2. 异步重试

方案一:把重试请求放到消息队列中,由专门的应用来进行重试,直到成功。或者直接把删除缓存这个操作放到消息队列中,消息队列保证消息成功投递,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合重试场景)。
方案二:订阅数据库变更日志,再操作缓存。当一条数据发生改变时,MySQL就会产生一条binlog(变更日志),可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。订阅变更日志,目前也有比较成熟的开源中间件,例如阿里的canal,可以使用canal将binlog日志采集发送到消息队列里面。

3. 缓存延迟双删

对于Redis读写分离 + 主从复制延迟,以及先删除缓存,再操作数据库这两种情况导致的数据不一致性,可以采用缓存延时双删策略(先删除缓存,再更新数据库,休眠一会,再次删除缓存)来解决,但是延迟时间设置多久才合适?第一种情况延迟时间要大于主从复制的延迟时间;第二种情况延迟时间要大于第二个线程读取数据+写入缓存的时间,这在实际场景中不好评估。

总结

  • 引入缓存后,就要考虑缓存和数据库一致性的问题,建议,先更新数据库,再删除缓存
  • 在【先更新数据库,再删除缓存】的方案下,为了保证两步都能执行成功,需要配合【消息队列】或【订阅变更日志】的方案来做,其本质是通过重试的方式保证数据一致性。
  • 在【先更新数据库,再删除缓存】方案下,【读写分离 + 主从延迟】也会导致缓存和数据库不一致,解次问题的方案是【延迟双删】,凭借经验发送【延迟消息】到队列中,延迟删除缓存,同时要也要控制主从库延迟,尽可能降低不一致发生的概率。

参考:
https://blog.csdn.net/yang31842/article/details/120300777
https://blog.csdn.net/weiwenhou/article/details/117170724?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-117170724-blog-113881238.pc_relevant_blogantidownloadv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-117170724-blog-113881238.pc_relevant_blogantidownloadv1&utm_relevant_index=1