# Redis:双写一致性 **Published by:** [icona](https://paragraph.com/@happycity/) **Published on:** 2021-12-03 **URL:** https://paragraph.com/@happycity/redis ## Content | 背景 Redis作为缓存可以极大的提升应用查询速度,对于一些实时性、精确性要求不高,读多写少的数据可以进行缓存,提高查询速度。 但引入缓存就必然存在数据同步的问题,因为数据缓存在Redis内存中,一旦数据库的数据发生变更,就会造成缓存与数据库数据不一致的问题。 | 解决方案 先更新缓存,再更新数据库 其实对于更新缓存这种操作都是不建议的,为什么呢?因为有些业务场景的缓存并不是直接从数据库中查出来就放入缓存的,可能还要经过一下计算,这中间存在不小的代价,不如直接删除缓存。 或许有人说了,我的缓存就是从数据库中查出来后就放入缓存的,不存在其他计算代价,那我也可以直接更新缓存啊。 这种做法也是不建议的,举个例子,用户对一个数据做了10个写操作,中间并没有任何读取操作,如果每次更新数据库的操作都要更新缓存,那其实这些更新缓存的操作是没有意义的,所以还是建议直接删除缓存,在需要读取的时候再缓存到内存中。 先更新数据库,再更新缓存 更新缓存的弊端上面已经说清楚了,这种方案都不考虑 先删除缓存,再更新数据库 先说结论,改方案也可能会出问题: 1、用户A更新了数据,先删除redis缓存,缓存删除成功 2、此时用户B对该数据进行查询,发现是空的,就直接去数据库查,此时查到的是修改前的脏数据 3、这时用户A更新数据的操作才执行完成,但用户B已经读取到脏数据,不能读到最新的值 解决这个问题最简答的方法就是延时双删策略。 但是在保证事务提交完以后再删除缓存还有一个问题,就是如果你使用的是MySQL的读写分离架构,那么主从同步之间也会有时间差 1、A发起更新操作,删除缓存 2、请求主库进行更新操作,主库与从库同步数据 3、B发起查询请求,发现redis为空,去从库中查询数据 4、此时从库数据同步未完成,拿到的数据是旧值 此时的解决方法就是如果是对Redis进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询 先更新数据库,再删除缓存 这种方式也可能存在问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。 此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑用语言描述如下: 请求 A 先对数据库进行更新操作。 在对 Redis 进行删除操作的时候发现报错,删除失败。 此时将Redis 的 key 作为消息体发送到消息队列中。 系统接收到消息队列发送的消息后再次对 Redis 进行删除操作。 但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。 | 总结 每种方案各有利弊,比如在第二种先删除缓存,后更新数据库这个方案我们最后讨论了要更新 Redis 的时候强制走主库查询就能解决问题,那么这样的操作会对业务代码进行大量的侵入,但是不需要增加的系统,不需要增加整体的服务的复杂度。 最后一种方案我们最后讨论了利用订阅 binlog 日志进行搭建独立系统操作 Redis,这样的缺点其实就是增加了系统复杂度。 其实每一次的选择都需要我们对于我们的业务进行评估来选择,没有一种技术是对于所有业务都通用的。没有最好的,只有最适合我们的。 ## Publication Information - [icona](https://paragraph.com/@happycity/): Publication homepage - [All Posts](https://paragraph.com/@happycity/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@happycity): Subscribe to updates - [Twitter](https://twitter.com/caishaowu1): Follow on Twitter