0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何设计一个缓存系统?

数据分析与开发 来源:CSDN 作者:zeb_perfect 2021-02-08 11:40 次阅读

设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。

缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿

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

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

解决方案

1.使用互斥锁(mutex key)

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

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间,所以这里给出两种版本代码参考:

//2.6.1前单机版本锁 Stringget(Stringkey){ Stringvalue=redis.get(key); if(value==null){ if(redis.setnx(key_mutex,"1")){ //3mintimeouttoavoidmutexholdercrash redis.expire(key_mutex,3*60) value=db.get(key); redis.set(key,value); redis.delete(key_mutex); }else{ //其他线程休息50毫秒后重试 Thread.sleep(50); get(key); } } }

最新版本代码:

publicStringget(key){ Stringvalue=redis.get(key); if(value==null){//代表缓存值过期 //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能loaddb if(redis.setnx(key_mutex,1,3*60)==1){//代表设置成功 value=db.get(key); redis.set(key,value,expire_secs); redis.del(key_mutex); }else{//这个时候代表同时候的其他线程已经loaddb并回设到缓存了,这时候重试获取缓存值即可 sleep(50); get(key);//重试 } }else{ returnvalue; } }

memcache代码:

if(memcache.get(key)==null){ //3mintimeouttoavoidmutexholdercrash if(memcache.add(key_mutex,3*60*1000)==true){ value=db.get(key); memcache.set(key,value); memcache.delete(key_mutex); }else{ sleep(50); retry(); } }

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

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

v=memcache.get(key); if(v==null){ if(memcache.add(key_mutex,3*60*1000)==true){ value=db.get(key); memcache.set(key,value); memcache.delete(key_mutex); }else{ sleep(50); retry(); } }else{ if(v.timeout<= now()) {           if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {               // extend the timeout for other threads               v.timeout += 3 * 60 * 1000;               memcache.set(key, v, KEY_TIMEOUT * 2);                  // load the latest value from db               v = db.get(key);               v.timeout = KEY_TIMEOUT;               memcache.set(key, value, KEY_TIMEOUT * 2);               memcache.delete(key_mutex);           } else {               sleep(50);               retry();           }       }   } 

3. "永远不过期":

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

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

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

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

Stringget(finalStringkey){ Vv=redis.get(key); Stringvalue=v.getValue(); longtimeout=v.getTimeout(); if(v.timeout<= System.currentTimeMillis()) {               // 异步更新后台异常执行               threadPool.execute(new Runnable() {                   public void run() {                       String keyMutex = "mutex:" + key;                       if (redis.setnx(keyMutex, "1")) {                           // 3 min timeout to avoid mutex holder crash                           redis.expire(keyMutex, 3 * 60);                           String dbValue = db.get(key);                           redis.set(key, dbValue);                           redis.delete(keyMutex);                       }                   }               });           }           return value;   }

4. 资源保护:

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

没有最佳只有最合适

解决方案 优点 缺点
简单分布式互斥锁(mutex key) 1. 思路简单
2. 保证一致性
1. 代码复杂度增大
2. 存在死锁的风险
3. 存在线程池阻塞的风险
“提前”使用互斥锁 1. 保证一致性 同上
不过期(本文) 1. 异步构建缓存,不会阻塞线程池 1. 不保证一致性。
2. 代码复杂度增大(每个value都要维护一个timekey)。
3. 占用一定的内存空间(每个value都要维护一个timekey)。
资源隔离组件hystrix(本文) 1. hystrix技术成熟,有效保证后端。
2. hystrix监控强大。
1. 部分访问存在降级策略。

四种方案来源网络,详文链接:http://carlosfu.iteye.com/blog/2269687

总结

针对业务系统,永远都是具体情况具体分析,没有最好,只有最合适。

最后,对于缓存系统常见的缓存满了和数据丢失问题,需要根据具体业务分析,通常我们采用LRU策略处理溢出,Redis的RDB和AOF持久化策略来保证一定情况下的数据安全。

原文标题:如何设计缓存系统:缓存穿透,缓存击穿,缓存雪崩解决方案分析

文章出处:【微信公众号:数据分析与开发】欢迎添加关注!文章转载请注明出处。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    13

    文章

    4261

    浏览量

    85660
  • 缓存
    +关注

    关注

    1

    文章

    233

    浏览量

    26645

原文标题:如何设计缓存系统:缓存穿透,缓存击穿,缓存雪崩解决方案分析

文章出处:【微信号:DBDevs,微信公众号:数据分析与开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    缓存之美——如何选择合适的本地缓存

    Guava cache是Google开发的Guava工具包中套完善的JVM本地缓存框架,底层实现的数据结构类似于ConcurrentHashMap,但是进行了更多的能力拓展,包括缓存过期时间设置、
    的头像 发表于 11-17 14:24 150次阅读
    <b class='flag-5'>缓存</b>之美——如何选择合适的本地<b class='flag-5'>缓存</b>?

    什么是CPU缓存?它有哪些作用?

    CPU缓存(Cache Memory)是计算机系统至关重要的组成部分,它位于CPU与内存之间,作为两者之间的临时存储器。CPU缓存的主
    的头像 发表于 08-22 14:54 2343次阅读

    ESP8266缓存AP后,是否会自动连接到任何缓存的AP?

    我有关于工作站模式的 AP 缓存的问题。我知道ESP8266最多可以缓存 5 AP 已成功连接。SDK 说: wifi_stati
    发表于 07-11 07:58

    缓存有大key?你得知道的些手段

          背景: 最近系统缓存CPU使用率直报警,超过设置的70%报警阀值,针对此场景,需要对应解决缓存是否有大key使用问题,扫描缓存
    的头像 发表于 06-19 09:38 690次阅读
    <b class='flag-5'>缓存</b>有大key?你得知道的<b class='flag-5'>一</b>些手段

    LOTO示波器软件PC缓存(波形录制与回放)功能

    的数据波形,我们在获取足够的缓存后,可以先暂停示波器的采集:,就可以看到PC缓存也停止了,滑动条也出现了,我们可以静态的选中任何帧我们感兴趣的波形。每个数据帧显示为
    发表于 05-16 11:23

    微软推出Garnet缓存系统,优化应用运行效率

    自2021年起,微软便开始致力于Garnet项目研发。微软在声明中强调此举旨在赋予应用与数据交互以史无前例的高效、低延时和经济效益。Garnet是由微软研究院基于C#.NET8.0打造的远程缓存存储系统,旨在满足快速、扩展性强且低延迟的需求。
    的头像 发表于 03-20 14:09 664次阅读

    交换机分布缓存_述说数据中心交换机的重要性能指标——缓存

    交换机是数据中心不可缺少的网络设备,在数据中心里发挥着重要作用。在平时使用和采购时,大多数都关注交换机的背板带宽、端口密度、单端口速度、协议特性等方面的性能指标,很少有人去关注缓存指标,这是常常
    的头像 发表于 03-15 17:39 701次阅读

    如何选择合适的本地缓存

    的 Guava 缓存、在 Guava 上进步传承的 Caffine 以及自称在 Java 中使用最广泛的 EhCache,那么我们该怎么选择适合自己应用的缓存呢,小编下面会简单介绍,并将以上
    的头像 发表于 01-18 11:19 809次阅读
    如何选择合适的本地<b class='flag-5'>缓存</b>?

    labview怎么清除串口缓存的数据

    LabVIEW 是款功能强大的图形化编程软件,常用于控制、监测和数据采集等应用。当我们使用串口进行数据通信时,有时会遇到串口缓存的数据无法及时清除或清除不彻底的情况。解决这个问题的方法有多种,下面
    的头像 发表于 01-08 11:30 3510次阅读

    文了解CPU高速缓存

    CPU的核心功能包括数据运算和指令控制。CPU运算的数据和执行的指令全部存储在CPU的寄存器中,这些数据和指令又都来自于CPU高速缓存
    的头像 发表于 01-02 16:01 1496次阅读
    <b class='flag-5'>一</b>文了解CPU高速<b class='flag-5'>缓存</b>

    Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透要点简析

    缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统
    的头像 发表于 12-25 09:41 849次阅读
    Redis<b class='flag-5'>缓存</b>预热+<b class='flag-5'>缓存</b>雪崩+<b class='flag-5'>缓存</b>击穿+<b class='flag-5'>缓存</b>穿透要点简析

    CPU Cache是如何保证缓存致性的?

    我们介绍`CPU Cache`的组织架构及其进行**读操作**时的寻址方式,但是缓存不仅仅只有读操作,还有 **写操作** ,这会带来新的问题
    的头像 发表于 12-04 15:05 1326次阅读
    CPU Cache是如何保证<b class='flag-5'>缓存</b><b class='flag-5'>一</b>致性的?

    mybatis缓存和二级缓存的原理

    SqlSession的生命周期中,当SqlSession关闭时,缓存也会被清空。 1.2 缓存实现机制 缓存采用了基于Perpetu
    的头像 发表于 12-03 11:55 1081次阅读

    Redis缓存与Mysql如何保证致性?

    基本流程就是客户端A请求,先去删除缓存,然后将数据写入数据库,此时客户端B查询先去查询缓存缓存没有返回,去查数据库,此时还没有完成主从同步,拿到是从库的旧数据,然后将旧数据进行缓存
    的头像 发表于 12-02 14:23 890次阅读
    Redis<b class='flag-5'>缓存</b>与Mysql如何保证<b class='flag-5'>一</b>致性?

    Spring Cache缓存常规配置

    作者最近在开发公司项目时使用到 Redis 缓存,并在翻看前人代码时,看到了种关于 @Cacheable 注解的自定义缓存有效期的解决方案,感觉比较实用,因此作者自己拓展完善了番后
    的头像 发表于 11-28 10:44 571次阅读
    Spring Cache<b class='flag-5'>缓存</b>常规配置