Redis跟MySQL的双写问题怎么解决

写在前面

严格意义上任何非原子操作都不可能保证一致性,除非用阻塞读写实现强一致性,所以缓存架构我们追求的目标是最终一致性。缓存就是通过牺牲强一致性来提高性能的。

这是由CAP理论决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。

以下3 种缓存读写策略各有优劣,不存在最佳。

三种读写缓存策略Cache-Aside Pattern(旁路缓存模式)

Redis和MySQL双写问题,这样解决真香

Cache-Aside Pattern,即旁路缓存模式,它的提出是为了尽可能地解决缓存与数据库的数据不一致问题。

读 :从缓存读取数据,读到直接返回。如果读取不到的话,从数据库加载,写入缓存后,再返回响应。写:更新的时候,先更新数据库,然后再删除缓存。

Read-Through/Write-Through(读写穿透)

Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。Cache服务的职责是读取和写入DB数据,从而减轻应用程序的负担。

因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能,所以使用并不多。

写:先查 cache,cache 中不存在,直接更新 DB。cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache和DB)。

读:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。

Write Behind Pattern(异步缓存写入)

Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。

但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。

很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就挂掉了,反而会带来更大的灾难。

这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。

Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。

旁路缓存模式解析Cache Aside Pattern 的一些疑问

旁路缓存模式是我们平时中使用最多的。下面根据上面介绍的旁路缓存模式,我们可以有以下几个疑问。

为什么写操作是删除缓存,而不是更新缓存

答:线程A先发起一个写操作,第一步先更新数据库。线程B再发起一个写操作,第二步更新了数据库,由于网络等原因,线程B先更新了缓存,线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。

实际上要写操作的时候更新缓存也是可以的,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。

在写数据的过程中,为什么要先更新DB在删除缓存

答:比如说请求1 是写操作,要是先删除缓存A,请求2是读操作,先读缓存A,发现缓存被删除了(被请求1删除了),然后去读数据库,但是此时请求1还没来得及把数据及时更新,那么请求2读的就是旧数据,并且请求2还会把读到的旧数据放到缓存中,造成了数据的不一致。

其实要先删缓存,再更新数据库也是可以,如采用延时双删策略休眠1秒,再次淘汰缓存 这么做,可以将1秒内所造成的缓存脏数据,再次删除。不一定是1秒,看你业务决定的,不过不推荐这种做法,因为在这1秒内可能发生因素很多,它的不确定性太大。

在写数据的过程中,先更新DB,后删除cache就没有问题了么?

答: 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小。

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

(1)缓存刚好失效(2)请求A查询数据库,得一个旧值(3)请求B将新值写入数据库(4)请求B删除缓存(5)请求A将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据。

然而,发生这种情况的概率并不高

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。

可是,仔细想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

还有其他造成不一致的原因么?

答: 如果删除缓存过程中失败了就会造成不一致问题

如何解决?使用Canal去订阅数据库的binlog,获得需要操作的数据。另起一个程序,获得这个订阅程序传来的信息,进行删除缓存操作。

Cache Aside Pattern 的缺陷

缺陷1:首次请求数据一定不在 cache 的问题

解决办法:可以将热点数据提前放入cache 中。

缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。

数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。



双写是指将数据同时写入Redis和MySQL中,以保证数据的可靠性和实时性。但是,双写过程中如果出现数据不一致,就会对业务带来严重影响。下面,就让我们来看看如何解决Redis和MySQL的双写问题。
一、使用Redis缓存层
在使用双写的过程中,一定要加入Redis这样的缓存层。将数据写入Redis时,可以通过数据过期时间、数据缓存和数据刷新策略等方式来保证Redis和MySQL数据的一致性。
二、定时同步Mysql和Redis数据
我们可以定时将MySQL和Redis上的数据进行同步。通过一定的时间策略,定时清空Redis中的数据,然后重新从MySQL中载入数据,保证数据的正确性。
三、利用消息队列
使用消息队列,将需要进行双写的数据发送到队列中,再由队列进行数据的写入操作。这样做可以将数据的写操作异步化,提高了整个系统的并发性和吞吐量。
四、使用分布式事务
在使用Redis和MySQL双写的时候,最好使用分布式事务,确保数据的一致性。通过将Redis作为分布式事务的一部分,可以优化双写过程中的数据同步问题。
五、切分Redis和MySQL数据源
在实际开发过程中,Redis和MySQL的数据源可以进行切分。将不同类型的数据存储在不同的Redis和MySQL服务器上,可以减少数据同步消耗,提高系统的性能。
六、运行多个Redis实例
Redis单机的性能和容量有限,在高并发下可能会出现问题。因此,可以运行多个Redis实例来实现Redis分片。这样可以有效地满足高可用性和高并发性的要求。
七、使用到Redis集群
Redis集群拥有多节点,多副本,数据分片和自动容错等特点。使用Redis集群可以提高Redis的可用性和可扩展性,减少Redis的单点故障发生的概率。
综上所述,Redis和MySQL的双写问题可以采用多种方式解决。在实际开发过程中,应根据业务需求、数据量大小、系统复杂度等进行选择。通过合理的解决方案,可以保证数据的有效性和系统性能的优化。