MySQL和Redis如何保证数据一致?

重点结论:

在满足实时性的条件下,不存在 MySQL 和 Redis 一致的方案,只有最终一致性方案

不存在 MYSQL 和 Redis 实时保证一致的方案,只有 Mysql 和 Redis 最终一致的方案。

最终一致性方案主要有:

坏的方案: 1-先写 Mysql ,再写 Redis 2-先写 Redis,再写 Mysql 3-先删除 Redis,再写 Mysql

好的方案:

4-先删除 Redis,再写 Mysql,再删除 Redis 5-先写 Mysql,再写 Redis 6-先写 Mysql,通过 binlog ,异步更新 Redis

先写 Mysql ,再写 Redis(更新请求)

前提条件:是写/更新请求,对于读请求一律先去读 Redis,数据在 Redis 里面不存在的时候,再去 Mysql 读 DB。

存在的问题:

高并发场景:请求 A 先后发出两个操作,操作 A1 和 A21,请求 B 也先后发出两个操作 B1 和 B2

A1 操作:更新 Mysql 为 10 A2 操作:更新 Redis 为 10

B1 操作:更新 Mysql 为 11 B2 操作:更新 Redis 为 11

但是实际请求的执行次序是:

A1 B1 (Mysql 为 11) B2 A2 (Redis 为 10)

造成 Mysql 和 Redis 的数据的不一致。

造成问题的原因是:A 请求在写 Redis 的时候,卡了一下,如果写 Redis 的请求有延迟,并且在这个期间,其他请求比 A 请求先一步写了 Mysql 和 Redis,那么造成了数据的不一致。最主要的原因还是:A 写 Redis 的时候可能延迟,导致 B 请求在这请求之前写 Mysql,覆盖了 A 对 Mysql 的写,并且在 A 写 Redis 之前,B 已经写了 Redis

先写 Redis,再写 Mysql(更新请求)

存在的问题:

高并发场景:请求 A 先后发出两个操作,操作 A1 和 A21,请求 B 也先后发出两个操作 B1 和 B2

A1 操作:更新 Redis 为 10 A2 操作:更新 Mysql 为 10

B1 操作:更新 Redis 为 11 B2 操作:更新 Mysql 为 11

但是实际请求的执行次序是:

A1 B1 (Redis 为 11) B2 A2 (Mysql 为 10)

造成 Mysql 和 Redis 的数据的不一致。

造成问题的原因是:A 请求在写 Mysql 的时候,卡了一下,如果写 Mysql 的请求有延迟,并且在这个期间,其他请求比 A 请求先一步写了 Redis 和 Mysql ,那么造成了数据的不一致。最主要的原因还是:A 写 Redis 的时候可能延迟,导致 B 请求在这请求之前写 Redis,覆盖了 A 对 Redis 的写,并且在 A 写 Mysql 之前,B 已经写了 Mysql

先删除 Redis,再写 Mysql(A 是更新请求,B 是读请求,但是 B 的读请求会回写 Redis)

存在的问题:

高并发场景:请求 A 先后发出两个操作,操作 A1 和 A2,请求 B 也先后发出三个操作 B1 ,B2 和 B3

A1:删除缓存 10 A2:更新 Mysql 为 11

B1: 查询 Redis 缓存未命中 B2:查询 Mysql 为 10 B3:回写缓存。

但是实际请求的执行次序是:

A1 B1 B2 A2 B3

(此时 Mysql 为 11,但是 Redis 为 10)

造成 Mysql 和 Redis 的数据的不一致。

造成问题的原因是:

A 请求在删除了缓存 10 后,但是更新 MySql 为 11 的时候卡顿了一下,这个时候,导致其他读请求因为 A 的删除缓存而读出 Mysql 的未更新的数据,并把未更新的数据回写到 Redis. 最主要的原因是读请求读出未更新的数据并回写到 Redis 导致数据的不一致。

先删除 Redis,再写 Mysql,再删除 Redis(缓存双删)

存在的问题:

高并发场景:请求 A 先后发出三个操作,操作 A1 A2 A3,请求 B 也先后发出三个操作 B1 ,B2 和 B3

A1:删除缓存 10 A2:更新 Mysql 为 11 A1:删除缓存 10

B1: 查询 Redis 缓存未命中 B2:查询 Mysql 为 10 B3:回写缓存为 10。

但是实际请求的执行次序是:A1 B1 B2 A2 B3 A3

A 请求在删除了缓存 10 后,但是更新 MySql 为 11 的时候卡顿了一下,这个时候,导致其他读请求因为 A 的删除缓存而读出 Mysql 的未更新的数据,并把未更新的数据回写到 Redis. 最主要的原因是读请求读出未更新的数据并回写到 Redis 导致数据的不一致。但是只要 A 请求再删除一次缓存 10 就可以避免这个问题。如果写失败,那么增加重试,可以借助消息队列的重试机制,也可以自己整个表记录重试次数。

优化:请求的 A 的二删操作建议将删除请求异步入队列。

先写 Mysql,后删除 Redis(实时性下比较推荐的方案)

存在的问题:

高并发场景:请求 A 先后发出 2 个操作,操作 A1 A2 ,请求 B 也先后发出 4 个操作 B1 ,B2 , B3 ,B4

A1:更新 Mysql 为 11 A2:删除缓存 10

B1: 查询 Redis 缓存命中,返回 10 B2:查询 Redis 缓存未命中 B3:查询 Mysql 为 11。 B4:回写缓存为 11。

但是实际请求的执行次序是:A1 B1 A1 B2 B3 B4

A 请求先更新 Mysql,导致其他读请求在 A 请求更新 Mysql 后依旧读出缓存中的旧数据,返回旧数据,但是第二次再来读请求的时候,旧的缓存数据被删除,导致第二次未命中缓存,然后该读请求的回写操作使得新的数据被同步到 Redis 使得 Redis 和 Mysql 数据一致。

需要满足的条件:

  • 缓存刚好失效
  • 请求 B 从数据库查出 10,回写缓存的耗时比请求 A 写数据库,并且删除缓存的时间长。

对于第二个条件,更新 DB 肯定比查询耗时要长。

先写 Mysql,通过 Binlog+kafka 异步更新 Redis(最终一致性下比较推荐的方案)

高并发场景:请求 A 先后发出 1 个操作

A1:更新 Mysql 为 11

但是再这个过程中 Mysql 监听 binlog,然后把 binlog 请求更新进入消息队列,Redis 按照顺序消费消息队列的消息+重试机制来更新 Redis

这个是最终一致性方案,但是不能保证实时性。

Tags: Redis
Share: X (Twitter) Facebook LinkedIn