12、Scan扫描所有key

学习目标:

1、了解Scan的使用

2、掌握Scan和JRedis的Scan的使用

学习过程:

     在生产环境中经常需要查看某些key的值,已获取其对应的value,也经常需要查看又那些大key,影响了性能,在前面我们学习了keys 命令,可以通过模糊查询获得对应对应的所有的key,如果生产环境的key很多,那么keys * 会很慢,因为redis是单线程的,这个keys 命令会导致redis卡顿,所以一般来说keys 命令都是禁用的,从redis 2.8后,推荐使用scan命令。scan命令是增量的循环,类似mysql的分页查询,每次调用只会返回一小部分的元素。所以不会有keys命令的问题。scan命令返回的是一个游标,从0开始遍历,到0结束遍历。

命令格式:

SCAN cursor [MATCH pattern] [COUNT count]
命令解释:scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量
通过scan中的MATCH <pattern> 参数,可以让命令只返回和给定模式相匹配的元素,实现模糊查询的效果

一、redis的命令行界面

先添加100个

@Test  
public void testSpringRedis2() {  
	for(int i=0;i<100;i++) {
		template.opsForValue().set("name"+i, "liubao"+1);
	}
}

redis测试,下面的测试比较长,可以直接看到最后一个了。坐标返回为0就标识全部编译结束了

127.0.0.1:6379> scan 0 match name* count 10
1) "132"
2)  1) "name23"
    2) "name71"
    3) "name60"
    4) "name50"
    5) "name77"
    6) "name56"
    7) "name53"
    8) "name31"
    9) "name22"
   10) "name18"
127.0.0.1:6379> scan 132 match name* count 10
1) "66"
2)  1) "name64"
    2) "name67"
    3) "name0"
    4) "name59"
    5) "name10"
    6) "name6"
    7) "name29"
    8) "name80"
    9) "name91"
   10) "name40"
127.0.0.1:6379> scan 66 match name* count 10
1) "234"
2)  1) "name34"
    2) "name36"
    3) "name49"
    4) "name51"
    5) "name63"
    6) "name30"
    7) "name76"
    8) "name9"
    9) "name44"
   10) "name85"
   11) "name89"
127.0.0.1:6379> scan 234 match name* count 10
1) "150"
2)  1) "name13"
    2) "name38"
    3) "name11"
    4) "name74"
    5) "name55"
    6) "name81"
    7) "name37"
    8) "name86"
    9) "name48"
   10) "name41"
127.0.0.1:6379> scan 150 match name* count 10
1) "254"
2)  1) "name45"
    2) "name96"
    3) "name35"
    4) "name58"
    5) "name93"
    6) "name79"
    7) "name84"
    8) "name62"
    9) "name4"
   10) "name1"
127.0.0.1:6379> scan 254 match name* count 10
1) "77"
2) 1) "name32"
   2) "name54"
   3) "name75"
   4) "name27"
   5) "name24"
   6) "name39"
   7) "name26"
   8) "name72"
   9) "name3"
127.0.0.1:6379> scan 77 match name* count 10
1) "131"
2)  1) "name98"
    2) "name69"
    3) "name2"
    4) "name70"
    5) "name25"
    6) "name78"
    7) "name88"
    8) "name7"
    9) "name15"
   10) "name66"
127.0.0.1:6379> scan 131 match name* count 10
1) "155"
2)  1) "name92"
    2) "name47"
    3) "name83"
    4) "name5"
    5) "name33"
    6) "name8"
    7) "name87"
    8) "name20"
    9) "name43"
   10) "name65"
127.0.0.1:6379> scan 155 match name* count 10
1) "23"
2)  1) "name97"
    2) "name61"
    3) "name16"
    4) "name94"
    5) "name19"
    6) "name"
    7) "name52"
    8) "name90"
    9) "name99"
   10) "name73"
   11) "name82"
   12) "name95"
127.0.0.1:6379> scan 23 match name* count 10
1) "95"
2) 1) "name14"
   2) "name57"
   3) "name21"
   4) "name12"
   5) "name46"
   6) "name68"
   7) "name17"
   8) "name28"
127.0.0.1:6379> scan 95 match name* count 10
1) "0"   #返回0就是遍历结束了。
2) 1) "name42"

你会发现每一次返回的并不都是10条数据,所以和mysql的分页查询还是又区别的。一直遍历到返回的 cursor 值为 0 时结束。

hash也同样支持scan,命令也类似hasc,示例如下:

