02-缓存引入的问题

缓存引入的问题

一、数据一致性问题

1.1 问题

  • 之前说道缓存本质是数据的拷贝,因此可能存在数据不一致的情况,在DB数据更新之后,需要更新缓存中的数据,理论上总会有时间差,因此数据不一致性问题再用引入缓存的时候就存在,为了降低这个问题的影响,我们能够做的就是在更新DB之后,尽可能快的更新缓存数据。

1.2 解决办法

1.2.1 实时更新
  • 实时更新。在DB修改之后,立刻操作更新缓存。
1.2.2 准实时更新
  • 通过消息中间件,发布订阅模式来更新,实时性相对弱一点。
1.2.3 缓存失效
  • 利用缓存本身的失效机制,等缓存失效之后,后面的读请求会访问数据库并更新缓存。
1.2.4 定时任务更新
  • 定时任务扫描缓存,同步最新数据到缓存。
一致性问题方案技术特点优点缺点适用场景
实时更新使用缓存工具或者编码实现数据一致性强代码耦合度高数据一致性要求高的场景
准实时更新消息中间件或者发布订阅等方式实现延迟较短但是与业务解耦有一定延迟,需要有补偿机制不适合强一致场景
缓存失效基于缓存本身特点实现简单有一定延迟,弱一致性,存在缓存雪崩的问题能够接受一定延迟的场景,数据一致性弱
定时任务最终一致性,采用任务调度框架实现与业务解耦不保证一致性,依赖框架,容易堆积垃圾数据占用资源适用数据一致性要求很低的场景

二、缓存穿透

2.1 问题

  • 缓存穿透是指查询一个一定不存在的数据。如果数据库和缓存中都不存在。那么请求会直接访问数据库,并且获取不到数据,业务无法将结果放到缓存,下一次来请求还是一样会访问数据库。这种情况,缓存形同虚设,访问全部走到数据库,仿佛穿透了缓存一般。如果偶尔一次问题不大,但是这存在很大的安全隐患,如果有人蓄意攻击,不断的请求一条明显不存在的记录,那么数据库将接受高并发的访问,有宕机的危险。

2.2 解决办法

2.2.1 布隆过滤器
  • 我们姑且称上面问题描述中的那条不存在的记录的查询key是假key,那么缓存中是没有假key对应的缓存值的。布隆过滤器的作用是:如果一个key是假key(它在数据库中没有对应的记录),那么经过布隆过滤器之后返回的布尔值是false,如果这个key经过布隆过滤器返回的是true,那么并不一定是假key(它在数据库可能有记录)。
如果布隆过滤器返回false  ----> 那么这个key一定是一个假key(一定是不存在的key)
如果布隆过滤器返回true   ----> 那么这个key不一定是真key(这个key也有可能是一个假key)
flase is always false , true maybe true。
  • 我们可以在获取缓存失败之后,读取DB之前,用布隆过滤器来验证key,如果返回false,那么不好意思,直接返回,保护我的数据库。如果返回true,那么就给个机会去DB查一下,查不查得到不管。
2.2.2 缓存空值
  • 既然假key查询数据库结果是空,那我也将这个(k,null)放到缓存,下一次即使它再来请求,直接就从缓存获取到了结果,避免访问数据库,注意这个空值的过期时间不要过长。

2.3 拓展

三、缓存雪崩

3.1 问题

  • 在前面分析利用缓存自身的失效机制来保证数据一致性的时候,分析了该方法的缺点是存在缓存雪崩的可能。缓存雪崩,就是大量的key同时失效,犹如雪崩一样。比如说我们在某个时刻生成了很多缓存,我们设置的失效时间是T以后,等到T以后的时候,这一批缓存会同时失效,如果此时很多请求都是来获取这些缓存的,那么都需要同时访问数据库,因此给数据库造成压力,带来宕机风险。

3.2 解决办法

3.2.1 随机过期时间
  • 在过期时间的选择上下功夫,比如添加一个随机的范围,避免大量key同时失效。
3.2.2 访问加锁
  • 访问数据库的时候需要加锁,这不仅仅是应对缓存雪崩,这是高并发下保护数据库的一种方法,如果缓存没有了,多个线程访问数据库需要排队,第一个获取lock的线程load数据之后放到缓存,等到第二个线程获取lock后,再次查一下缓存,查到了就直接返回了,这里在01-缓存基本概念的文章的缓存访问基本模板中可以看到,访问数据库需要加锁。

