误用Redis命令导致服务器挂了,领导让我写事故报告
大家肯定用过Redis,也知道Redis的命令以及用法,但是假如在某些场景下,误用了一些命令,后果会非常严重,所以要坚决杜绝这样的事情发生,由于我自己之前误用过,所以事故报告它来了!
前言
我相信大家都猜到了这个导致服务器挂的命令是什么,没错,他就是 “keys” 命令。误用这个Redis命令的场景是:
由于业务需要,会定时更新一批缓存的数据,但是一个个获取key效率低下,所以就想到了批量获取的思路,然后就使用了keys命令,在本地或者测试环境下,由于缓存中key的数量并不是那么多,所以没有出现缓存挂或者服务器宕机的情况,但是随着历史数据的增加和业务的增长,缓存中的key越来越多,达到了几百万甚至上千万,所以使用keys命令的时候,查询出来的符合查询规则的数据量也非常大,导致服务器阻塞,随后宕机!
解析KEYS命令
先来看下官网的介绍
查找所有符合给定模式pattern(正则表达式)的 key 。
时间复杂度为O(N),N为数据库里面key的数量。
例如,Redis在一个有1百万个key的数据库里面执行一次查询需要的时间是40毫秒 。
警告: KEYS
的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 KEYS
, 你最好还是用 Redis 的集合结构 SETS 来代替。
支持的正则表达模式:
h?llo
匹配hello
,hallo
和hxllo
h*llo
匹配hllo
和heeeello
h[ae]llo
匹配hello
和hallo,
但是不匹配hillo
h[^e]llo
匹配hallo
,hbllo
, … 但是不匹配hello
h[a-b]llo
匹配hallo
和hbllo
如果你想取消字符的特殊匹配(正则表达式,可以在它的前面加\
。
返回值
array-reply: 所有符合条件的key
例子:
redis> MSET one 1 two 2 three 3 four 4
OK
redis> KEYS *o*
1) "four"
2) "one"
3) "two"
redis> KEYS t??
1) "two"
redis> KEYS *
1) "four"
2) "three"
3) "one"
4) "two"
redis>
可以看到官网在介绍keys的时候从优点和缺点都给出了信息,优点就是在数据量没那么大的时候,keys的效率确实非常高,但是缺点也很明显,那就是会影响服务器的性能,导致服务器阻塞,进而影响其他服务的使用
了解了keys命令之后,Garnett确实也不推荐搭建使用,所以这里我们就不深入去研究keys命令了,那么不推荐使用这个,有什么替代方案吗,当然有了,那就是SCAN命令
解析SCAN命令
先来看下官网的介绍
SCAN 命令及其相关的 SSCAN, HSCAN 和 ZSCAN 命令都用于增量迭代一个集合元素。
SCAN 命令用于迭代当前数据库中的key集合。
SSCAN 命令用于迭代SET集合中的元素。
HSCAN 命令用于迭代Hash类型中的键值对。
ZSCAN 命令用于迭代SortSet集合中的元素和元素对应的分值
以上列出的四个命令都支持增量式迭代,它们每次执行都只会返回少量元素,所以这些命令可以用于生产环境,而不会出现像 KEYS 或者 SMEMBERS 命令带来的可能会阻塞服务器的问题。
不过,SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于SCAN这类增量式迭代命令来说,有可能在增量迭代过程中,集合元素被修改,对返回值无法提供完全准确的保证。
官网再次提到了在使用KEYS时候的缺点,所以还是特别要重视的
SCAN命令的基本用法
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程
当SCAN命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
以下是一个 SCAN 命令的迭代过程示例 :
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"2) "key:8"3) "key:4"4) "key:14"5) "key:16"6) "key:17"7) "key:15"8) "key:10"9) "key:3"10) "key:7"11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"2) "key:18"3) "key:0"4) "key:2"5) "key:19"6) "key:13"7) "key:6"8) "key:9"9) "key:11"
在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。第二次迭代使用的是第一次迭代时返回的游标 17 ,作为新的迭代参数 。
显而易见,SCAN命令的返回值 是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。
在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集已经被完整遍历过了。
full iteration :以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历。
SCAN命令参数解析
SCAN cursor [MATCH pattern] [COUNT count]
1.scan 命令提供三个参数,第一个是cursor,第二个是要匹配的正则,第三个是单次遍历的槽位
若选择了可选参数 MATCH XXX ,则按照正则返回匹配的keys
若选择了可选参数COUNT XXX,则设置每次迭代返回的数量,默认为10
SCAN在代码中的用法
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
String key = "PLFX-ZZSFP-*";
ScanParams scanParams = new ScanParams();
scanParams.match(key);// 匹配以 PLFX-ZZSFP-* 为前缀的 key
scanParams.count(1000);
while (true){//使用scan命令获取数据,使用cursor游标记录位置,下次循环使用ScanResult<String> scanResult = jedis.scan(cursor, scanParams);cursor = scanResult.getStringCursor();// 返回0 说明遍历完成List<String> list = scanResult.getResult();long t1 = System.currentTimeMillis();for(int m = 0;m < list.size();m++){String mapentry = list.get(m);System.out.println(mapentry);//jedis.del(key, mapentry);}long t2 = System.currentTimeMillis();System.out.println("获取" + list.size()+ "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor);if ("0".equals(cursor)){break;}
}
通过不断的移动游标直到全部数据获取完成
在这里使用的是jedis单机的场景,假如使用redis集群的话,使用jedisCluster,不能直接使用,需要获取每个jedis的实例,然后一个个获取,以下是代码:
Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
for (Map.Entry<String, JedisPool> entry : clusterNodes.entrySet()) {//获取单个的jedis对象Jedis jedis = entry.getValue().getResource();// 判断非从节点(因为若主从复制,从节点会跟随主节点的变化而变化),此处要使用主节点从主节点获取数据if (!jedis.info("replication").contains("role:slave")) {List<String> keys = getScan(jedis, matchKey);if (keys.size() > 0) {Map<Integer, List<String>> map = new HashMap<>(8);//接下来的循环不是多余的,需要注意for (String key : keys) {// cluster模式执行多key操作的时候,这些key必须在同一个slot上,不然会报:JedisDataException:int slot = JedisClusterCRC16.getSlot(key);// 按slot将key分组,相同slot的key一起提交if (map.containsKey(slot)) {map.get(slot).add(key);} else {List<String> list1 = new ArrayList();list1.add(key);map.put(slot, list1);}}for (Map.Entry<Integer, List<String>> integerListEntry : map.entrySet()) {list.addAll(integerListEntry.getValue());}}}
}public static List<String> getScan(Jedis redisService, String key) {List<String> list = new ArrayList<>();//扫描的参数对象创建与封装ScanParams params = new ScanParams();params.match(key);//扫描返回一百行,这里可以根据业务需求进行修改params.count(100);String cursor = "0";ScanResult scanResult = redisService.scan(cursor, params);//scan.getStringCursor() 存在 且不是 0 的时候,一直移动游标获取while (null != scanResult.getStringCursor()) {//封装扫描的结果list.addAll(scanResult.getResult());if (! "0".equals( scanResult.getStringCursor())) {scanResult = redisService.scan(cursor, params);} else {break;}}return list;
}
总结
1)KEYS 的算法采用O(N)复杂度的遍历算法,没有limit限制,一次性遍历所有key,属于暴力搜索。假如redis服务器存在千万级别的key数量,但是又由于低版本的redis为单线程,那么如果执行keys命令,将会造成卡顿,一段时间内无法处理其他命令,造成其他客户端阻塞。所以生产环境不建议使用。
2)SCAN命令支持增量式迭代, 它们每次执行都只会返回少量元素, 所以这些命令可以用于生产环境, 而不会出现像KEYS 命令、 SMEMBERS命令带来的问题 —— 当 KEYS命令被用于处理一个大的数据库时, 又或者 SMEMBERS用于处理一个大的集合键时, 它们可能会阻塞服务器达数秒之久。
综上:
keys可一次性返回我们想要的所有key,但是若key的数量级比较大会造成阻塞
scan可分次返回匹配的key,不会造成阻塞,但是返回的key可能有重复,客户端需要根据需要进行去重
本文从实际场景出发,了解使用redis批量获取key的命令,解析keys和scan的作用和区别,所以在使用这些命令的时候,一定要仔细考察业务场景,合理使用。
假如面试中你被问到这些,我相信你看了这篇一定能拨动面试官的心!
希望你们是我最好的观众!
乐于输出干货的Java技术公众号:Garnett的Java之路。公众号内有大量的技术文章、海量视频资源、精美脑图,不妨来关注一下!回复【资料】领取大量学习资源和免费书籍!
觉得有点东西就点一下“赞和在看”吧!感谢大家的支持了!
误用Redis命令导致服务器挂了,领导让我写事故报告相关推荐
- 由于Redis后门漏洞导致服务器被注入挖矿脚本解决过程
由于Redis后门漏洞导致服务器被注入挖矿脚本解决过程 事件描述 某一天的早晨,我还是像往常一样搭着公交车开启打工仔的一天,一早8.30就到办公室了,坐着玩手机等上班,就这这时突然我组长飞快的回来办公 ...
- redis 命令 释放连接_redis scan命令导致redis连接耗尽,线程上锁的解决
使用redis scan方法无法获取connection,导致线程锁死. 0.关键字 redis springboot redistemplate scan try-with-resource 1.异 ...
- 一个致命的 Redis 命令,导致公司损失 400 万
转载自 一个致命的 Redis 命令,导致公司损失 400 万 最近安全事故濒发啊,前几天发生了<顺丰高级运维工程师的删库事件>,今天又看到了 PHP 工程师在线执行了 Redis 危险 ...
- 18. Redis 管理命令-查看服务器状态
Redis 提供了info 命令, 可以查看Redis 服务器的相关信息. 1. info 命令格式 info 命令可以查询redis 服务器的相关信息, 默认查看全部的信息.也可以查看具体某部分信息 ...
- redis命令详解与使用场景举例——Server(服务器)
BGREWRITEAOF 执行一个 AOF文件 重写操作.重写会创建一个当前 AOF 文件的体积优化版本. 即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 B ...
- linux 关闭redis 命令_linux关闭redis命令 redis配置redis的服务器启动和关闭 - Redis - 服务器之家...
linux关闭redis命令 redis配置redis的服务器启动和关闭 发布时间:2017-04-13 来源:服务器之家 # chkconfig: 2345 10 90 # description: ...
- Redis获取客户端 服务器信息常用命令
AUTH password 通过设置配置文件中 requirepass 项的值(使用命令 CONFIG SET requirepass password ),可以使用密码来保护 Redis 服务器. ...
- redis命令,SpringBoot整合Redis6,主从复制,哨兵模式,集群,springCache初高级应用。
目录 1. Docker安装Redis 2. Redis的基础 2.1 redis的key命令 2.2 reids的数据结构(6.0新增的数据结构) 1. String(字符串)类型 2. List( ...
- 不到 10 个提升逼格的 Redis 命令
keys 我把这个命令放在第一位,是因为笔者曾经做过的项目,以及一些朋友的项目,都因为使用keys这个命令,导致出现性能毛刺.这个命令的时间复杂度是O(N),而且redis又是单线程执行,在执行key ...
最新文章
- GO语言教程1:Linux--debian/ubuntu下Go语言的安装
- AI芯片浪潮:创新企业造芯抢夺物联网时代制高点
- MFC 基础知识:对话框背景添加图片和按钮Button添加图片
- 万能素材库_自媒体运营必备3款黑科技工具,一个万能素材网站,你都在用吗?...
- db2存储过程 可以使用游标循环嵌套吗_DB2存储过程使用动态游标的例子
- html播放切片,[Html/Css]网页切片
- 二进制安装kubernetes v1.11.2 (第十章 kube-scheduler集群部署)
- Arcgis学习笔记(二)投影和定义投影
- javascript中文乱码问题分析及解决方案
- import java.io 是什么意思_Java IO 详解
- Linux下把ncsi设置成OCP模式,一种支持NCSI信号管理功能自动切换的电路及服务器的制作方法...
- linux系统tfs安装,Jenkins使用TFS部署
- 关于同构关系的一些证明(1)
- Hadoop MR 分区(partition)和全排序(WritableComparable)
- 【开发指南】Spring Cloud集成POI完成Excel读写操作
- GitHub上常用第三方库
- python 音速_Python:在播放过程中更改音速
- idc机房建设费用_数据中心机房收费标准
- 计算最大公约数(GCD)
- 【转载】Cmd Markdown 数学公式指导手册
热门文章
- python自动排课表_利用python爬取广西科技大学教务管理信息系统班级课表
- 阿朱看中国企业信息化发展方向
- 随然响应式导航网址目录主题 4.0.0 站长导航网址程序源码 全局SEO zblog博模板源码
- 香农公式--通信的浅显理解--单纯只是为了弄懂功率和信道容量的关系
- 微信小程序城市列表构建
- Matplotlib 数据可视化(读书笔记)
- 第四课 产品经理的能力知识结构1 - 自我管理能力
- AdvantEdge-刀具材料和涂层分析
- 共享单车调度_共享单车调度模型及算法研究
- 视频教程-Project-管理项目(进阶)-Office/WPS