Redis 缓存处理策略 -- 基于车辆查询
Preword
文章讲解Redis做缓存时要采用什么样的处理策略解决一致性/穿透/雪崩/击穿等问题.
并且以车辆查询作为例子贯穿文章,在讲解完思路之后编写代码,最终通过编写CacheClient工具类来实现不同策略代码的复用.
环境搭建
- SQL脚本
引子
众所周知, Redis相较于SQL具有高效读写的特点, 在处理大量的请求时有更好的性能. 因此非常适合作为缓存, 来降低数据库的压力, 可以大大提高后端性能.
那么我们采取的策略也很简单, 当请求过来时, 我们先查Redis, 如果有则返回. 如果没有则查数据库, 然后将内容放入Redis做缓存. 最后返回数据即可.
不过这样自然会有一个问题, 如果你数据库里的数据要更新, 你的缓存还是旧数据, 怎么办? 这也是我们要处理的第一个问题.
Q1: Redis中的缓存如何确保和数据库中的一致性?
当然如果一致性要求不高的时候我们就不用管, redis自己有内存处理.
但有时我们需要数据库与缓存具备高一致性, 我们可以采取以下的措施:
第一: 我们可以给缓存加入一个有效期(TTL), 当到期之后必须去数据库拿新数据来作为新缓存.
第二: 我们可以主动更新. 即数据库更新时, 把缓存也更新. 但是每次数据库更新都要更新缓存吗? 如果数据库更新了100次都无人访问, 那缓存更新100次只是浪费资源, 没有意义. 那么我们绝对不是数据库更新的时候顺便更新缓存, 而是直接数据库更新时直接删除掉缓存, 当用户来访问时再从数据库中建立缓存. 这样就可以防止资源浪费.
OK, so remember! 直接删除缓存而不是更新缓存.
还有另一个问题, 既然已经知道是删除缓存, 那么到底是先更新数据库再删缓存, 还是先删缓存再更新数据库?
这两个好像乍一听没啥区别, 但其实两者都有可能发生错误
- 若先删缓存再改库, 发生异常为线程一删除缓存后, 线程二进来查库又写缓存, 然后线程一改数据库内容. 因为删缓存极快, 改库操作较慢, 此时中间插入线程二操作概率会比较大.
- 若先改库再删缓存, 则异常情况为缓存首先失效, 此时线程一查询数据需要读数据库, 而在查询完数据库后线程二又改数据库, 删除缓存, 然后线程一又将读到的数据库旧数据写入缓存, 此时会造成数据库数据为新, 而缓存数据为旧数据. 此时异常发生条件非常严苛, 需要缓存失效时发生读取操作, 读取操作过程中又发生数据库改数据, 发生概率非常小.
两种操作都会发生异常, 我们肯定选择发生异常概率低的情况. 因此先改库再删缓存.
总结一下, 我们可以采用主动更新 + 超时剔除,
- 删除缓存 or 更新缓存? -> 删除缓存, 等有查询了再更新, 若更新缓存但无查询, 则更新没有意义.
- 先操作数据库, 再删缓存 or 反 -> 两种操作都会发生异常, 我们肯定选择发生异常概率低的情况. 若先删缓存再改库, 发生异常为线程一删除缓存后, 线程二进来查库又写缓存, 然后线程一改数据库内容. 因为删缓存极快, 改库操作较慢, 此时中间插入线程二操作概率会比较大. 若先改库再删缓存, 则异常情况为缓存首先失效, 此时线程一查询数据需要读数据库, 而在查询完数据库后线程二又改数据库, 删除缓存, 然后线程一又将读到的数据库旧数据写入缓存, 此时会造成数据库数据为新, 而缓存数据为旧数据. 此时异常发生条件非常严苛, 需要缓存失效时发生读取操作, 读取操作过程中又发生数据库改数据, 发生概率非常小. 因此先改库再删缓存.
Q2: 缓存穿透
缓存穿透: 即用户访问的参数根本不存在, 缓存肯定没有, 请求肯定就直接打到数据库. 如果大量请求都这样, 数据库肯定直接gg了, 那怎么解决?
- 缓存置空 -> 如果redis中没有且数据库中也没有, 则将redis中添加空缓存, 并设置有效期. 这样来的请求拿不到数据也打不到数据库.
- 布隆过滤器 -> 添加布隆过滤器, 若无一定无, 若有不一定有.
布隆过滤器就不详细讲解了, 我们这里主要实现缓存置空.
Q3: 缓存雪崩
即大量的缓存同时过期/失效了, 这时大量的请求直接打到数据库, 肯定也会直接gg.
Q4: 缓存击穿
大量请求访问并且重建耗时较长的key过期以后, 大量请求访问到数据库, 给数据库造成巨大压力.
- 解决方案在一致性和高性能之间做抉择
- 互斥锁: key过期后一个线程进行恢复, 并且上锁, 此时其他线程只能等待, 导致性能变差. 但所有线程拿到的都是新数据, 一致性高.
- 逻辑过期: key不设置TTL, 而是在value中写入TTL. 线程对TTL进行校验, 如果过期则单开线程进行更新. 此时所有线程拿到旧数据返回, 一致性较差, 但不会像互斥锁那样等待, 因此效率高.
封装工具类
因为里面的泛型啊, 还有function参数马尿还没掌握的非常熟练, 先留坑吧. 难受了.