四、缓存击穿

4.1问题

  • 缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。电商项目称之为“爆款”。
    和缓存雪崩的区别在于,雪崩是大量key失效,因此形成雪崩,击穿是单个热点数据。

4.2 解决办法

4.2.1 使用互斥锁(mutex key)
  • 这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了。如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,
    可以用redis的setnx, zookeeper的添加节点操作)。以redis为例,如果获取失败之后,尝试去setnx一个key,只有一个线程设置成功,其他的会等待,成功的线程去操作数据库并且完成缓存会写,伪代码如下:
    result = queryFromCache(id); //1.缓存查询
    if(result != null){
        return result;  //2.缓存有就返回
    }
    //3.缓存没有就查数据库,需要加锁避免并发请求数据库
    lock(id);
    try{
        //4.获取锁之后,有可能另一个读请求已经将数据加载到缓存了,因此尝试一次缓存中获取
        result = queryFromCache(id);
        if(result != null){
            return result;  //5.获取成功返回
        }
        result = queryFromDb(id);//6.缓存中没有即请求数据库
        if(result != null){
            setCache(result);//7.将结果放入缓存
        }        
        return result;   
    }finally{
        unLock(id);//8.finally保证锁会释放        
    }
 
4.2.2 永远不过期
  • 物理永不过期。因此就不会出现热点key过期的问题了
  • 逻辑不过期。将过期时间保存在key的value里面,发现快要过期时就通过异步的方式刷新缓存值。

五、一致性Hash

5.1 问题

  • 假设我们有N台服务器,根据key的hash值对N取余来决定缓存放在哪一台服务器。比如N是5,k1的hash是7,
    那么它应该放在第2台服务器。基于这样前提,假设我们的3号服务器故障,剩下4台可用服务器。我们通过
    hash(K)%4来计算k对应的缓存应该保存在哪一台服务器,此时我们会发现,绝大部分的k计算后的位置和3号
    服务器故障之前都不一样了,这里我们可用算一下,要想不变需要满足hash(k)%5==hash(k)%4,数学计算可知
    大部分数字不满足,比如一个k的hash是14,宕机前在4号服务器,宕机后在2号服务器。因此会造成大量的key
    访问不到,需要去数据库加载。缓存服务器的节点的伸缩会导致大量的key无法命中,这就是一致性Hash问题。
Hash算法通常采用求余的方法来做一些类似于负载均衡,分布式的操作。传统的hash算法,比如5台服务器,
对5求余之后找对应的缓存数据,但是这样的方案在增加服务器或者减少服务器的时候,不好处理,比如增加了
一台,原本一个key的hash是22,对5取余是2,到第二台设备获取缓存,但是现在对6取余,得到的是4,就找不
到数据了,最坏的情况是需要将前面的5台设备的数据全部再次分配一次,代价很大。

5.2 解决办法

5.2.1 一致性Hash算法(DHT)
  • 一致性hash算法是将hash值对2的32次方取余,将0-2的32次方之间的数据看做一个环,所有的key取余之后,顺
    时针方向的第一个服务器就是他保存数据的服务器。假设我们的服务器所处的位置就是在环上大致均匀分布,假设
    增加了一台设备6,它在5号服务器和0号服务器之间,那么只需要将5号服务器上的部分数据迁移到6即可,同理减少
    了5号的话,只需要将5的数据迁移到0即可(顺时针),一致性hash算法可以很好的解决分布式环境下服务器的增减
    问题,包括负载均衡问题,Nginx中有使用到。

  • 一致性哈希的问题:如果服务器分布不均匀,可能导致数据分布不均匀,因此在一致性hash算法的基础上,改进
    形成了虚拟一致性hash来解决,

5.2.2 虚拟一致性Hash

  • 虚拟一致性Hash是一致性Hash的升级,在一致性Hash中,如果服务器分布不均匀,比如部分服务器挨得太近,回导
    致缓存分布不均匀的问题,通过一致性Hash来解决(这也是一致性Hahs算法得以应用的主要原因),原理是将每一台服务器虚拟出来几个节点,分布在虚拟节点的数据保存到其对应的物理节点,来让缓存分布更加均匀。

六、参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值