缓存三大问题的解决办法

摘要:
1.1缓存渗透的危险如果存在大量查询根本不存在的数据的请求,这是因为缓存中没有存储这些空数据的密钥,并且具有空数据库查询结果的密钥也存储在缓存中。当对该密钥的后续查询请求发生时,(2)BloomFilter使用Blum过滤器来避免缓存,该过滤器将当前所有密钥存储在数据库中。如果不在缓存中,请转到数据库进行查询。
1.缓存穿透

  在大多数互联网应用中,缓存的使用方式如下图所示:

  缓存三大问题的解决办法第1张

   当业务系统发起某一个请求时:

    首先判断缓存中是否有该数据。

    如果缓存中存在,则直接返回数据。

    如果缓存中不存在,则再查询数据库,然后返回数据。

  了解了上述过程后,下面说说缓存穿透。

  1.1 缓存穿透的危害

  如果存在海量请求查询根本就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能导致系统崩溃(要知道,目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,所以我们要向种种办法来保护它)。

  1.2 为什么会发生缓存穿透?

  发生缓存穿透的原因有很多,一般为两种:

  (1)恶意攻击,故意营造大量不存在的数据请求我们的服务,由于缓存总并不存在这些数据,因此海量请求均落在数据库中,从而可能会导致数据库崩溃。

  (2)代码逻辑错误,这是程序员的锅,没啥好讲的,开发中一定要避免。

  1.3 缓存穿透的解决方案

  下面介绍两种防止缓存穿透的手段。

  (1)缓存空数据

    之所以发生缓存穿透,是因为缓存中没有存储这些空数据的key,导致这些请求全部都打到数据库上,那么,我们可以稍微改一下业务系统中的代码,将数据库查询结果为空的key也存储在缓存中,当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库。

  (2)BloomFilter

    第二种避免缓存的方式即为使用布隆过滤器,也就是BloomFilter.

    它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key,如下图所示:

    缓存三大问题的解决办法第2张

   当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在。则说明数据库中也不存在该数据,因此缓存都不重要了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。

  它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

  (3)两种方法的比较

  这两种方案都能解决缓存穿透的问题,但使用场景却各不相同。

  对于一些恶意攻击,查询的key往往各不相同,而且数据贼多。此时,第一种方案就显得提襟见肘了。因为它需要存储所有空数据的key,而这些恶意攻击的key往往各不相同,而且同一个key往往只请求一次。因此即使缓存了这些空数据的key,由于不再使用第二次,因此也起不了保护数据库的作用。 因此,对于空数据的key各不相同、key重复请求概率低的场景而言,应该选择第二种方案。而对于空数据的key数量有限、key重复请求概率较高的场景而言,应该选择第一种方案。

2. 缓存雪崩

  缓存雪崩就是指缓存由于某些原因(比如宕机、cache服务挂了或者不响应)整体crash掉了,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。 

  下面的就是一个雪崩的简单过程:

    1、redis集群彻底崩溃

    2、缓存服务大量对redis的请求hang住,占用资源

    3、缓存服务大量的请求打到源头服务去查询mysql,直接打死mysql

    4、源头服务因为mysql被打死也崩溃,对源服务的请求也hang住,占用资源

    5、缓存服务大量的资源全部耗费在访问redis和源服务无果,最后自己被拖死,无法提供服务

    6、nginx无法访问缓存服务,redis和源服务,只能基于本地缓存提供服务,但是缓存过期后,没有数据提供

    7、网站崩溃

  解决方法

     1. 加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3.  令牌桶Token Bucket 4.漏桶 leaky bucket [1]

     在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

     业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如RedisSETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

     2.数据预热

      可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

     3.做二级缓存,或者双缓存策略。

        A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

        4.缓存永远不过期

     这里的永远不过期包含两层意思:

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

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

   从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

3. 缓存击穿

  3.1 什么是击穿

  对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

  3.2 会带来什么问题

  缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 

  3.3 如何解决

  1.使用互斥锁(mutex key)

  业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

  2. "提前"使用互斥锁(mutex key):

  在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。

  3."永远不过期"  

  这里的永远不过期包含两层意思:

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

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

        从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

  4. 资源保护:

  采用netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。

  四种解决方案:没有最佳只有最合适

  缓存三大问题的解决办法第3张

 

  

    

免责声明:文章转载自《缓存三大问题的解决办法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇IP分割和重组配置 OSPF 的 DR 选择示例下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[Project] HUSTOJ随笔

转载自 : http://blog.csdn.net/zhblue/article/details/7259940 (版权为 zhblue) HUSTOJ http://code.google.com/p/hustoj 是一个开源OnlineJudge系统,广泛应用于计算机程序设计比赛和编程能力测试。 从代码上HUSTOJ分为两大部分,core...

数据库系统Informix为例,介绍改善用户查询计划的方法。

数据库系统Informix为例,介绍改善用户查询计划的方法。 1.合理使用索引 索引是数据库中重要的数据结构,它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处,其使用原则如下: ●在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则由优化器自动生成索引。 ●在频繁进行排...

配置CNPM-基础案例

下面给出一个样例配置: JavaScript module.exports = { enableCluster: true, database: { db: "snpm", username: "username", password: "password", dialect: "mysql", host: "127.0.0.1", port: 3306...

Zabbix之配置文件详解

zabbix的配置文件一般有三种:zabbixserver的配置文件zabbix_server.confzabbixproxy的配置文件zabbix_proxy.confzabbix_agentd的配置文件zabbix_agentd.conf 1.zabbixserver的配置文件: NodeID=0 #分布式节点id号,0代表是独立服务器,默认是被注释掉...

Windows 安装配置 JIRA

MySQL-5.5.28 JDK1.6.0_21  JIRA功能全面,界面友好,安装简单,配置灵活,权限管理以及可扩展性方面都十分出色。  一、MySQL建库和建账号 1、 mysql中创建数据库jiradb create database jiradb character set 'UTF8'; 2、创建数据库用户并赋于权限 create user ji...

Vue vue-resource 请求数据

<template> <!-- 所有的内容要被根节点包含起来 --> <div id="home"> 首页组件 <button @click="getData()">请求数据</button> <hr&g...