缓存的设计以及相关问题

缓存的设计以及相关问题

受益与成本

受益

  • 加速读写
  • 降低后端负载

成本

  • 数据不一致:缓存层和数据层在时间窗口不一致
  • 代码维护成本
  • 运维成本:例如Redis cluster

缓存更新策略

各种更新策略对比

策略 一致性 维护成本
内存淘汰 (LRU/LFU/FIFO算法) 最差
超时剔除(expire) 较差
主动更新(开发控制缓存和数据一致性的业务)

LRU和LFU的区别如下:

算法缩写 算法名称 算法目的
LRU 最近最少使用算法(Least Recently Used) 淘汰最近最长时间未被使用的数据
LFU 最近最不常用算法(Least Frequently Used) 淘汰一定时期内被访问次数最少的数据

建议

  • 低一致性:最大内存和淘汰策略
  • 高一致性: 超时剔除和主动更新结合,最大内存和淘汰策略兜底。

缓存穿透问题

定义

缓存穿透是指查询时,这条数据在数据库和缓存都没有,但是还会一直查询数据库,对数据库的访问压力就会增加。

解决方案

缓存穿透的解决方案有以下两种:

  • 缓存空对象:缓存空对象的实现代码很简单,但是缓存空对象会带来比较大的问题,就是缓存中会存在很多空对象,占用内存的空间(即使设置了过期时间),浪费资源。
  • 布隆过滤器:布隆过滤器是一种基于概率的数据结构,主要用来判断某个元素是否在集合内,它具有运行速度快(时间效率),占用内存小的优点(空间效率),但是有一定的误识别率以及不支持删除的问题。它只能告诉你某个元素一定不在集合内或可能在集合内。

在这里插入图片描述

缓存击穿问题(热点key重建问题)

定义

缓存击穿是指一个key非常热点,在不停的扛着大并发,多个线程集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存直接请求数据库,瞬间对数据库的访问压力增加,甚至让应用崩溃。

原因

缓存击穿这里强调的是并发,造成缓存击穿的原因有以下两个:

  • 该数据很少有人查询 ,突然大并发的访问(冷门数据)。
  • 添加到了缓存且有设置数据失效的时间 ,在这条数据缓存刚好失效时大并发访问(热点数据)。

目标

  • 减少重建缓存的次数 数据尽可能一致 较少的潜在危险

解决方案

目前有下面两种解决方案:

  • 互斥锁:此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
  • 逻辑过期:包含两层意思:从缓存层面来看,确实没有设置过期时间(没有用expire)。从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

两种方案优缺点如下:

方案 优点 缺点
互斥锁 思路简单、保证一致性 代码复杂度增加、存在死锁的风险
逻辑过期 基本杜绝缓存击穿问题 不保证一致性、逻辑过期时间增加维护成本和内存成本

在这里插入图片描述
在这里插入图片描述

缓存雪崩问题

定义

缓存雪崩是指在某一个时间段内缓存集中过期失效,此刻无数的请求绕开缓存直接请求数据库,对数据库的访问压力增加,甚至压垮数据库。

原因

造成缓存雪崩的原因,有以下两种:

  • reids宕机
  • 大部分数据失效

解决方案

对于缓存雪崩的解决方案有以下两种:

  • 设置不同的过期时间,防止同一时间内大量的key失效。
  • 搭建高可用的集群,防止单机的Redis宕机。
  • 给缓存业务添加降级限流策略、给业务添加多级缓存
    在这里插入图片描述

无底洞问题

问题描述

Facebook的工作人员反应2010年已达到3000个memcached节点,储存数千G的缓存。他们发现一个问题–memcached的连接效率下降了,于是添加memcached节点,添加完之后,并没有好转。称为“无底洞”现象。

问题原因

键值数据库或者缓存系统,由于通常采用hash函数将key映射到对应的实例,造成key的分布与业务无关,但是由于数据量、访问量的需求,需要使用分布式后(无论是客户端一致性哈性、redis-cluster、codis),批量操作比如批量获取多个key(例如Redis的mget操作),通常需要从不同实例获取key值,相比于单机批量操作只涉及到一次网络操作,分布式批量操作会涉及到多次网络io。

问题关键点

  • 更多的机器!=更高的性能
  • 批量接口需求(mget,mset等)
  • 数据增长与水平扩展需求

IO的优化思路

  • 命令本身的效率:例如sql优化,命令优化
  • 网络次数:减少通信次数
  • 降低接入成本:长连/连接池,NIO等
  • IO访问合并:O(n)到O(1)过程:批量接口(mget)