在实际项目中你是否会有以下疑问?

如何访问Redis中的海量数据,却不影响其他要求访问Redis?Redis中有百万/千万数据,如何高效访问?Redis中数据量太大,如何既担保快速访问,又不至于使做事宕机?

以上问题亦是Redis口试的高频问题。

2、思考

Q1:为什么Redis中的数据量很大时,某些数据操作会导致Redis卡顿,乃至宕机?

phphscan玩转Redis若何高效拜访Redis中的海量数据 JavaScript

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:迭代返回数量;

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示例 @zxiaofan127.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 11) "24"2) (empty list or set)127.0.0.1:6378> SSCAN sscantest 24 MATCH ? COUNT 11) "20"2) (empty list or set)127.0.0.1:6378> SSCAN sscantest 0 MATCH COUNT 11) "24"2) 1) "sscantest:3" 2) "sscantest:2a"127.0.0.1:6378> SSCAN sscantest 24 MATCH COUNT 11) "20"2) 1) "a1"

4.3、HSCAN示例

// HSCAN示例 @zxiaofan127.0.0.1:6378> HMSET hscantest hscantest:1 1 hscantest:2 2 hscantest:3 3 hscantest:4 4 hscantest:1a 1a hscantest:2a 2a hscantest:1ab 1ab hscantest:a1 a1 hscantest:aa1 aa1 OK127.0.0.1:6378> HSCAN hscantest 0 MATCH hscantesta COUNT 201) "0"2) 1) "hscantest:1a" 2) "1a" 3) "hscantest:2a" 4) "2a"127.0.0.1:6378> HSCAN hscantest 0 MATCH hscantesta COUNT 21) "0"2) 1) "hscantest:1a" 2) "1a" 3) "hscantest:2a" 4) "2a"127.0.0.1:6378>

  从HSCAN示例可以看出,纵然count参数为2,也返回了所有匹配的结果。
这便是先条件到的,数据量较小时,直接返回所有数据。

4.4、ZSCAN示例

// ZSCAN示例 @zxiaofan// 【移除】并弹出count个分数最大的元素,count默认为1127.0.0.1:6378> ZPOPMAX zscantest 20 1) "sscantest:1ab" 2) "6" 3) "sscantest:2a" 4) "5" 5) "sscantest:1a" 6) "4" 7) "sscantest:3" 8) "3" 9) "zscantest:1"10) "2"11) "sscantest:2"12) "2"13) "test1"14) "1"15) "sscantest:1"16) "1"127.0.0.1:6378> ZPOPMAX zscantest 20(empty list or set)127.0.0.1:6378> ZADD zscantest 1 zscantest:1 2 zscantest:2 3 zscantest:3 4 zscantest:1a 5 zscantest:2a 6 zscantest:1ab 7 zscantest:a1 8 zscantest:aa1(integer) 8// NX:不存在才添加;CH:返回被改变(含新增)的元素个数127.0.0.1:6378> ZADD zscantest NX CH 1 test1 2 zscantest:1(integer) 1127.0.0.1:6378> ZSCAN zscantest 0 MATCH a COUNT 51) "0"2) 1) "zscantest:1a" 2) "4" 3) "zscantest:2a" 4) "5"127.0.0.1:6378> 5、总结

5.1、看看口试时你能答上几个问题

SCAN迭代可以并发吗?SCAN返回数据为空便是迭代结束了吗?如果首次迭代cursor参数不是0,能实现完全迭代吗?可以严格掌握每次迭代返回的数据量吗?迭代返回的数据一定完全吗?为什么迭代返回的元素列表可能为空?

5.2、部分问题解答

5.2.1、SCAN返回数据为空便是迭代结束了吗

// SCAN返回数据为空便是迭代结束了吗? @zxiaofan127.0.0.1:6378> keys k?1) "k1"2) "k2"127.0.0.1:6378> SCAN 0 MATCH k?1) "88"2) (empty list or set)127.0.0.1:6378> SCAN 88 MATCH k?1) "34"2) 1) "k1"127.0.0.1:6378> SCAN 34 MATCH k?1) "122"2) (empty list or set)127.0.0.1:6378> SCAN 122 MATCH k?1) "14"2) (empty list or set)127.0.0.1:6378> SCAN 14 MATCH k?1) "33"2) (empty list or set)127.0.0.1:6378> SCAN 33 MATCH k?1) "53"2) (empty list or set)127.0.0.1:6378> SCAN 53 MATCH k?1) "93"2) (empty list or set)127.0.0.1:6378> SCAN 93 MATCH k?1) "107"2) 1) "k2"127.0.0.1:6378> SCAN 107 MATCH k?1) "79"2) (empty list or set)127.0.0.1:6378> SCAN 79 MATCH k?1) "0"2) (empty list or set)127.0.0.1:6378>

  看上述示例,匹配“k?”的数据实际有2条“k1”、“k2”,在全体迭代过程中,多次返回数据为空,但是迭代未曾结束(由于“k1”、“k2”没有全部迭代返回)。
  以是,只有当游标返回为0时,才能解释迭代结束了。

5.2.2、如果首次迭代cursor参数不是0,能实现完全迭代吗?

// 如果首次迭代cursor参数不是0,能实现完全迭代吗? @zxiaofan127.0.0.1:6378> keys k?1) "k1"2) "k2"127.0.0.1:6378> SCAN 66 MATCH k?1) "122"2) (empty list or set)127.0.0.1:6378> SCAN 122 MATCH k?1) "14"2) (empty list or set)127.0.0.1:6378> SCAN 14 MATCH k?1) "33"2) (empty list or set)127.0.0.1:6378> SCAN 33 MATCH k?1) "53"2) (empty list or set)127.0.0.1:6378> SCAN 53 MATCH k?1) "93"2) (empty list or set)127.0.0.1:6378> SCAN 93 MATCH k?1) "107"2) 1) "k2"127.0.0.1:6378> SCAN 107 MATCH k?1) "79"2) (empty list or set)127.0.0.1:6378> SCAN 79 MATCH k?1) "0"2) (empty list or set)127.0.0.1:6378>

  看上述示例,匹配“k?”的数据实际有2条“k1”、“k2”,当第一次SCAN利用cursor为66,我们可以创造经由多次迭代,游标返回为0时,“k1”一贯未曾被迭代返回。
  以是,如果首次迭代cursor参数不是0,不能实现完全迭代。

  完全迭代必须是游标从0开始,游标到0结束。

6、后记

  本文针对Redis的SCAN系列命令做了详细的比拟剖析以及实际利用示例,并整理了口试中的高频问题。
建议阅读本文的同学实际动手练习下,效果更好。

作者:zxiaofan链接:https://juejin.im/post/5dd10fde518825291f38e7a8