127.0.0.1:6379> hset students name1 liu1 name2 liu2 name3 liu3
(integer) 3
127.0.0.1:6379> hscan students 0 match name* count 10
1) "0"
2) 1) "name1"
   2) "liu1"
   3) "name2"
   4) "liu2"
   5) "name3"
   6) "liu3"
 

二、遍历顺序

大家可以看到scan的遍历并不是一次性取得所有的数据,那么如果在遍历的过程中,发生了扩容,如果保证遍历的数据没有重复和遗漏呢。scan不是从第一维数组的第 0 位一直遍历到末尾,而是采用了高位进位加法来遍历。这样就可以解决到字典的扩容和缩容时避免槽位的遍历重复和遗漏。高位进位法从左边加,进位往右边移动,高位进位法可以使得扩容后并没有改变原来的排序,同普通加法正好相反,所以最终它们都会遍历所有的槽位并且没有重复,有兴趣的同学可以自己去了解一下,

三、定位大 key --bigkeys

前面说了为了避免对生产环境的 Redis服务 造成卡顿,我们经常需要查找那些大的key,不过 Redis 官方已经在 redis-cli 指令中提供了这样的扫描功能,我们可以直接拿来即用。

redis-cli -a 123456 –-bigkeys

这个指令会大幅抬升 Redis 的 ops 导致线上报警,还可以增加一个休眠参数。

redis-cli  –-bigkeys -i 0.1

上面这个指令每隔 100 条 scan 指令就会休眠 0.1s,ops 就不会剧烈抬升,但是扫描的时间会变长。

四、java封装的scan

示例代码如下:

@Test  
public void  testScan() {  
	
	final String query="name*";

	template.execute(new RedisCallback<Object>() {

		@Override
		public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
			ScanOptions options = ScanOptions.scanOptions().match(query).count(Integer.MAX_VALUE).build();
	        Cursor<byte[]> c = redisConnection.scan(options);
	        while (c.hasNext()) {
	            System.out.println(new String((byte[]) c.next()));
	        }
			return null;
		}
	});
}

ursor一定不能关闭,在之前的版本中,这里Cursor需要手动关闭,但是从1.8.0开始,不能手动关闭!否则会报异常。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Java 中通过 Redis SCAN 命令可以获取所有的 key,SCAN 命令可以配合游标(cursor)进行分批次获取,以避免一次性获取过多的 key 导致性能问题。以下是使用 Jedis 和 Lettuce 两个 Redis 客户端库中的方法实现的示例代码: 使用 Jedis 客户端库: ```java // 创建 Jedis 实例 Jedis jedis = new Jedis("localhost", 6379); // 初始化游标 String cursor = ScanParams.SCAN_POINTER_START; // 创建 ScanParams 实例 ScanParams params = new ScanParams().count(1000); // 循环扫描所有 key while (true) { // 执行 SCAN 命令 ScanResult<String> result = jedis.scan(cursor, params); // 获取当前批次的游标和 key 列表 cursor = result.getStringCursor(); List<String> keys = result.getResult(); // 对 key 列表进行处理 for (String key : keys) { System.out.println(key); } // 如果游标为 0,则说明已经扫描完成 if (cursor.equals(ScanParams.SCAN_POINTER_START)) { break; } } // 关闭 Jedis 连接 jedis.close(); ``` 使用 Lettuce 客户端库: ```java // 创建 RedisClient 实例 RedisClient client = RedisClient.create("redis://localhost"); // 创建 RedisConnection 实例 RedisConnection<String, String> connection = client.connect(); // 初始化游标 String cursor = ScanCursor.INITIAL.getValue(); // 创建 ScanArgs 实例 ScanArgs args = ScanArgs.Builder.limit(1000); // 循环扫描所有 key while (true) { // 执行 SCAN 命令 RedisAdvancedClusterAsyncCommands<String, String> asyncCommands = connection.async(); RedisFuture<ScanResult<String>> future = asyncCommands.scan(cursor, args); ScanResult<String> result = future.get(); // 获取当前批次的游标和 key 列表 cursor = result.getCursor(); List<String> keys = result.getResult(); // 对 key 列表进行处理 for (String key : keys) { System.out.println(key); } // 如果游标为 0,则说明已经扫描完成 if (cursor.equals(ScanCursor.INITIAL.getValue())) { break; } } // 关闭 RedisConnection 连接 connection.close(); // 关闭 RedisClient 连接 client.shutdown(); ``` 需要注意的是,SCAN 命令会消耗一定的性能,因此需要根据实际情况来调整游标的大小和扫描的频率。同时,SCAN 命令是一个近似算法,可能会漏扫或者重复扫描一些 key,因此在实际使用中需要进行进一步的判断和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值