redis—— 渐进式遍历
目录
为啥不用keys*遍历?
引入渐进式遍历
SCAN进行渐进式遍历
格式及参数说明
使用示例
注意
为啥不用keys*遍历?
之前学过key* 获取所有的key,但是这个操作可能会一次性得到太多的key,阻塞redis服务器,所以不建议在生产环境中使用。
性能问题:
KEYS *
命令需要遍历整个数据库中的所有键名,这对于大型的 Redis 数据库来说会非常耗时和资源消耗。阻塞问题:
KEYS *
命令会阻塞 Redis 服务器的主线程,这意味着在执行这个命令期间,Redis 将无法处理其他客户端的请求。安全问题:如果在生产环境中误操作使用
KEYS *
命令,可能会导致非常严重的安全问题。例如,可能会返回包含敏感信息的键名,或者删除一些重要的键值对
引入渐进式遍历
Redis渐进式遍历允许我们在不阻塞Redis服务器的情况下,逐步获取大量的数据。所谓渐进式,不是一次性把所有Key拿到,而是每当执行一次命令,只获取到其中的一部分(多次迭代,逐步获取)。这样保证当前这一次操作不会太卡。多执行几次渐进式遍历,就可以得到所有的key,相当于化整为零。
SCAN进行渐进式遍历
在Redis中,可以使用SCAN命令进行渐进式遍历。SCAN命令通过游标来记录当前遍历的位置,并返回一批键值对。我们可以使用返回的游标继续下一次遍历,直到完成遍历为止。
格式及参数说明
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] #pattern等同于key中的pattern通配符 #count限制这一次遍历能够获取到多少元素,默认是10;与mysql中的limit不同; #limit精确,redis对于count只是一个建议、提示,并且它和最后的结果也比一定一致
cursor
表示游标,用来记录当前遍历的位置,不能理解为下标,并不是一个连续递增的整数,仅仅是一个字符串,供服务器使用;MATCH
参数表示要匹配的键名模式;COUNT
参数表示每次遍历返回的最大元素个数,所以最后返回的结果条数并不一定与count值相等。COUNT这个里的数字,不是说每次遍历都得设置成一样。这- 其中执行命令的前半部分:下次遍历光标开始的位置.(这与传统的下标不同);后半部分:真正遍历到的key内容;
- 当命令返回的cursor回到0了,才说明遍历结束了。
使用示例
# 遍历整个数据库 SCAN 0 # 输出结果为:(10086, [key1, key2, key3, ...]) # 遍历数据库中匹配指定模式的键 SCAN 0 MATCH "prefix:*" # 输出结果为:(10086, [prefix:key1, prefix:key2, prefix:key3, ...]) # 分批遍历数据库中的键 SCAN 0 COUNT 10 # 输出结果为:(10086, [key1, key2, key3, ..., key10]) #使用SCAN命令从游标0开始,每次返回最多10个键值对。 #如果还有更多的数据需要遍历,返回的结果中会包含一个新的游标值,我们可以使用这个新的游标值进行下一次遍历。
# 第一次执行scan 0 127.0.0.1:6379> scan 0 1) "11" 2) 1) "user" 2) "a" 3) "test" 4) "user.name" 5) "hk" 6) "y" 7) "user.city" 8) "b" 9) "list1" 10) "c" # 使用新的cursor = '11',执行scan 11 127.0.0.1:6379> scan 11 1) "0" 2) 1) "list2" 2) "k" 3) "x"
运行命令和结果解释:
1、第一次执行scan 0 ,返回结果包括两部分,第一部分 11 就是下次执行scan命令需要的cursor参数,第二部分是返回的10个键。
2、第二次执行scan 11 ,得到的结果是 “0”,说明所有的键都已经被遍历过了。
如果还有更多的数据需要遍历,返回的结果中会包含一个新的游标值,我们可以使用这个新的游标值进行下一次遍历。
需要注意, 由于Redis是一个内存数据库,如果要遍历的数据集比较大,可能会对服务器性能产生一定的影响。此外,由于Redis是一个键值存储数据库,遍历的顺序是不确定的。
总结一下,Redis的渐进式遍历通过SCAN命令实现,可以高效地逐步获取大量的数据。在实际使用中,我们可以根据需求设置合适的COUNT参数来控制每次返回的数据量,以及根据返回结果中的游标值来进行下一次遍历。每次使用 SCAN 命令遍历时,都需要将上一次遍历的游标作为下一次遍历的参数传递进去,以便继续从上次遍历结束的地方开始。因此,使用 SCAN 命令进行渐进式遍历时需要编写一些代码来管理游标。
注意事项
- 渐进式遍历在遍历过程中,不会在服务器存储任何的状态信息,此处的遍历随时可以终止。
举个例子来理解:
比如,我去买煮馍吃,商家都做了一半了,此时我想取消。
在这个例子中,我相当于客户端,商家相当于服务器, 如果我想取消,此时已经在服务器保留了状态信息,此时就会对服务器的运行造成影响。但是redis的服务器不保留任何状态
scan命令能有效的解决keys命令带来的阻塞问题,但是却带来新的问题。
在scan过程中,如果键发生了变化(增、删、改),那么有可能会出现新增的键没有遍历到或者遍历出了重复键的情况。这是在开发过程中需要注意到的地方。
使用迭代器或foreach循环遍历集合时,如果在遍历过程中直接调用集合的修改方法(如add、remove等),会导致迭代器的内部状态与集合的实际状态不一致,从而抛出
ConcurrentModificationException
异常。