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

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

    关注

    12

    文章

    3856

    浏览量

    84661
  • 缓存
    +关注

    关注

    1

    文章

    220

    浏览量

    26443

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

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

收藏 人收藏

    评论

    相关推荐

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

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

    如何选择合适的本地缓存

    小编最近在使用系统的时候,发现尽管应用已经使用了 redis 缓存提高查询效率,但是仍然有进一步优化的空间,于是想到了比分布式缓存性能更好的本地缓存,因此对领域内常用的本地
    的头像 发表于 01-18 11:19 504次阅读
    如何选择合适的本地<b class='flag-5'>缓存</b>?

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

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

    mybatis一级缓存和二级缓存的原理

    MyBatis是一种轻量级的持久化框架,它提供了一级缓存和二级缓存的机制来优化数据库操作性能。一级缓存是默认开启的,而二级缓存需要手动配置启用。 一、一级
    的头像 发表于 12-03 11:55 516次阅读

    存储技术的研究发展现状及趋势

    基于可编程网络设备的在网存储系统支持在数据传输路径上执行存储任务,颠覆了传统以CPU为核心的存储系统架构思想. 根据可编程网络设备执行的任务,我们将在网存储系统分为4类:在网数据缓存
    发表于 10-11 14:47 367次阅读
    存储技术的研究发展现状及趋势

    CDN运作原理 CDN的特点总结

    的一个核心标准,当用户访问的资源恰好在缓存系统里,可以直接返回给用户,说明CDN命中;如果CDN缓存中,没有命中资源,那么会触发回源动作。 CDN回源 当CDN本地缓存没有命中时,触发
    的头像 发表于 10-09 16:12 393次阅读
    CDN运作原理 CDN的特点总结

    如何使用缓存

    中存放一个副本,当该内容下次被访问时,不必连接到驻留网站,而是由Cache中保留的副本提供。 在企业Web应用中,通过缓存技术能够提高请求的响应速度;减少系统IO开销;降低系统数据读写压力...
    的头像 发表于 10-08 14:07 331次阅读

    本地缓存的技术实践

    一、摘要 说到缓存,面试官基本上会绕不开以下几个话题! 项目中哪些地方用到了缓存?为什么要使用缓存?怎么使用它的?引入缓存后会带来哪些问题? 这些问题,基本上是互联网公司面试时必问的一
    的头像 发表于 09-30 15:29 379次阅读
    本地<b class='flag-5'>缓存</b>的技术实践

    如何在Rust中使用Memcached

    Memcached是一种高性能、分布式的内存对象缓存系统,可用于加速动态Web应用程序。Rust是一种系统级编程语言,具有内存安全、高性能和并发性等特点。Rust语言的Memcached库提供
    的头像 发表于 09-19 16:30 883次阅读

    高速缓存Cache介绍

    什么是高速缓存?• 高速存储器块,包含地址信息(通常称作TAG)和相关联的数据。• 目的是提高对存储器的平均访问速度• 高速缓存的应用基于下面两程序的局部性 :• 空间局部性:如果
    发表于 09-07 08:22

    聊聊如何实现一种闪存缓存设计

    许多web服务需要对数十亿个小对象实现快速访问,而每个小对象只有几百个字节。为了实现这一点同时考虑实际生产效益,缓存系统必须做到同时低成本,大容量与高性能。
    的头像 发表于 08-29 09:01 416次阅读
    聊聊如何实现一种闪存<b class='flag-5'>缓存</b>设计

    通用缓存引擎cachelib介绍

    网络服务几乎在系统架构的每一层都依赖于缓存。大型网络服务依靠缓存系统来实现高性能和高效率。例如,在Facebook,CDN缓存为70%的网络
    的头像 发表于 07-27 09:02 670次阅读
    通用<b class='flag-5'>缓存</b>引擎cachelib介绍

    157.157、缓存 缓存使用 本地锁在分布式下的问题

    缓存
    充八万
    发布于 :2023年07月18日 04:44:59

    聊聊本地缓存和分布式缓存

    本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无
    发表于 06-11 15:12 582次阅读
    聊聊本地<b class='flag-5'>缓存</b>和分布式<b class='flag-5'>缓存</b>

    Caffeine教程缓存介绍

    缓存(Cache)在代码世界中无处不在。从底层的CPU多级缓存,到客户端的页面缓存,处处都存在着缓存的身影。缓存从本质上来说,是一种空间换时
    的头像 发表于 05-22 11:01 670次阅读
    Caffeine教程<b class='flag-5'>缓存</b>介绍