redis学习笔记11-缓存设计
1. 缓存的收益和成本
缓存的收益:
1)加速读写:缓存通常都是全内存的,通过缓存的使用可以有效地加速读写,优化用户体验。
2)降低后端负载:帮助后端减少访问量,降低了后端的负载。
缓存成本:
1)数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
2)代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。
3)运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。
2. 缓存更新策略
2.1. LRU/LFU/FIFO算法剔除
1)使用场景:用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。
2)一致性:要清理哪些数据是由具体算法决定,开发人员只能决定使用哪种算法,所以数据的一致性是最差的。
3)维护成本:算法不需要开发人员自己来实现,通常只需要配置最大maxmemory和对应的策略即可。开发人员只需要知道每种算法的含义,选择适合自己的算法即可。
2.2. 超时剔除
1)使用场景:给缓存数据设置过期时间,让其在过期时间后自动删除。
2)一致性:一段时间窗口内(取决于过期时间长短)存在一致性问题,即缓存数据和真实数据源的数据不一致。
3)维护成本:维护成本不是很高,只需设置expire过期时间即可。
2.3. 主动更新
1)使用场景:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。
2)一致性:一致性最高,但如果主动更新发生了问题,那么这条数据很可能很长时间不会更新,所以建议结合超时剔除一起使用效果会更好。
3)维护成本:维护成本会比较高,开发者需要自己来完成更新,并保证更新操作的正确性。
2.4. 最佳实践
1)低一致性业务建议配置最大内存和淘汰策略的方式使用。
2)高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据。
3. 缓存粒度控制
即缓存数据时,是缓存全部列还是缓存重点列的问题,需要根据业务具体情况进行取舍。
4. 缓存穿透
定义:指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
危害:可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。
产生原因:自身业务代码或者数据出现问题;一些恶意攻击、爬虫等造成大量缓存不命中。
解决方案:
1)缓存空对象,即存储层不命中后,仍然将空对象保留到缓存层。
问题:缓存层中存了更多的键,需要更多的内存空间;缓存层和存储层的数据会有一段时间窗口的不一致。
解决方案:设置一个较短的过期时间,让其自动剔除;主动更新缓存。
2)布隆过滤器拦截
5. 无底洞优化
当缓存节点到达一定数量后,添加节点不但不能提供性能反而降低性能,这种现象就叫无底洞现象。一般来说对于单个key的操作不会存在无底洞问题,但批量操作,如mget、pineline操作等会出现无底洞问题。
无底洞问题分析:客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随着节点的增多,耗时会不断增大。
解决方法:
1)串行命令:将批量指令转化成单个指令逐个执行。操作时间=n次网络时间+n次命令时间
2)串行IO:将属于同一个节点的key进行归档,然后对每个节点执行批量操作。操作时间=node次网络时间+n次命令时间。
3)并行IO:将属于同一个节点的key进行归档,然后使用多线程并行方式对每个节点执行批量操作。操作时间=max_slow(node 网络时间 )+n 次命令时间。
4)hash_tag方式
redis集群使用hash函数对key进行计算,然后和16383(槽数量)取模,得到该key应该存储到那个槽中,在redis中,key是唯一标识一条数据的键,但有些键本身具有一定的关联性,并且也需要将这种数据保存到同一个槽中。如user:user1:ids保存用户id,user:user1:detail保存用户的具体信息,两个key不同名但两个key有相同的地方,即user。能不能拿这一部分去计算hash呢?这就是 Hash Tag。redis允许用key的部分字符串来计算hash,当一个key包含 {} 的时候,就不对整个key做hash,而仅对 {} 包括的字符串做hash。如user:{user1}:ids和user:{user1}:detail,其hash值都等同于sha1(user1)。
hash_tag方式可以将多个key强制分配到一个节点上,它的操作时间=1次网络时间+n次命令时间。
5)无底洞4种解决方案总结
6. 雪崩优化
定义:由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请
求都会达到存储层,存储层的调用量会暴增,造成存储层宕机的情况。
解决方案:
1)保证缓存层服务高可用性,redis sentinel和redis分布式缓存都能保证redis的高可用。
2)Hystrix熔断、服务降级机制。当缓存不可用时,可用采用熔断、服务降级等机制来保护后端存储层。例如:后端数据库可支持的并发为1000每秒,而系统的并发为10000每秒,当缓存不可用时,可在应用程序中过滤掉9000并发(直接异常返回),只放1000的并发到后端存储层,从而让系统部分可用,即在应用程序中限制并发量。
注意:如果并发量很大,而缓存又不可用,出现雪崩时,重启系统是无法解决问题的。
7. 热点key重建优化
使用“缓存+过期时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但如下两个问题同时出现时,可能会对应用系统造成致命的影响:
1)当前key是一个热点key,并发量非常大。
2)当前key失效,而重建缓存可能很费时。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
1)互斥锁:
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
优点:减轻后端存储层的并发压力。
缺点:大量并发请求在应用层等待,可能导致应用程序出现雪崩效应。
2)永不过期
对热点key不设置过期时间,但为其对于的value设置一个逻辑过期时间,启用一个线程定期查询该key对应的逻辑过期时间,如果发现超过逻辑过期时间,则使用这个线程去重建key对应的value。