Redis从出门到高可用–Redis缓存设计与优化
一、缓存的受益余成本
1、受益:
(1)、加速读写
(2)、降低后端负载
后端服务器通过前端缓存降低负载:业务端使用Redis降低后端Mysql负载等
2、成本
(1)、数据不一致:缓存层和数据层有时间窗口不一致问题,和更新策略有关
(2)、代码维护成本:多了一层缓存逻辑
(3)、运维成本:例如Redis Cluster的维护成本。
3、使用场景
(1)、降低后端负载:对高消耗的SQL,例如join结果集/分组统计结果缓存。
(2)、加速请求响应:利用Redis/Memcache优化IO响应时间
(3)、大量写合并为批量写:如计数器先Redis累加再批量写DB
二、缓存更新策略
1、LRU/LFU/FIFO算法剔除:例如maxmemory-policy
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘
汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
(1). 新数据插入到链表头部;
(2). 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
(3). 当链表满的时候,将链表尾部的数据丢弃。
LFU(Least Frequently Used)算法根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。
(1). 新加入数据插入到队列尾部(因为引用计数为1);
(2). 队列中的数据被访问后,引用计数增加,队列重新排序;
(3). 当需要淘汰数据时,将已经排序的列表最后的数据块删除。
FIFO:按照“先进先出(First In,First Out)”的原理淘汰数据,正好符合队列的特性,数据结构上使用队列Queue来实现。
(1). 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动;
(2). 淘汰FIFO队列头部的数据;
2、超时剔除:例如expire
3、主动更新:开发控制生命周期
对比:
两条建议:
低一致性要求时:使用最大内存和淘汰策略
高一致性要求时:超时剔除和主动更新结合,最大内存和淘汰策略兜底。
三、缓存粒度控制
所谓的缓存粒度问题就是,在向缓存中保存数据时,是保存数据库查询记录的全部属性还是部分属性问题
应该从三个方面进行考虑:
(1)、通用性角度考虑:全量属性更好
(2)、占用空间角度考虑:部分属性更好
(3)、代码维护角度考虑:表面上看全量属性更好。
四、缓存穿透优化
当客户端发来大量请求且存在大量请求不命中时,即数据库中没有该数据,会对数据库造成较大的隐患,这就是缓存穿透。
可能的原因:
(1)、业务代码问题:根本就无法查询到该记录
(2)、恶意攻击、网络爬虫
如何解决:
(1)、将空对象缓存
但是这样会需要更多的键,且存在缓存层和存储层数据短期的不一致
五、无底洞问题优化
无底洞问题描述:
在2010年,Facebook有了3000个Memcache节点,发现当继续加机器后性能没有提升反而下降了。
问题关键点:
更多的机器!=更高的机器
批量接口的需求
数据增长和水平扩展存在矛盾
优化IO几种方法:
1、命令本身优化
2、减少网络通信的次数:串行mget、串行IO、并行IO、hash_tag
3、降低接入成本
六、缓存雪崩优化
redis挂了,客户端直接请求到数据库里面。数据库负载非常高。甚至数据库拖挂了;
优化方法:
(1)、保持缓存层服务器的高可用。
监控、集群、哨兵。当一个集群里面有一台服务器有问题,让哨兵踢出去。
(2)、依赖隔离组件为后端限流并降级。
比如推荐服务中,如果个性化推荐服务不可用,可以降级为热点数据。
(3)、提前演练。
演练 缓存层crash后,应用以及后端的负载情况以及可能出现的问题。,对此做一些预案设定。
七、热点key重建优化
问题描述:热点key+较长的重建时间,如下图:
要解决该问题的三个目标和两个解决办法:
1、三个目标:
(1)、减少重建缓存的次数
(2)、数据尽可能一致
(3)、减少潜在危险
2、两个解决办法:
(1)、互斥锁
会存在大量线程等待的过程。
(2)、永不过期:
在缓存层面,没有设置过期时间(没有用expire)但是在功能层面,为每个value添加逻辑过期时间,但是发现逻辑过期后,会使用单独的线程去构建缓存。这样不会存在线
程的等待过程,且可以保证只有一个线程进行缓存的重建,但是会存在数据不一致的情况。
两种解决方案对比: