Redis缓存的主要异常及解决方案
1 导读
Redis 是当前最流行的 NoSQL数据库。Redis主要用来做缓存使用,在提高数据查询效率、保护数据库等方面起到了关键性的作用,很大程度上提高系统的性能。当然在使用过程中,也会出现一些异常情景,导致Redis失去缓存作用。2 异常类型
异常主要有 缓存雪崩 缓存穿透 缓存击穿。2.1 缓存雪崩2.1.1 现象
缓存雪崩是指大量请求在缓存中没有查到数据,直接访问数据库,导致数据库压力增大,最终导致数据库崩溃,从而波及整个系统不可用,好像雪崩一样。
2.1.2 异常原因缓存服务不可用。缓存服务可用,但是大量KEY同时失效。2.1.3 解决方案
1.缓存服务不可用
redis的部署方式主要有单机、主从、哨兵和 cluster模式。单机
只有一台机器,所有数据都存在这台机器上,当机器出现异常时,redis将失效,可能会导致redis缓存雪崩。主从
主从其实就是一台机器做主,一个或多个机器做从,从节点从主节点复制数据,可以实现读写分离,主节点做写,从节点做读。
优点:当某个从节点异常时,不影响使用。
缺点:当主节点异常时,服务将不可用。哨兵
哨兵模式也是一种主从,只不过增加了哨兵的功能,用于监控主节点的状态,当主节点宕机之后会进行投票在从节点中重新选出主节点。
优点:高可用,当主节点异常时,自动在从节点当中选择一个主节点。
缺点:只有一个主节点,当数据比较多时,主节点压力会很大。cluster模式
集群采用了多主多从,按照一定的规则进行分片,将数据分别存储,一定程度上解决了哨兵模式下单机存储有限的问题。
优点:高可用,配置了多主多从,可以使数据分区,去中心化,减小了单台机子的负担.
缺点:机器资源使用比较多,配置复杂。小结
从高可用得角度考虑,使用哨兵模式和cluster模式可以防止因为redis不可用导致的缓存雪崩问题。
2.大量KEY同时失效
可以通过设置永不失效、设置不同失效时间、使用二级缓存和定时更新缓存失效时间设置永不失效
如果所有的key都设置不失效,不就不会出现因为KEY失效导致的缓存雪崩问题了。redis设置key永远有效的命令如下:
PERSIST key
缺点:会导致redis的空间资源需求变大。设置随机失效时间
如果key的失效时间不相同,就不会在同一时刻失效,这样就不会出现大量访问数据库的情况。
redis设置key有效时间命令如下:
Expire key
示例代码如下,通过RedisClient实现/** * 随机设置小于30分钟的失效时间 * @param redisKey * @param value */ private void setRandomTimeForReidsKey(String redisKey,String value){ //随机函数 Random rand = new Random(); //随机获取30分钟内(30*60)的随机数 int times = rand.nextInt(1800); //设置缓存时间(缓存的key,缓存的值,失效时间:单位秒) redisClient.setNxEx(redisKey,value,times); }使用二级缓存
二级缓存是使用两组缓存,1级缓存和2级缓存,同一个Key在两组缓存里都保存,但是他们的失效时间不同,这样1级缓存没有查到数据时,可以在二级缓存里查询,不会直接访问数据库。
示例代码如下:public static void main(String[] args) { CacheTest test = new CacheTest(); //从1级缓存中获取数据 String value = test.queryByOneCacheKey("key"); //如果1级缓存中没有数据,再二级缓存中查找 if(StringUtils.isBlank(value)){ value = test.queryBySecondCacheKey("key"); //如果二级缓存中没有,从数据库中查找 if(StringUtils.isBlank(value)){ value =test.getFromDb(); //如果数据库中也没有,就返回空 if(StringUtils.isBlank(value)){ System.out.println("数据不存在!"); }else{ //二级缓存中保存数据 test.secondCacheSave("key",value); //一级缓存中保存数据 test.oneCacheSave("key",value); System.out.println("数据库中返回数据!"); } }else{ //一级缓存中保存数据 test.oneCacheSave("key",value); System.out.println("二级缓存中返回数据!"); } }else { System.out.println("一级缓存中返回数据!"); } }异步更新缓存时间
每次访问缓存时,启动一个线程或者建立一个异步任务来,更新缓存时间。
示例代码如下:public class CacheRunnable implements Runnable { private ClusterRedisClientAdapter redisClient; /** * 要更新的key */ public String key; public CacheRunnable(String key){ this.key =key; } @Override public void run() { //更细缓存时间 redisClient.expire(this.getKey(),1800); } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } public static void main(String[] args) { CacheTest test = new CacheTest(); //从缓存中获取数据 String value = test.getFromCache("key"); if(StringUtils.isBlank(value)){ //从数据库中获取数据 value = test.getFromDb("key"); //将数据放在缓存中 test.oneCacheSave("key",value); //返回数据 System.out.println("返回数据"); }else{ //异步任务更新缓存 CacheRunnable runnable = new CacheRunnable("key"); runnable.run(); //返回数据 System.out.println("返回数据"); } }
3.小结
上面从服务不可用和key大面积失效两个方面,列举了几种解决方案,上面的代码只是提供一些思路,具体实施还要考虑到现实情况。当然也有其他的解决方案,我这里举例是比较常用的。毕竟现实情况,千变万化,没有最好的方案,只有最适用的方案。2.2 缓存穿透2.2.1 现象
缓存穿透是指当用户在查询一条数据的时候,而此时数据库和缓存却没有关于这条数据的任何记录,而这条数据在缓存中没找到就会向数据库请求获取数据。用户拿不到数据时,就会一直发请求,查询数据库,这样会对数据库的访问造成很大的压力。
2.2.2 异常原因非法调用2.2.3 解决方案
1.非法调用
可以通过缓存空值或过滤器来解决非法调用引起的缓存穿透问题。缓存空值
当缓存和数据库中都没有值时,可以在缓存中存放一个空值,这样就可以减少重复查询空值引起的系统压力增大,从而优化了缓存穿透问题。
示例代码如下:private String queryMessager(String key){ //从缓存中获取数据 String message = getFromCache(key); //如果缓存中没有 从数据库中查找 if(StringUtils.isBlank(message)){ message = getFromDb(key); //如果数据库中也没有数据 就设置短时间的缓存 if(StringUtils.isBlank(message)){ //设置缓存时间(缓存的key,缓存的值,失效时间:单位秒) redisClient.setNxEx(key,null,60); }else{ redisClient.setNxEx(key,message,1800); } } return message; }
缺点:大量的空缓存导致资源的浪费,也有可能导致缓存和数据库中的数据不一致。布隆过滤器
布隆过滤器由布隆在 1970 年提出。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。是以空间换时间的算法。
布隆过滤器的实现原理是一个超大的位数组和几个哈希函数。
假设哈希函数的个数为 3。首先将位数组进行初始化,初始化状态的维数组的每个位都设置位 0。如果一次数据请求的结果为空,就将key依次通过 3 个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为 1。当数据请求再次发过来时,用同样的方法将 key 通过哈希映射到位数组上的 3 个点。如果 3 个点中任意一个点不为 1,则可以判断key不为空。反之,如果 3 个点都为 1,则该KEY一定为空。
缺点:
可能出现误判,例如 A 经过哈希函数 存到 1、3和5位置。B经过哈希函数存到 3、5和7位置。C经过哈希函数得到位置 3、5和7位置。由于3、5和7都有值,导致判断A也在数组中。这种情况随着数据的增多,几率也变大。
布隆过滤器没法删除数据。布隆过滤器增强版
增强版是将布隆过滤器的bitmap更换成数组,当数组某位置被映射一次时就+1,当删除时就-1,这样就避免了普通布隆过滤器删除数据后需要重新计算其余数据包Hash的问题,但是依旧没法避免误判。布谷鸟过滤器
但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。不同于布谷鸟的是,布谷鸟哈希算法会帮这些受害者(被挤走的蛋)寻找其它的窝。因为每一个元素都可以放在两个位置,只要任意一个有空位置,就可以塞进去。所以这个伤心的被挤走的蛋会看看自己的另一个位置有没有空,如果空了,自己挪过去也就皆大欢喜了。但是如果这个位置也被别人占了呢?好,那么它会再来一次「鸠占鹊巢」,将受害者的角色转嫁给别人。然后这个新的受害者还会重复这个过程直到所有的蛋都找到了自己的巢为止。缺点:
如果数组太拥挤了,连续踢来踢去几百次还没有停下来,这时候会严重影响插入效率。这时候布谷鸟哈希会设置一个阈值,当连续占巢行为超出了某个阈值,就认为这个数组已经几乎满了。这时候就需要对它进行扩容,重新放置所有元素。
2.小结
以上方法虽然都有缺点,但是可以有效的防止因为大量空数据查询导致的缓存穿透问题,除了系统上的优化,还要加强对系统的监控,发下异常调用时,及时加入黑名单。降低异常调用对系统的影响。2.3 缓存击穿2.3.1 现象
key中对应数据存在,当key中对应的数据在缓存中过期,而此时又有大量请求访问该数据,缓存中过期了,请求会直接访问数据库并回设到缓存中,高并发访问数据库会导致数据库崩溃。redis的高QPS特性,可以很好的解决查数据库很慢的问题。但是如果我们系统的并发很高,在某个时间节点,突然缓存失效,这时候有大量的请求打过来,那么由于redis没有缓存数据,这时候我们的请求会全部去查一遍数据库,这时候我们的数据库服务会面临非常大的风险,要么连接被占满,要么其他业务不可用,这种情况就是redis的缓存击穿。
2.3.2 异常原因
热点KEY失效的同时,大量相同KEY请求同时访问。2.3.3 解决方案
1.热点key失效设置永不失效
如果所有的key都设置不失效,不就不会出现因为KEY失效导致的缓存雪崩问题了。redis设置key永远有效的命令如下:
PERSIST key
缺点:会导致redis的空间资源需求变大。设置随机失效时间
如果key的失效时间不相同,就不会在同一时刻失效,这样就不会出现大量访问数据库的情况。
redis设置key有效时间命令如下:
Expire key
示例代码如下,通过RedisClient实现/** * 随机设置小于30分钟的失效时间 * @param redisKey * @param value */ private void setRandomTimeForReidsKey(String redisKey,String value){ //随机函数 Random rand = new Random(); //随机获取30分钟内(30*60)的随机数 int times = rand.nextInt(1800); //设置缓存时间(缓存的key,缓存的值,失效时间:单位秒) redisClient.setNxEx(redisKey,value,times); }使用二级缓存
二级缓存是使用两组缓存,1级缓存和2级缓存,同一个Key在两组缓存里都保存,但是他们的失效时间不同,这样1级缓存没有查到数据时,可以在二级缓存里查询,不会直接访问数据库。
示例代码如下:public static void main(String[] args) { CacheTest test = new CacheTest(); //从1级缓存中获取数据 String value = test.queryByOneCacheKey("key"); //如果1级缓存中没有数据,再二级缓存中查找 if(StringUtils.isBlank(value)){ value = test.queryBySecondCacheKey("key"); //如果二级缓存中没有,从数据库中查找 if(StringUtils.isBlank(value)){ value =test.getFromDb(); //如果数据库中也没有,就返回空 if(StringUtils.isBlank(value)){ System.out.println("数据不存在!"); }else{ //二级缓存中保存数据 test.secondCacheSave("key",value); //一级缓存中保存数据 test.oneCacheSave("key",value); System.out.println("数据库中返回数据!"); } }else{ //一级缓存中保存数据 test.oneCacheSave("key",value); System.out.println("二级缓存中返回数据!"); } }else { System.out.println("一级缓存中返回数据!"); } }异步更新缓存时间
每次访问缓存时,启动一个线程或者建立一个异步任务来,更新缓存时间。
示例代码如下:public class CacheRunnable implements Runnable { private ClusterRedisClientAdapter redisClient; /** * 要更新的key */ public String key; public CacheRunnable(String key){ this.key =key; } @Override public void run() { //更细缓存时间 redisClient.expire(this.getKey(),1800); } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } public static void main(String[] args) { CacheTest test = new CacheTest(); //从缓存中获取数据 String value = test.getFromCache("key"); if(StringUtils.isBlank(value)){ //从数据库中获取数据 value = test.getFromDb("key"); //将数据放在缓存中 test.oneCacheSave("key",value); //返回数据 System.out.println("返回数据"); }else{ //异步任务更新缓存 CacheRunnable runnable = new CacheRunnable("key"); runnable.run(); //返回数据 System.out.println("返回数据"); } }分布式锁
使用分布式锁,同一时间只有1个请求可以访问到数据库,其他请求等待一段时间后,重复调用。
示例代码如下:/** * 根据key获取数据 * @param key * @return * @throws InterruptedException */ public String queryForMessage(String key) throws InterruptedException { //初始化返回结果 String result = StringUtils.EMPTY; //从缓存中获取数据 result = queryByOneCacheKey(key); //如果缓存中有数据,直接返回 if(StringUtils.isNotBlank(result)){ return result; }else{ //获取分布式锁 if(lockByBusiness(key)){ //从数据库中获取数据 result = getFromDb(key); //如果数据库中有数据,就加在缓存中 if(StringUtils.isNotBlank(result)){ oneCacheSave(key,result); } }else { //如果没有获取到分布式锁,睡眠一下,再接着查询数据 Thread.sleep(500); return queryForMessage(key); } } return result; }
2.小结
除了以上解决方法,还可以预先设置热门数据,通过一些监控方法,及时收集热点数据,将数据预先保存在缓存中。3 总结
Redis缓存在互联网中至关重要,可以很大的提升系统效率。 本文介绍的缓存异常以及解决思路有可能不够全面,但也提供相应的解决思路和代码大体实现,希望可以为大家提供一些遇到缓存问题时的解决思路。如果有不足的地方,也请帮忙指出,大家共同进步。
真金白银!多地出台生育支持政策今年以来,已有多地陆续推进一系列积极生育支持措施,如发放生育津贴和育儿补贴,加强住房保障支持等。其中,云南山东济南湖南长沙辽宁沈阳黑龙江哈尔滨等多地发放真金白银。资料图。中新社记者
如何正确给婴儿拍嗝?这些知识建议收藏到底什么时候拍嗝最好?拍嗝的正确姿势又是怎样的?经常拍不出嗝又该如何处理?希望这些内容能让各位新手父母减少对拍嗝的焦虑,更自信地照顾baby01hr拍嗝的原因新生儿容易吐奶的原因在
2022年度中国电竞产业年会在深圳举办南方网讯(记者杨智明)为充分挖掘电子竞技的正向价值,展现电子竞技在经济文化城市发展中的重要作用,由中国音像与数字出版协会深圳市南山区人民政府共同主办,主题为数引新未来,竞创新生态的
王者最讨厌,烦人的5个辅助英雄王者一共一百多个英雄,每个位置都有很讨厌的英雄,就是当对面有这些英雄的时候直接操作减半,非常让人难受,不管会不会玩都很限制就对了。今天就来讲讲最被人讨厌的5个游走英雄。一东皇太一对
多彩活动迎接二月二多彩活动迎接二月二新华网2月20日,浙江省台州市仙居县迎晖幼儿园的孩子们在二月二龙抬头活动中表演舞龙。2月20日,江西省吉安市吉州区保育院的孩子们在二月二龙抬头活动上参加拔河比赛。
电商导购CPS,京东联盟如何跟单实现用户和订单绑定前言做过自媒体的小伙伴都知道,不管是发图文还是发短视频,直播也好,可以带货。在你的内容里面挂上商品,你自己都不需要囤货,如果用户通过这个商品下单成交了,自媒体平台就会给你佣金。问题
年亏15亿美元,还想讲崛起故事,越南新能源绷不住了1曾几何时,造车是发达国家的专利。可如今,越南印度印尼这些工业基础极差的国家也掺和了进来,扯下了新能源车企的最后一块遮羞布。上周,我的一位好友发了条朋友圈,感叹越南发展神速,他们的
全力拼经济奋战开门红丨抢订单拼环境许昌火力全开大河网讯(记者祝传鹏)还未走进许昌安彩新能科技有限公司车间的大门,就感受到一股热浪袭来。站在六七米远的地方,只见火红的玻璃液不断从熔窑缓缓流出并压延成型,生产线上工业机器人精细作业
拿钱不出力,你小子是懂不劳而获的谈到科怀伦纳德,大家或许会想到,他在季后赛中将库里与詹姆斯防到崩溃,在抢七比赛中将恩比德打哭,是猛龙队史首冠的奠基人,可在最近两个赛季他是球迷口中的轮休第一人。据数据统计,自他加盟
向海向阳向未来!2023仙境海岸海阳马拉松启动京报体育记者陈嘉堃2月22日,2023仙境海岸海阳马拉松新闻发布会于北京国家会议中心召开。第五届仙境海岸海阳马拉松将于2023年5月21日7时30分在海阳旅游度假区鸣枪开跑。本项赛
真正有钱的人,大多有以下特征前言一个人有没有钱,实际上还是非常明显的,不管怎么伪装都装不出来的,因此,有些事情我们一定要放在心上,只有这样才能够让自己的生活幸福在更多的时候,我们需要把事情考虑的清清楚楚,脚踏
装修工人触电身亡,谁来担责?装修工人是在从事装修工作中发生的事故还是不在工作期间发生的呢?装修工人在从职于谁工作?如果你是他老板,那肯定是你担责啊,你是责任主体。如果你是业主,装修工人是你找来干活的的话,出了
上班如上坟,但上有老下有小不敢辞职,天天难受,早晚抑郁怎么办?看完题目,我想作者应该在工作中遇到了不顺心的事情,比如遭到同事的排挤,才会有这样沉闷的心情。鉴于提问者上班很难受,还有得抑郁症的倾向。我建议你调整心态,如果实在不行,那就辞职重新找
房价会不会跌?房价还要继续大涨的城市,1,广州市,在校大学生人口156万,广州市总人口是1883万,12个人里面就有1个大学生,广州的从化区,房价只有1。13万一平米花都区,房价只要1。39万一
如何看待很多单位存在的同工不同酬现象?某市直单位通过编外聘用了3个驾驶员,用来开单位公务用车。单位同时也有1个工勤编制内的老同志,活没有聘用驾驶员干的多。都是驾驶员岗位,编制内的和聘用的待遇几乎相差一倍。这就是同工不同
退休后,除了养老金,还有哪些钱可以领?在广东除了支取养老金的还有七十八十九十一百岁的老人每月分别可补助501003001000元。退休以后,除了养老金以外,还可以拿到6笔钱。一是独生子女退休补助费二是住房公积金三是企业
安徽公安职业学院怎么样?入警率怎么样?安徽公安职业学院是由安徽省公安厅主管的全省唯一一所全日制公安高等院校。也就是说,这学校是省级公安部门唯一的嫡系学校,虽然校名为公安职业学院,但是与其它省的警察学院基本是相同性质的警
江苏省滨海县出过哪些名人?崇敬军人,宣传将军。介绍几位江苏滨海县籍解放军将军,他们是朱文泉,江苏响水人(原隶属滨海县)。上将军衔。1942年10月生,1961年8月入伍,1964年5月入党,大学学历,历任任
一旦老人出现哪些征兆,他们时间所剩不多了,为什么?父母,终将会老去,虽说这是大自然的规律,但是作为子女,还是应该好好的珍惜父母在世的时光,尽最大的可能报答父母,以免将来父母走的时候,抱憾终身。如果我们仔细观察,当老人出现如下症状的
你见过有人因为做尽坏事而得到报应的吗?哈尔滨南岗区的王立华是个包工头子,这些年是大发特发了,高楼买了,豪车也置办了,但是没有那个命享受!说起他的发家史,那是件件都带着工人的血和泪,从10年前开始,他就通过关系承包工地的
现在5000元的工资,相当于80年代多少钱?我们以上海为例来算一算。80年代上海的物价如下1工资,约50元每月,现在5000元的工资,仅相当于80年代的50元。2大米,约0。15元一斤,现在的5000元可以买2000斤大米,
退休金7500在山东泰安市是什么水平?当然是高薪水平,泰安的收入水平在省内也就是中等偏下一些的位置,最常见的工资水平就在20003500之间,这个收入区间的群体占比至少60,月薪5000以下的占比超75。像题主这个收入