• 关于缓存常遇到的问题


    以下内容都是在网上收集而来的

    缓存失效

      引起这个原因的主要因素是高并发下,我们一般设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些;并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间在同一时刻,这个时候就可能引发——当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。

      处理方法:

        一个简单方案就是将缓存失效时间分散开,不要所以缓存时间长度都设置成5分钟或者10分钟;比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

       缓存失效时产生的雪崩效应,将所有请求全部放在数据库上,这样很容易就达到数据库的瓶颈,导致服务无法正常提供。尽量避免这种场景的发生。

    缓存穿透

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

      当在流量较大时,出现这样的情况,一直请求DB,很容易导致服务挂掉。

         处理方法:

        方法1.在封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就已这个KEY为前缀设定一个标识KEY;以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好的非false或者NULL值,然后APP做相应的处理,这样缓存层就不会被穿透。当然这个验证KEY的失效时间不能太长。

        方法2.如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,一般只有几分钟。

        方法3.采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

    缓存并发: 

      出现场景:当网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。

      处理方法:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

    个人认为,当缓存将要失效时,及时地把新的数据刷到缓存里,这个是解决缓存失效瞬间高并发查DB的最好方法。 那么如何及时地知道缓存将要失效?

    解决这个问题有几种思路:

    比如一个key是testKey,失效时间是30s

    1.定期从DB里查询数据,再刷到缓存里

    缺点:有些业务的key可能是变化的,不确定的。而且不好界定哪些数据是应该查询出来放到缓存中的,难以区分冷热数据

    2.当缓存取到为null时,加锁去查询DB,只允许一个线程去查询DB

    缺点:这种方式不太靠谱,不多讨论。 而且如果是多个web服务器的话,还是有可能有并发的操作

    3.在向缓存写入value时,同时写入当前机器在时间作为过期时间

    当get得到数据时,如果当前时间 – 过期时间 > 5s,则后台启动一个任务去查询DB,更新缓存

    当然,这里的后台任务必须保证同一个key,只有一个线程在执行查询DB的任务,不然这个还是高并发查询DB

    缺点:是要把过期时间和value合在一起序列化,取出数据后,还要反序列化。 很不方便

    网上大部分文章提到的全都是前面两种方式,有少数文章提到第3种方式。 下面提出一种基于两个key的方法:

    4.两个key,一个key用来存放数据,另一个用来标记失效时间

    比如key是testKey,设置失效时间为30s,则另一个key为expire_testKey,失效时间为25s。用multiget,同时取出testKey和expire_testKey,如果expire_testKey的为null,则后台启动一个任务加锁去查询DB,更新缓存。集群式的部署的,如何实现只允许一个任务执行,用到memcached的add命令,或redis的setnx命令。设置expire_testKey超时过间为3s,防止后台任务失败或者阻塞。

    缺点:内存翻倍,而且程序上要维护2个key。

    5.两个key,时间存到value里,结合add/setnx来保证原子性更新缓存

    最近重新思考了下这个问题。 发现第4种两个key的办法比较耗memcached的内存,因为key数翻倍了。 结合第3种方式,重新设计了下,思路如下。

    仍然使用2个key方案,但是expire_testKey相当于锁。只允许add/setnx成功的线程去更新数据。更新成功后把expire_testKey进行删除。由于这个expire_testKey存在时间较短,不会占用太多缓存内存。

    优点:节省内存,数据是自然冷热适应的,不用担心集群带来并发风险

    总结:

    我个人是倾向于第5种方式的,因为很简单,直观。 比第4种方式要节省内存,而且不用mget,在使用memcached集群时不用担心出麻烦事

    这种两个key的方式,还有一个好处,就是数据是自然冷热适应的。 如果是冷数据,30秒都没有人访问,那么数据会过期

    如果是热门数据,一直有大流量访问,那么数据就是一直热的,而且数据一直不会过期

  • 相关阅读:
    ios --键盘监听JYKeyBoardListener
    ios -- 成员变量、实例变量与属性的区别
    input checkbox复选框全选与部分选中效果
    select可以多选实现
    input输入金额时格式化
    js数组对象相同项合并处理
    JSON常用方法
    js几种继承方式(六种)
    Object常用方法总结
    未知宽高的div水平垂直居中
  • 原文地址:https://www.cnblogs.com/xiaopotian/p/6730813.html
Copyright © 2020-2023  润新知