读书笔记:缓存设计

缓存的收益和成本:
   

   1>加速读写:因为缓存通常都是全内存的例如Redis、Memcache,而存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效加速读写
   2>降低后端访问量和复杂计算
  成本:
   数据不一致:缓存层和储存层存在一定时间窗口的不一致性,时间窗口跟更新策略有关
   加入缓存后需要同时处理缓存层和逻辑层逻辑,增加了开发者维护代码成本
   提升了运营成本比如Redis Cluster为例
 

缓存的更新策略:


    缓存数据通常由生命周期的,需要在指定时间后被删除或更新,保证在一个可控范围,但是缓存中数据和数据源真实数据有一段时间窗口的不一致,需要利用某些策略进行更新
  1)LRU/LFU/FIFO算法剔除
    通常用于缓存使用量超过预设的最大值时候,如何对现有数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值对于数据的剔除策略
  2) 超时剔除
    通常给缓存设置过期时间,比如Redis提供的expire命令。但是存在一段时间窗口内一致性问题,维护成本不高,只需要设置expire过期时间即可
  3)主动更新
    应用方对数据的一致性要求高,需要在真实数据更新后立即更新缓存数据。一致性高。但是如果主动更新发生问题,那么这条数据很长时间不会更新,所以一般结合超时剔除一起使用。维护成本比较高,需要开发者自己来完成更新
 

 
    建议:低一致性业务建议配置最大内存和淘汰策略的方式使用
          高一致性可以结合使用超时剔除和主动更新,这样即使主动更新除了问题,也能保证数据过期后删除脏数据

穿透优化


   缓存穿透是指查询一个根本不存在的数据,缓存层和储存层都不会命中,通常处于容错的考虑,缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义
   可能使后端存储负载加大,由于很多后端存储不具备高并发性,甚至有可能造成后端存储宕掉。
   通常在系统中分别统计总调用数、缓存层命中数、存储层命中数、如果发现大量储存层被命中,可能就是缓存穿透问题
   原因:
   1>自身业务代码或者数据出现问题
   2>一些恶意攻击、爬虫造成大量空命中
   解决办法:
   a>缓存空对象
     当储存层不命中后,仍然将空对象保留在缓存层,之后再访问这个数据将会从缓存中获取
     存在的问题:
       第一:如果空值做了缓存,意味着需要更多内存空间(如果是攻击问题更严重)比较有效的办法是针对这类数据设置一个较短过期时间,让其自动剔除
       第二:缓存层和存储层数据会有一段时间空间不一致,可能会对业务有影响,比如此时存储层加了这个数据,导致数据不一致。此时可以利用消息系统或者其他方式清除缓存中空对象
    b>布隆过滤拦截
     在访问缓存层和存储层之前将存在的key用布隆过滤器提前保存起来,做第一层拦截
     比如:一个推荐系统有4e个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放在存储层中,但是最新的用户由于没有历史行为,就会发送缓存穿透行为,为此可以将所有推荐数据的用户做成布隆过滤器,如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层
     可以利用Redis的Bitmaps实现布隆过滤器,GitHub已经开源类似方案,这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,代码维护复杂,但是缓存空间占用少

     如图对于缓存空对象和布隆过滤器方案对比


 
 无底洞优化:


   在之前facebook的Memcache节点已经达到了3000个,承载着TB级别的缓存数据。但是出现一个问题,为了满足业务要求添加了大量新Memcache节点,但是发现性能不但没有好转反而下降了,这种现象称为缓存的无底洞
   原因:通常来说添加节点使得Memcache集群性能应该更强了,但事实并非如此。键值数据库通常采用哈希函数将key映射到各个节点上,造成key的分布于业务无关,但是由于数据量和访问量的持续增长,造成需要添加大量节点做水平扩容,导致键值分布到更多的节点上,所以无论是Memcache还是Redis分布式,批量操作通常需要从不同节点上获取,相比于单机批量操作只设计一次网络操作,分布式批量操作会多次网络时间

扫描二维码关注公众号,回复: 5749754 查看本文章

   问题分析:
     客户端一次批量操作会涉及多次网络操作,批量操作会随着节点增加,耗时不断增加,网络连接数变多对节点性能也有一定影响
     但是分布式又是不可以避免的,因为访问量和数据量越来越大一个节点根本扛不住,如果如何高效在分布式缓存中批量操作是一个难点
     

 常见的IO优化思路:
       1>命令本身的优化,比如优化SQL语句
       2>减少网络通信次数
       3>降低接入成本,比如客户端使用长/连接池、NIO等
      这里假设命令、客户端已经为最优,重点减少网络操作次数
       以Redis批量获取n个字符串 三中实现方法
       a>客户端n次get:n次网络+n次get命令本身
       b>客户端1次pipelien get :1次网络+n次get命令本身
       c>客户端1次get:1次网络+1次mget命令本身

     结合Redis Cluster特性对分布式批量操作说明:
     
      1.串行命令


        由于n个key比较均匀分布在redis cluster各个节点上,因此无法使用mget命令一次性获取,通常来讲要获取n个key的值,最简单就是逐次执行n个get命令,操作时间=n次网络时间+n次命令时间 网络次数n 特点 实现简单
      
      2.串行IO


        Redis Cluster使用CRC16算法计算出散列值,再取16383的余数可以算出slot值,客户端会保存slot和节点的对应关系,有了这两个数据可以将属于同一个节点的key进行归档,得到每个节点的key子列表,之后对每个及诶单执行mget或者pipeline操作 操作时间=node次网络时间+n次命令时间  网络次数是node的个数 但是如果节点太多还是有一定的性能问题
      3.并行IO


        是将方案2中最后一步改为多线程执行,网络次数虽然还是节点个数,但由于使用多线程网络时间变为O1, 操作时间=max_slow(node 网络时间)+n次命令时间

      4.hash_tag实现
        它可以将多个key强制分配到一个及诶单,它的操作时间=1次网络时间+n次命令时间

比较:

   

 

雪崩优化:


     由于缓冲层接受大量请求,有效的保护了储存层,但是如果缓存层由于某些原因不能提供服务,于是所有请求会达到存储层,造成存储层宕机的情况
     
     预防和解决缓存雪崩问题:

      1)保证缓存层服务高可用性:可以利用Redis Sentinel和Redis Cluster把缓存层设计成高可用,即使个别节点、个别机器甚至机房宕掉,依然可以提供服务

      2)依赖隔离组件为后端限流并降级:在并发量大的系统,假如有一个资源不可用,可能会造成线程全部阻塞在这个资源上,造成整个系统不可用。降级机制在高并发系统是非常普遍的:比如推荐服务中如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面时开天窗。   还可以对重要资源(Redis、Mysql、HBase、外部接口)都进行隔离,让每种资源都单独运行在自己线程池中,即使个别资源出了问题对其他服务没有影响(推荐使用Hystrix具有关闭资源池、开启资源池、资源池阈值管理等,但是只能用于java应用)

      3)提前演练。在项目上线前,缓存层宕掉后应用以及后端的负载情况以及可能出现的问题
 

猜你喜欢

转载自blog.csdn.net/ligupeng7929/article/details/88950724