redis学习记录(5)redis缓存

缓存的使用与设计

加入缓存的流程

一、缓存的收益与成本

1、收益

  1. 加速读写
            因为缓存通常都是全内存的(例如Redis、 Memcache) , 而存储层通常读写性能不够强悍(例如MySQL) , 通过缓存的使用可以有效地加速读写, 优化用户体验。
  2. 降低后端负载
            帮助后端减少访问量和复杂计算(例如很复杂的SQL语句) , 在很大程度降低了后端的负载。

2、成本

  1.  数据不一致:
    缓存层和数据层有时间窗口不一致,和更新策略有关。
  2. 代码维护成本:
    多了一层缓存逻辑。

二、缓存更新策略

  1. LRU/LFU/ FIFO 删除
  2. 超时剔除:例如expire
    在数据过期后, 再从真实数据源获取数据, 重新放到缓存并设置过期时间。
  3. 主动更新:开发控制生命周期

三、缓存粒度控制

一、什么是缓存粒度

下面这个图是很多项目关于缓存使用最常用的一个抽象,那么我们假设storage层为mysql,cache层为redis。

这里写图片描述

    假如我现在需要对视频的信息做一个缓存,也就是需要对select * from video where id=?的每个id在redis里做一份缓存,这样cache层就可以帮助我抗住很多的访问量。

    我们假设视频表有100个属性,那么问题来了,需要缓存什么维度呢,也就是有两种选择吧:

部分数据和全部数据

二、全部数据和部分数据比较

1. 两者的特点是显而易见的:

数据类型通用性空间占用(内存空间 + 网络码率)代码维护
全部数据简单
部分数据较为复杂

2. 通用性

    如果单从通用性上看、全部数据是最优秀的,但是有个问题就是是否有必要缓存全部数据,任务以后会有这样的需求,但是从经验上看除了非常重要的信息,那些不重要的字段基本不会再出现,也就是说着通用性,通常都是想象出来的。而且全部数据会占用大量空间。

3. 空间占用:部分属性更好。全部属性会占用更多的空间。

4. 代码维护

    代码维护性,全部数据的优势更加明显,而部分数据一旦要加新字段就会修改代码,而且还需要对原来的数据进行刷新。

四、缓存穿透优化

        缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,并且出于容错考虑, 如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

原因:

  1. 业务代码自身问题
  2. 恶意攻击。爬虫等等

如何发现:

  1. 业务的响应时间
  2. 业务本身问题
  3. 相关指标:总调用数、缓存层命中数、存储层命中数。

解决解决思路大致有两个

①、缓存空对象

 

        (1). 定义:如上图所示,当第②步MISS后,仍然将空对象保留到Cache中(可能是保留几分钟或者一段时间,具体问题具体分析),下次新的Request(同一个key)将会从Cache中获取到数据,保护了后端的Storage。

        (2) 适用场景:数据命中不高,数据频繁变化实时性高

        (3) 维护成本:代码比较简单,但是有两个问题:

             第一是空值做了缓存,意味着缓存系统中存了更多的key-value,也就是需要更多空间,解决方法是我们可以设置一个较短的过期时间。

             第二是数据会有一段时间窗口的不一致,假如,Cache设置了5分钟过期,此时Storage确实有了这个数据的值,那此段时间就会出现数据不一致,解决方法是我们可以利用消息或者其他方式,清除掉Cache中的数据。

②布隆过滤器拦截

如上图所示,在访问所有资源(cache, storage)之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截, 

五、无底洞问题优化

问题描述:2010年,Facebook有了3000个memcached节点,发现加 机器,性能并没有提升,反而下降。

 当节点从一个增加到三个,执行mget命令需要三次网络时间,并且命令执行时间受最慢的那个节点的影响。

更多的机器!=更高的性能,所谓“无底洞”就是说投入越多不一定产出越多。

优化IO的几种方法

  1. 命令本身优化:例如慢查询keys、hgetall bigkey
  2. 减少网络通信次数
  3. 降低接入成本:例如客户端长连接/连接池等。

 

 四种解决方案:

(1).串行mget

将Mget操作(n个key)拆分为逐次执行N次get操作, 很明显这种操作时间复杂度较高,它的操作时间=n次网络时间+n次命令时间,网络次数是n,很显然这种方案不是最优的,但是足够简单。

(2). 串行IO

    将Mget操作(n个key),利用已知的hash函数算出key对应的节点,这样就可以得到一个这样的关系:Map<node, somekeys>,也就是每个节点对应的一些keys

    它的操作时间=node次网络时间+n次命令时间,网络次数是node的个数,很明显这种方案比第一种要好很多,但是如果节点数足够多,还是有一定的性能问题。

 

(3). 并行IO

   此方案是将方案(2)中的最后一步,改为多线程执行,网络次数虽然还是nodes.size(),但网络时间变为O(1),但是这种方案会增加编程的复杂度。

   它的操作时间=1次网络时间+n次命令时间

 

(4). hash-tag实现。将所有的keys强制分配到一个节点。

 四种方案比较:

 

六、缓存雪崩优化

1. 由于Cache层承载着大量请求,有效的保护了Storage层(通常认为此层抗压能力稍弱),所以Storage的调用量实际很低,所以它很爽。

      2. 但是,如果Cache层由于某些原因(宕机、cache服务挂了或者不响应了)整体cache掉了,也就意味着所有的请求都会达到Storage层,所有Storage的调用量会暴增,所以它有点扛不住了,甚至会挂掉 。

 优化:

  1. 保证Cache服务高可用性
    例如redis sentinel、redis cluster。
  2. 依赖隔离组件为后端限流并降级
            其实无论是cache或者是mysql, hbase, 甚至别人的API,都会出现问题,我们可以将这些视同为资源,作为并发量较大的系统,假如有一个资源不可访问了,即使设置了超时时间,依然会hang住所有线程,造成其他资源和接口也不可以访问。
    例如推荐系统中,个性化推荐不能提供服务了,就降级补充热点数据。
  3. 提前演练:例如压力测试。

七、热点key重建优化

 我们通常使用 缓存 + 过期时间的策略来帮助我们加速接口的访问速度,减少了后端负载,同时保证功能的更新,一般情况下这种模式已经基本满足要求了。

但是有两个问题如果同时出现,可能就会对系统造成致命的危害:

      (1) 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),所以这种key访问量可能非常大。

      (2) 缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖(各种接口)等等)

因为缓存的构建是需要时间的,所以会出现一个问题:在缓存失效后,同时有大量线程重建缓存

三个目标和两个解决:

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

两个解决:

  • 互斥锁
    就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了。

  • 永不过期

    (1)从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

     (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期。
     

    两种方案对比:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值