1、前言
Redis以高性能著称,但性能再好,在面对海量数据时,若不正确的使用,也终将会有性能瓶颈,甚至造成服务宕机。
在实际项目中你是否会有以下疑问?
- 如何访问Redis中的海量数据,却不影响其他请求访问Redis?
- Redis中有百万/千万数据,如何高效访问?
- Redis中数据量太大,如何既保证快速访问,又不至于使服务宕机?
以上问题亦是Redis面试的高频问题。
《玩转Redis》系列文章主要讲述Redis的基础及中高级应用,文章基于Redis5.0.4+,欢迎前往CSDN、订阅号、开源中国、掘金等平台搜索【zxiaofan】查看系列文章。
2、思考
Q1:为什么Redis中的数据量很大时,某些数据操作会导致Redis卡顿,甚至宕机?
A1:Redis是单线程服务,所有指令都是顺序执行,当某一指令耗时很长时,就会阻塞后续的指令执行。当被积压的指令越来越多时,Redis服务占用CPU将不断升高,最终导致Redis实例崩溃甚至服务器宕机。
Q2:利用万能的keys命令查询任何想查的数据?
A2:自己电脑几万条数据玩玩就好了,线上使用keys命令,Excuse me?你想卷铺盖走人了吧。
++“某公司php工程师执行redis keys * 导致数据库宕机!
技术部发生2起本年度PO级特大事故,造成公司资金损失400万。”++ 这条新闻记忆犹新,警钟长鸣!
Q3:Redis中海量数据的正确操作方式
A3:利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成数据迭代。
Redis的【SCAN系列命令】你了解多少呢?
3、SCAN系列命令详解
SCAN系列命令,并不单纯指代SCAN命令,还包含SSCAN、HSCAN、ZSCAN,每种命令操作对象是有区别的,但用法及功能基本相同。
3.1、SCAN系列命令对比分析
- cursor:迭代游标;
- MATCH:数据匹配模式;
- COUNT:迭代返回数量;
命令 | 功能 | 参数 | 返回值 |
---|---|---|---|
SCAN | 基于游标迭代DB | cursor [MATCH pattern] [COUNT count] | 返回数组,第一个值是下一次迭代的游标(无符号64bit),第2个值是元素列表(key列表) |
SSCAN | 基于游标迭代Sets | key cursor [MATCH pattern] [COUNT count] | 返回数组,第一个值是下一次迭代的游标(无符号64bit),第2个值是元素列表 |
HSCAN | 基于游标迭代Hashes | key cursor [MATCH pattern] [COUNT count] | 返回数组,第2个值是field-value列表 |
ZSCAN | 基于游标迭代ZSets | key cursor [MATCH pattern] [COUNT count] | 返回数组,第2个值是member-score列表 |
3.2、SCAN系列命令注意事项
- SCAN的参数没有key,因为其迭代对象是DB内数据;
- 返回值都是数组,第一个值都是下一次迭代游标;
- 时间复杂度:每次请求都是O(1),完成所有迭代需要O(N),N是元素数量;
- 可用版本:version >= 2.8.0;
3.3、SCAN系列命令详解
3.3.1、 增量迭代,可用于生产环境
- 并不像KEYS、SMEMBERS一样是全量迭代,对大集合执行时可能阻塞服务很长时间;
3.3.2、不保证准确结果
- SMEMBERS可以返回整个set的元素,而SCAN这类增量迭代命令可能出现迭代过程中元素被改变,所以并不能保证准确的返回结果;
3.3.3、基于游标迭代
- SCAN基于游标迭代,每次请求将返回下一次需要使用的游标;
- 游标cursor可以比DB元素总量大,可以为负数;
- 错误游标:使用间断(不是迭代返回的)、负数、超出范围或其他非法游标,迭代不会报错,可能产生未定义行为(无法保证准确性);
3.3.4、迭代结束标记
- SCAN返回的游标不一定递增,某次迭代返回的元素数量可能为0;
- 返回元素列表为空,不代表迭代结束;
- 一个完整的迭代:SCAN游标从0开始,返回游标为0结束;
- 迭代状态由返回的游标控制。可以并发执行迭代;可随时终止迭代;
3.3.5、迭代完整性
- 遍历开始到遍历结束一直存在的数据,一定能被迭代返回;
- 同一个元素可能返回多次,数据去重应由应用程序完成;
- 在迭代过程中增删的元素,可能返回,可能不返回;
- 当数据类型是sets(由integer组成)、hashes、sorted sets且集合较小时,迭代将返回整个集合的数据,与count无关;
- 迭代结束保证:元素添加速率小于迭代速率。
3.3.6、why有时迭代直接返回整个集合
- 底层数据结构是hash时,如果数据量较小,Redis有内存优化策略,会使用紧凑的压缩编码。此时SCAN操作并不是返回有意义的游标,而是迭代整个集合;
- 数据量较小?参见官方memory-optimization(内存优化)说明。
3.3.7、参数count说明
- count默认值是10;
- 数据集较大时,如果没有使用match,返回元素为count或比count略大;
- 每次迭代的count参数值可以不同,只要使用上次迭代返回的游标即可;
3.3.8、参数match说明
- 和keys的pattern类似;
- MATCH操作是在检索出数据到返回元素前的期间执行,所以如果被匹配的元素较少,那么可能多次迭代返回的元素列表均为空;
4、SCAN系列命令示例
4.1、SCAN示例
详见《5.2、部分问题解答》
4.2、SSCAN示例
// SSCAN示例 @zxiaofan
127.0.0.1:6378> SADD sscantest sscantest:1 1 sscantest:2 2 sscantest:3 3 sscantest:4 4 sscantest:1a 1a sscantest:2a 2a sscantest:1ab 1ab sscantest:a1 a1 sscantest:aa1 aa1
(integer) 0
// MATCH ?:无匹配数据
127.0.0.1:6378> SSCAN sscantest 0 MATCH ? COUNT 1
1) "24"
2) (empty l