缓存失效场景&优化


缓存失效场景&优化

缓存穿透

场景

查询根本不存在的数据,使请求直达存储层。被恶意攻击者利用,导致负载过高宕机。

优化

  • 缓存空对象

    存储层查不到返回空值,将空值也存入缓存中,这样请求就可以打到缓存层。

    缺点1:如果是攻击,将占用大量缓存空间。

    解决:将空值键设置较短的过期时间。

    缺点2:如果缓存层已缓存了空值,此时存储层对应的键已更新了值,会造成缓存层与存储层数据不一致。

    解决:通过消息系统等方式清除掉缓存层的值。

  • 布隆过滤器

    将所有键存到缓存层之前的一个布隆过滤器中,请求打过来,如果布隆过滤其中没有对应key,就直接返回空,避免数据打到缓存层和存储层。

    举例:比如一个推荐系统有1亿用户,每个小时后台根据用户的历史行为计算出推荐数据放入缓存层。

    但新用户没有推荐数据,因此新用户的推荐数据请求会打到存储层。加入布隆过滤器,将1亿用户id放入,新用户di传过来,布隆过滤器中没有,就可以返回空值。

对比

优化方案 适用场景 维护成本
缓存空值 数据命中不高/数据频繁变化实时性高 代码维护简单/需要过多缓存空间/数据不一致
布隆过滤器 数据命中不高/数据相对固定实时性低 代码维护复杂/缓存空间占用少

缓存击穿/热点key重建

场景

一份热点数据访问量非常大,在其失效瞬间,大量请求涌向存储层,导致服务崩溃。

优化

  • 加互斥锁

    对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待。

    该线程访问过后,缓存中的数据将被重建,到时其他线程就可以直接从缓存取值。

  • 永不过期

    物理上不设置过期时间。

    给value设置逻辑过期时间,发现逻辑时间过期时,单独线程重建缓存。

    下次再访问时,value就是新值。

对比

优化方案 优点 缺点
简单分布式锁 思路简单/保证一致性 代码复杂度大/存在死锁风险/存在线程池阻塞风险
永不过期 基本杜绝热点key问题 不保证一致性/逻辑过期时间增加代码维护和内存成本

缓存雪崩

场景

某种情况下整个缓存层突然失效,请求全部直达存储层。

优化

  • 保证高可用,Redis Sentinel&Redis Cluster

  • 避免键同时过期,键的过期时间设置为随机数

  • 构建多级缓存,如增加本地缓存

  • 限流&降级服务:一个服务无法提供后提供降级服务

    p.s. Hystrix隔离工具

缓存无底洞

场景

缓存节点越扩越多,缓存性能反而下降。

原因:key通过hash算法分布在不同的节点上,如果一次性 mget N个key,N个key会分布在M个节点上,节点越多,M可能越大,网络延时开销就越大。而只在1个节点上 mget N个key,只有一次网络开销。

优化

前提:假设要一次性获取n个key的值。在集群上,直接mget keys会报错,因为key分布在不同的slot上。

只有key都在同一个slot上才可以。

  • 串行命令,执行n次get key

    开销:n次网络时间 + n次命令时间

  • 串行IO,Smart客户端会保存slot和节点的对应关系,这样可以得到每个节点key的字列表,再通过CRC16算法算出slot。

    开销:node次网络时间 + n次命令时间

  • 并行IO,因节点不同,将上面的串行改为并行。

    开销:最慢的那次网络时间 + n次命令时间

  • hashtag,存的时候将所有key通过hashtag强制放到同一个槽上,获取的时候就方便获取

    开销:1次网络时间 + n次命令时间

    hashtag原理:键中内容含{},计算slot时是以slot中的内容为准

    mget user:{10086}:info user:{10086}:video

对比

优化方案 优点 缺点 网络IO
串行命令 编程简单/key少可满足性能要求 大量key请求延迟严重 O(keys)
串行IO 编程简单/节点少可满足性能要求 大量node请求延迟严重 O(nodes)
并行IO 延迟取决于最慢节点 编程复杂/多线程定位问题较难 O(max_slow(nodes))
hash_tag 性能最高 业务维护成本高/易出现数据倾斜 O(1)

参考

《 Redis开发与运维 》


文章作者: Wendell
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wendell !
评论
  目录