辛德蕾拉|面试官:Redis缓存了解吗?面对这11道题是否有很多问号?(11)


辛德蕾拉|面试官:Redis缓存了解吗?面对这11道题是否有很多问号?解决思路:先删除缓存 , 再更新数据库 。 如果数据库更新失败了 , 那么数据库中是旧数据 , 缓存中是空的 , 那么数据不会不一致 。 因为读的时候缓存没有 , 所以去读了数据库中的旧数据 , 然后更新到缓存中 。
比较复杂的数据不一致问题分析
数据发生了变更 , 先删除了缓存 , 然后要去修改数据库 , 此时还没修改 。 一个请求过来 , 去读缓存 , 发现缓存空了 , 去查询数据库 , 查到了修改前的旧数据 , 放到了缓存中 。 随后数据变更的程序完成了数据库的修改 。 完了 , 数据库和缓存中的数据不一样了...
为什么上亿流量高并发场景下 , 缓存会出现这个问题?
只有在对一个数据在并发的进行读写的时候 , 才可能会出现这种问题 。 其实如果说你的并发量很低的话 , 特别是读并发很低 , 每天访问量就 1 万次 , 那么很少的情况下 , 会出现刚才描述的那种不一致的场景 。 但是问题是 , 如果每天的是上亿的流量 , 每秒并发读是几万 , 每秒只要有数据更新的请求 , 就可能会出现上述的数据库+缓存不一致的情况 。
解决方案如下:
更新数据的时候 , 根据数据的唯一标识 , 将操作路由之后 , 发送到一个 jvm 内部队列中 。 读取数据的时候 , 如果发现数据不在缓存中 , 那么将重新读取数据+更新缓存的操作 , 根据唯一标识路由之后 , 也发送同一个jvm 内部队列中 。
一个队列对应一个工作线程 , 每个工作线程串行拿到对应的操作 , 然后一条一条的执行 。 这样的话一个数据变更的操作 , 先删除缓存 , 然后再去更新数据库 , 但是还没完成更新 。 此时如果一个读请求过来 , 没有读到缓存 , 那么可以先将缓存更新的请求发送到队列中 , 此时会在队列中积压 , 然后同步等待缓存更新完成 。
这里有一个优化点 , 一个队列中 , 其实多个更新缓存请求串在一起是没意义的 , 因此可以做过滤 , 如果发现队列中已经有一个更新缓存的请求了 , 那么就不用再放个更新请求操作进去了 , 直接等待前面的更新操作请求完成即可 。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后 , 才会去执行下一个操作 , 也就是缓存更新的操作 , 此时会从数据库中读取最新的值 , 然后写入缓存中 。
如果请求还在等待时间范围内 , 不断轮询发现可以取到值了 , 那么就直接返回;如果请求等待的时间超过一定时长 , 那么这一次直接从数据库中读取当前的旧值 。
高并发的场景下 , 该解决方案要注意的问题:
(1)读请求长时阻塞
由于读请求进行了非常轻度的异步化 , 所以一定要注意读超时的问题 , 每个读请求必须在超时时间范围内返回 。 该解决方案 , 最大的风险点在于说 , 可能数据更新很频繁 , 导致队列中积压了大量更新操作在里面 , 然后读请求会发生大量的超时 , 最后导致大量的请求直接走数据库 。 务必通过一些模拟真实的测试 , 看看更新数据的频率是怎样的 。
另外一点 , 因为一个队列中 , 可能会积压针对多个数据项的更新操作 , 因此需要根据自己的业务情况进行测试 , 可能需要部署多个服务 , 每个服务分摊一些数据的更新操作 。 如果一个内存队列里居然会挤压 100 个商品的库存修改操作 , 每隔库存修改操作要耗费 10ms 去完成 , 那么最后一个商品的读请求 , 可能等待 10 *100 = 1000ms = 1s 后 , 才能得到数据 , 这个时候就导致读请求的长时阻塞 。
一定要做根据实际业务系统的运行情况 , 去进行一些压力测试 , 和模拟线上环境 , 去看看最繁忙的时候 , 内存队列可能会挤压多少更新操作 , 可能会导致最后一个更新操作对应的读请求 , 会 hang 多少时间 , 如果读请求在 200ms 返回 , 如果你计算过后 , 哪怕是最繁忙的时候 , 积压 10 个更新操作 , 最多等待 200ms , 那还可以的 。


推荐阅读