0%

缓存数据一致性问题解决方案

问题:如何保证本地缓存和redis缓存数据的一致性

这个问题日常工作中应当比较常遇到,通用解决方案:

方案一:

​ 业务逻辑确定本地缓存有效期。就是给本地缓存添加一个相对短的过期时间。 适用与一些非重点数据,例如用户头像等可以接受短时间数据不一致情况。

方案二:

采用Redis的发布/订阅机制(或者Rocketmq等) 分布式集群的所有阶段订阅删除。

方案三:

通过调用接口方式删除。 实际操作是在数据变更发生是,触发接口调用所有容器删除本地缓存接口。 这样做的情况 需要在每个容器启动时注册当前容器ip和端口信息。 方案优点: 响应快,稳定性好。 缺点: 实现复杂度高。

方案四:

通过Zookeeper中间件方式: 实现单个容器操作数据变更时 其他容器接收到数据变更消息删除本地缓存重新拉取。

问题: 如何保证redis缓存和mysql数据的一致性

网上资料的解决方案:

选择合适的缓存更新策略

  1. 删除缓存而不是更新缓存

    当一个线程对缓存的key进行写操作的时候,如果其他线程进来读数据库的时候,读到的就是脏数据。 产生了数据不一致问题。

    相比较而言: 删除缓存的速度,比更新缓存的速度快很多。 所以用时相对小很多。 读脏数据的概率也小很多。

  2. 先更新数据后删除缓存

    更新数据耗时是在删除缓存的百倍以上 。 如果先删缓存,在数据更新成功之前,其他线程获取数据会把脏数据写入到缓存。

    目前最流行的缓存读写策略就是采用先更新数据库,再删缓存的方式。

缓存不一致的处理

如果不是并发特别高,对缓存依赖性很强,其实一定程度的不一致是可以接受的。

缓存和数据库数据不一致常见的原因:

  • 缓存key删除失败
  • 并发导致写入脏数据

解决方案:

通过消息队列保证key被删除。 把要删除或者删除失败的key写入消息队列,利用消息队列的重试机制,重试删除对应key。

缺点: 代码侵入

数据库订阅+消息队列保证key被删除 可以用一个服务监听数据库的binlog 获取需要操作的数据。 然后用一个公共服务获取订阅程序传来的信息,进行缓存删除操作。 这种方式降低了对业务代码侵入,但是复杂度提升了。 适合基建完善的大厂。

延时双删防止脏数据 在缓存不存在的情况,写入了脏数据,这种情况先删缓存,再更新数据库的缓存更新策略下发生比较多。 解决方案: 延时双删

操作: 在第一次删除缓存后过了一段时间再次删除缓存。 这种方式的延时时间需要仔细斟酌。

设置缓存过期时间兜底

这个是朴素但是有用的方案,给缓存设置一个过期时间,即使发生数据不一致问题,它也不会永远不一致下去。

**下面是我自己整理的解决方案: **

这个问题遇到的应当是最多的。

常规解决方案:

方案一:

先删除缓存数据,然后执行mysql的数据更新。 这种方案 如果缓存没有有效期的情况 可能存在脏读。 就是缓存了 更新前的数据。

解决方案:mysql 数据更新后 再次删除缓存。 也就是二次删除。 这样可以保证redis的数据 跟db数据一致。 这里有个前提,redis数据读取的需要是主库数据,如果是从库数据,可能存在主从同步延迟,导致重新读取的数据还是旧数据情况。

方案二:

异步删除缓存。 先更新db数据,然后通过binlog或者rocketmq等方式 消费消息时进行缓存key的删除。 这样可以保证缓存数据一定被成功删除。 例如rocketmq消息消费失败,就是删除失败情况 可以多次尝试。 缺点: 对业务侵入大。

方案三:

设置缓存过期时间,其实这个应当是基本要求,所有缓存都要设置过期时间。 但是设置过期时间后会引发另外一个 redis穿透问题。 需要对这种情况进行特殊处理。

Welcome to my other publishing channels