如何保证Redis与数据库的数据一致性

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

首先分为两种场景

一. 针对读场景

1 A请求查询数据如果命中缓存那么直接取缓存数据返回即可。如果请求中不存在数据库中存在那么直接取数据库数据返回然后将数据同步到Redis中。不会存在数据不一致的情况。
2 在高并发的情况下A请求和B请求一起访问某条数据如果缓存中数据存在直接返回即可如果不存在直接取数据库数据返回即可。无论A请求B请求谁先谁后本质上没有对数据进行修改数据本身没变只是从缓存中取还是从数据库中取的问题因此不会存在数据不一致的情况。

因此单独的读场景是不会造成Redis与数据库缓存不一致的情况因此我们不用关心这种情况。

二. 针对写场景

1 如果该数据在缓存中不存在那么直接修改数据库中的数据即可不会存在数据不一致的情况。
2 如果该数据在缓存中和数据库中都存在那么就需要既修改缓存中的数据又修改数据库中的数据而且在高并发的场景下还存在修后关系这就会导致数据不一致的问题。

针对(2)的情况有两个疑问

1是删除缓存数据等待下次查询该数据时缓存中没有直接去数据库中查询同时添加到缓存中还是更新缓存呢
2更新缓存中的数据是先更新缓存还是先更新数据库呢

关于疑问(1)有两个方案

方案1删除缓存

优点实现简单不需要再更新数据库操作时在进行更新数据逻辑直接删除对应缓存的key即可。
缺点由于缓存被删除下次查询无法命中缓存需要在查询后将数据写入缓存增加查询逻辑。同时在高并发的情况下同一时间大量请求访问该条数据第一条查询请求还未完成写入缓存操作时这种情况大量查询请求都会打到数据库加大数据库压力。

方案2更新缓存

优点缓存命中率高只要缓存进行了更新后续的读请求就不会出现缓存未命中的情况。
缺点在某些业务场景下更新数据的成本较大并不是单纯将数据的数据查询出来丢到缓存中即可而是需要连接很多张表组装对应数据存入缓存中并且可能存在更新后该数据并不会被使用到的情况。

综合分析

在一般的业务中一般都采用缓存淘汰这种方案而非缓存更新。因为

  • 大多数情况下redis缓存中的数据并不是完全复制数据库中的数据而是将db中多张表的数据进行了重新计算筛选后更新到redis。如果在db某一张表的数据发生了变化的情况下需要同步重新计算redis中值的话更新成本过高。
  • 缓存更新后的新值无法保证一定会有读请求命中如果一直没有请求命中该部分冷数据其实是产生了一定的资源浪费计算成本+存储成本。
  • 相较于删除缓存方案来说仅有一次读请求cache miss的结果来说淘汰缓存策略的缺点完全可以容忍。

比如A表中的字段1分钟更改了100次如果采用更新缓存策略则需要计算100次哪怕1分钟内只有1次读请求如果采用淘汰缓存策略如果1分钟内只有1次请求则只需要计算1次即可开销大幅度降低。

关于疑问(2)有两个方案

方案1先更新缓存后更新数据库

正常情况

1A请求进行写操作先淘汰缓存再更新数据库
2B请求进行读操作由于A请求已将缓存淘汰B请求没有在redis中发现所需数据因此从数据库中读取数据并更新缓存到redis中

异常情况1

1A请求进行写操作先淘汰缓存
2B请求进行读操作由于A请求已将缓存淘汰B请求没有在redis中发现所需数据因此从数据库中读取数据并更新缓存到redis中。注意此时redis中被更新的依然是老数据A请求的数据库更新操作尚未完成
3A请求进行数据库更新操作。此时数据库中是新数据redis缓存中是老数据产生了数据不一致的问题。且该不一致会一直持续到缓存自然失效或者下次的更新操作

对于该种异常情况提供两种解决思路

1.异步更新缓存

1A请求进行写操作先淘汰缓存
2B请求进行读操作由于A请求已将缓存淘汰B请求没有在redis中发现所需数据因此从数据库中读取数据。注意此时不向redis写入新的缓存策略
3A请求通过订阅数据库binlog对redis缓存数据进行异步更新

该方案虽然解决了数据不一致的问题但是在数据库更新操作完成前所有的读请求都会直接打到数据库上具有比较大的风险。

2.延时双删

1A请求进行写操作先淘汰缓存
2B请求进行读操作由于A请求已将缓存淘汰B请求没有在redis中发现所需数据因此从数据库中读取数据并更新缓存到redis中。注意此时redis中被更新的依然是老数据A请求的数据库更新操作尚未完成。假设该步骤耗时N秒
3A请求进行数据库更新操作。
4由于此时redis中写入了老数据因此A请求在休眠M秒后M略大于N再次对redis进行淘汰缓存操作

该方案虽然解决了数据不一致的问题但是由于请求A在更新完数据库之后需要休眠M秒再次淘汰缓存一定程度上影响了数据更新操作的吞吐量。可以尝试将等待M秒更新redis的操作放到另一个单独的线程比如消息队列 + 重试机制。可以有效缓解吞吐量降低的问题。

异常情况2

1A请求进行读操作此时redis缓存中没有数据因此直接从数据库中读取数据
2B请求进行写操作先淘汰缓存再更新数据库
3A请求进行将从数据库中读到的老数据更新到redis。此时产生数据不一致问题。

该种异常情况发生概率极低一般读操作比写操作要快。如有担心可以采用上述的延时删除策略

方案2: 先更新数据库后更新缓存

正常情况

1A请求进行写操作先更新数据库再淘汰缓存
2B请求进行读操作由于A请求已将缓存淘汰B请求没有在redis中发现所需数据因此从数据库中读取数据并更新缓存到redis中

异常情况1

1A请求进行写操作先更新数据库
2B请求进行读操作由于A请求尚未淘汰缓存B请求在redis中发现所需数据因此直接返回老数据产生了数据不一致的问题
3A请求淘汰缓存。
4C请求进行读操作发现redis中没有数据因此从数据库中读取新数据并更新至缓存。数据不一致的问题解决。

该场景下数据最终一致只是在高并发下产生了一小段时间的数据不一致。

异常情况2

1A请求进行读操作此时redis缓存中没有数据因此直接从数据库中读取数据
2B请求进行写操作更新数据库并将redis中缓存进行了淘汰虽然此时redis中并没有任何的缓存
3A请求将从数据库中读到的老数据更新到redis。此时产生数据不一致问题。

该种异常情况发生概率极低一般读操作比写操作要快。如有担心可以采用上述的延时删除策略。

总结

方案1先淘汰缓存后更新数据库的策略有可能导致长时间的数据不一致问题可以通过延时双删 or 异步更新缓存策略进行解决。
方案2先更新数据库后更新缓存有可能导致极短时间内的数据不一致但是数据最终是一致的。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis数据库