查看“关于:缓存穿透、缓存击穿、缓存雪崩、热点数据失效”的源代码
←
关于:缓存穿透、缓存击穿、缓存雪崩、热点数据失效
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:Redis]] == 关于 == 项目通常会引入NoSQL技术(即:基于内存的数据库,如redis),来应对大数据量的需求,以规避磁盘读/写速度比较慢的问题而存在的性能弊端;<br/> 但是,引入redis等又有可能出现缓存穿透,缓存击穿,缓存雪崩等问题,并需要开发对这些问题进行考量并作出应对。 == 缓存穿透(请求一个“一定不存在的数据”) == 缓存穿透:'''请求一个不存在的数据''',缓存和数据库都查不到这个数据,每次都会去数据库查询。 : 比如:用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。 === 解决方案 === 有很多种方法可以有效地解决缓存穿透问题: # '''BloomFilter''':(“布隆过滤器”)将所有可能存在的数据哈希到一个足够大的bitmap中:每次查询的时候都先去BloomFilter判断,如果没有就直接返回null;如果存在再进行查缓存、DB查询; #* 注意BloomFilter没有删除操作,对于删除的key,可以在缓存中缓存null; # '''缓存空值''':如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然'''为这些key在缓存中设置对应的值为null'''。 #* 要对这些key设置过期时间,以防止真的有数据; #: <syntaxhighlight lang="java"> //伪代码 public object GetProductListNew() { int cacheTime = 30; String cacheKey = "product_list"; String cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; } cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //数据库查询不到,为空 cacheValue = GetProductListFromDB(); if (cacheValue == null) { //如果发现为空,设置个默认值,也缓存起来 cacheValue = string.Empty; } CacheHelper.Add(cacheKey, cacheValue, cacheTime); return cacheValue; } } </syntaxhighlight> * BloomFilter可以结合缓存空值用; * 针对于一些恶意攻击,攻击带过来的大量key是不存在的,可以考虑使用BloomFilter进行过滤。 == 缓存击穿 == 缓存击穿:'''大量的请求同时查询同一个key'''时,此时这个'''key正好失效'''了,就会导致同一时间,这些请求都会去查询数据库,这样的现象我们称为缓存击穿。 === 解决方案 === # 采用分布式锁,只有'''拿到锁的第一个线程去请求数据库,然后插入缓存''',当然每次拿到锁的时候都要去查询一下缓存有没有; #: <syntaxhighlight lang="java"> //伪代码 public object GetProductListNew() { int cacheTime = 30; String cacheKey = "product_list"; String lockKey = cacheKey; String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { synchronized(lockKey) { cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { // 这里一般是sql查询数据 cacheValue = GetProductListFromDB(); CacheHelper.Add(cacheKey, cacheValue, cacheTime); } } return cacheValue; } } </syntaxhighlight> == 缓存雪崩 == 缓存雪崩:当某一时刻发生'''大规模的缓存失效'''的情况,如缓存服务宕机。 === 解决方案 === 针对服务器宕机: # 采用集群,降低服务宕机的概率; # ehcache本地缓存 + Hystrix限流&降级;【???】 #* ehcache 本地缓存:在 Redis Cluster 完全不可用的时候,ehcache 本地缓存做临时支持; #* Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑; == 热点数据失效 == 热点数据失效:缓存的数据'''集体过期'''; === 解决方案 === 针对缓存过期: # 为缓存'''设置不同的失效时间'''(原有的失效时间基础上增加一个随机值),避免集体失效; # 采用加锁或队列,来保证不会有大量的线程对数据库一次性进行读写;(缓存击穿的解决办法) #* 加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。甚至可能带来分布式锁的问题,因此,在真正的高并发场景下很少使用! # 永不失效,就是采用定时任务对快要失效的缓存进行更新缓存和失效时间; # (设置过期标志更新缓存:) #: <syntaxhighlight lang="java"> //伪代码 public object GetProductListNew() { int cacheTime = 30; String cacheKey = "product_list"; //缓存标记 String cacheSign = cacheKey + "_sign"; String sign = CacheHelper.Get(cacheSign); //获取缓存值 String cacheValue = CacheHelper.Get(cacheKey); if (sign != null) { return cacheValue; //未过期,直接返回 } else { CacheHelper.Add(cacheSign, "1", cacheTime); ThreadPool.QueueUserWorkItem((arg) -> { //这里一般是 sql查询数据 cacheValue = GetProductListFromDB(); //日期设缓存时间的2倍,用于脏读 CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2); }); return cacheValue; } } </syntaxhighlight> #* 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存; #* 缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
返回至“
关于:缓存穿透、缓存击穿、缓存雪崩、热点数据失效
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息