学习目标:
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开始,不能手动关闭!否则会报异常。