1 收益与成本
1.1 收益
- 加速读写,优化用户体验;
- 降低后端负载。
1.2 成本
- 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
- 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。
- 运维成本。
2 更新策略
2.1 LRU/LFU/FIFO算法剔除
2.1.1 使用场景
剔除算法
通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy
这个配置作为内存最大值后对于数据的剔除策略。
2.1.2 一致性
要清理哪些数据是由具体算法决定,开发人员只能决定使用哪种算法,所以数据的一致性是最差的
。
2.1.3 维护成本
算法不需要开发人员自己来实现,通常只需要配置最大maxmemory
和对应的策略
即可。开发人员只需要知道每种算法的含义,选择适合自己的算法即可。
2.2 超时剔除
2.2.1 使用场景
一般用于能够容忍一段时间不一致的场景。通过对数据进行有限时间的缓存,等到过期了,再通过一定的策略进行更新,达到快速响应的目的。而对于一些交易类的场景,此种更新策略就显得不合适。
2.2.2 一致性
如上所述,存在一定时间的不一致性。
2.2.3 维护成本
维护成本不是很高,只需设置expire
过期时间即可,当然前提是应用方允许这段时间可能发生的数据不一致。
2.3 主动更新
2.3.1 使用场景
一致性要求比较高的场景。一般通过消息系统或者canal方式更新缓存。
2.3.2 一致性
一致性最高,但如果主动更新发生了问题,那么这条数据很可能很长时间不会更新,所以建议结合超时剔除
一起使用效果会更好。
2.3.3 维护成本
维护成本会比较高,开发者需要自己来完成更新,并保证更新操作的正确性。
2.4 三种常见更新策略的对比
策略 | 一致性 | 维护成本 |
---|---|---|
LRU/LFU/FIFO算法剔除 | 最差 | 低 |
超时剔除 | 较差 | 较低 |
主动更新 | 强 | 高 |
2.5 最佳实践
- 低一致性业务建议配置最大内存和淘汰策略的方式使用;
- 高一致性业务可以结合使用超时剔除和主动更新.
3 粒度控制
缓存全部数据和部分数据对比:
数据类型 | 通用性 | 空间占用(内存占用+网络带宽) | 代码维护 |
---|---|---|---|
全部数据 | 高 | 大 | 简单 |
部分数据 | 低 | 小 | 较为复杂 |
缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,网络带宽的浪费,代码通用性较差等情况,需要综合数据
通用性
、空间占用比
、代码维护性
三点进行取舍。
4 穿透优化
4.1 定义
缓存穿透
是指查询一个根本不存在的数据,结果将压力全部给到关系数据库当中,当存在大量这种数据时,导致后端压力倍增,甚至宕机。
4.2 解决方案
4.2.1 缓存空对象
4.2.2 布隆过滤器
查询布隆过滤器说数据
存在
,并不一定
证明数据库中存在
这个数据,但是查询到数据不存在
,数据库中一定就不存在
这个数据
4.2.3 方案对比
方案 | 适用场景 | 维护成本 |
---|---|---|
缓存空对象 | 数据命中不高;数据频率变化实时性高 | 代码维护简单;需要过多的缓存空间;数据不一致 |
布隆过滤器 | 数据命中不高;数据相对固定实时性低 | 代码维护复杂;缓存空间占用少 |
5 击穿优化
5.1 定义
指对于热key,如果在某个时间缓存失效,导致这个时间段访问突然增高,导致全部压力集中至后端存储层,甚至宕机。
5.2 解决方案
5.2.1 永不过期
将不变的少量数据设置永不过期,就不会出现击穿的风险。但是实际生产中,热key都是变化的,只是时间长短而已。因而一般处理步骤是:
提前预热
:根据业务部门和营销部门的统计数据,将非常热点的数据提前装入缓存,并适当延长过期时间;定时更新
:实时监控哪些数据热门,实时调整key过期时间。
5.2.2 分布式锁
当要对热key进行更新时,同一时间,只能允许一个线程做出更改。其他线程需等待构建完成后,才能读取数据。但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁
和线程池阻塞
的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
6 雪崩优化
6.1 定义
指大量key
设置同一过期时间
,这个时间点大量请求过来,可能就会把redis打崩,也可能造成后端宕机。
6.2 解决方案
6.2.1 过期时间+随机值
对缓存的数据过期时间采用:固定值+随机值
。
6.2.2 双缓存
对缓存的数据做双缓存
,A缓存时间短一些,B缓存的长一些。更新时采用队列或者canal方式或者加锁机制进行。
6.2.3 互斥锁
当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁
,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
实现互斥锁的时候,最好设置超时时间
,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。
6.2.4 数据库分库分表
提高数据库的容灾能力,可以使用分库分表
,读写分离
的策略。
6.2.5 Redis集群
为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群
,提高Redis的容灾性
。
6.3 Redis宕机解决方案
针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:
- 服务熔断或请求限流机制;
- 构建 Redis 缓存高可靠集群;