Redis从出门到高可用--Redis缓存设计与优化

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)、代码维护角度考虑:表面上看全量属性更好。

四、缓存穿透优化

当客户端发来大量请求且存在大量请求不命中时,即数据库中没有该数据,会对数据库造成较大的隐患,这就是缓存穿透。

image

可能的原因:
 (1)、业务代码问题:根本就无法查询到该记录
(2)、恶意攻击、网络爬虫

如何解决:
(1)、将空对象缓存

image

但是这样会需要更多的键,且存在缓存层和存储层数据短期的不一致

五、无底洞问题优化

无底洞问题描述:
在2010年,Facebook有了3000个Memcache节点,发现当继续加机器后性能没有提升反而下降了。
问题关键点:
更多的机器!=更高的机器
批量接口的需求
数据增长和水平扩展存在矛盾

优化IO几种方法:
1、命令本身优化
2、减少网络通信的次数:串行mget、串行IO、并行IO、hash_tag
3、降低接入成本

六、缓存雪崩优化

redis挂了,客户端直接请求到数据库里面。数据库负载非常高。甚至数据库拖挂了;

image

优化方法: 
(1)、保持缓存层服务器的高可用。 
监控、集群、哨兵。当一个集群里面有一台服务器有问题,让哨兵踢出去。 
(2)、依赖隔离组件为后端限流并降级。 
比如推荐服务中,如果个性化推荐服务不可用,可以降级为热点数据。 
(3)、提前演练。 
演练 缓存层crash后,应用以及后端的负载情况以及可能出现的问题。,对此做一些预案设定。

七、热点key重建优化

问题描述:热点key+较长的重建时间,如下图:

image

要解决该问题的三个目标和两个解决办法:
1、三个目标:
    (1)、减少重建缓存的次数
    (2)、数据尽可能一致
    (3)、减少潜在危险

2、两个解决办法:
    (1)、互斥锁

image

会存在大量线程等待的过程。
(2)、永不过期:
 在缓存层面,没有设置过期时间(没有用expire)但是在功能层面,为每个value添加逻辑过期时间,但是发现逻辑过期后,会使用单独的线程去构建缓存。这样不会存在线
 程的等待过程,且可以保证只有一个线程进行缓存的重建,但是会存在数据不一致的情况。

image

两种解决方案对比:

image

文章目录
  1. 1. 一、缓存的受益余成本
  2. 2. 二、缓存更新策略
  3. 3. 三、缓存粒度控制
  4. 4. 四、缓存穿透优化
  5. 5. 五、无底洞问题优化
  6. 6. 六、缓存雪崩优化
  7. 7. 七、热点key重建优化