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

    文章

    4542

    浏览量

    87593
  • 缓存
    +关注

    关注

    1

    文章

    246

    浏览量

    27344

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

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

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    高性能缓存设计:如何解决缓存伪共享问题

    在多核高并发场景下, 缓存伪共享(False Sharing) 是导致性能骤降的“隐形杀手”。当不同线程频繁修改同缓存行(Cache Line)中的独立变量时,CPU缓存
    的头像 发表于 07-01 15:01 ?158次阅读
    高性能<b class='flag-5'>缓存</b>设计:如何解决<b class='flag-5'>缓存</b>伪共享问题

    MCU缓存设计

    MCU 设计通过优化指令与数据的访问效率,显著提升系统性能并降低功耗,其核心架构与实现策略如下: 缓存类型与结构 指令缓存(I-Cache)与数据
    的头像 发表于 05-07 15:29 ?410次阅读

    Nginx缓存配置详解

    Nginx 是功能强大的 Web 服务器和反向代理服务器,它可以用于实现静态内容的缓存缓存可以分为客户端缓存和服务端
    的头像 发表于 05-07 14:03 ?653次阅读
    Nginx<b class='flag-5'>缓存</b>配置详解

    nginx中强缓存和协商缓存介绍

    缓存直接告诉浏览器:在缓存过期前,无需与服务器通信,直接使用本地缓存
    的头像 发表于 04-01 16:01 ?427次阅读

    缓存与不带缓存的固态硬盘有什么区别

    随着信息技术的不断进步,存储设备作为计算机系统的核心组成部分,其性能与稳定性直接影响到整个系统的运行效率。固态硬盘(Solid State Disk,简称SSD)作为新代存储设备,以其高速读写、低
    的头像 发表于 02-06 16:35 ?2571次阅读

    基于javaPoet的缓存key优化实践

    作者:京东物流 方志民 . 背景 在系统opsreview中,发现了些服务配置了@Cacheable注解。@cacheable 来源于spring cache框架中,作用是使用
    的头像 发表于 01-14 15:18 ?855次阅读
    基于javaPoet的<b class='flag-5'>缓存</b>key优化实践

    缓存对大数据处理的影响分析

    缓存对大数据处理的影响显著且重要,主要体现在以下几个方面: 、提高数据访问速度 在大数据环境中,数据存储通常采用分布式存储系统,数据量庞大,直接从存储系统中读取数据会存在较高的延迟。
    的头像 发表于 12-18 09:45 ?814次阅读

    HTTP缓存头的使用 本地缓存与远程缓存的区别

    HTTP缓存头是组HTTP响应头,它们控制浏览器和中间代理服务器如何缓存网页内容。合理使用HTTP缓存头可以显著提高网站的加载速度和性能,减少服务器的负载。 1. HTTP
    的头像 发表于 12-18 09:41 ?502次阅读

    Web缓存的类型及功能分析

    速度,降低了延迟,并提高了网站的可用性。 Web缓存的类型 Web缓存主要分为以下几种类型: 浏览器缓存(Browser Cache) 功能 :浏览器缓存是用户计算机上的
    的头像 发表于 12-18 09:35 ?843次阅读

    缓存技术在软件开发中的应用

    在现代软件开发中,随着数据量的爆炸性增长和用户对响应速度的高要求,缓存技术成为了提升系统性能的重要手段。缓存技术通过将数据存储在离用户更近的位置,减少数据访问延迟,提高数据处理速度,从而优化
    的头像 发表于 12-18 09:32 ?747次阅读

    什么是缓存(Cache)及其作用

    缓存(Cache)是种高速存储器,用于临时存储数据,以便快速访问。在计算机系统中,缓存的作用是减少处理器访问主存储器(如随机存取存储器RAM)所需的时间。
    的头像 发表于 12-18 09:28 ?1.2w次阅读

    探讨移动设备中的缓存文件管理

    ? 本文发表于FAST 2022。 探讨 缓存文件管理方法。本文 通过轻量级的基于机器学习的分类引擎来筛选和个性化管理缓存文件 ,实验 在 华为P9 和 Mate30 两部手机上进
    的头像 发表于 11-28 11:50 ?1109次阅读
    探讨移动设备中的<b class='flag-5'>缓存</b>文件管理

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

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

    异构计算下缓存致性的重要性

    在众多回复中,李博杰同学的回答被认为质量最高。他首先将缓存致性分为两主要场景:是主机内CPU与设备间的致性;二是跨主机的
    的头像 发表于 10-24 17:00 ?1820次阅读
    异构计算下<b class='flag-5'>缓存</b><b class='flag-5'>一</b>致性的重要性

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

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