Redis Scan的使用方式以及Spring redis的坑

SpringRedisTemplate针对这个Scan进行了封装,示例使用(针对最新库spring-data-redis-1.8.1.RELEASE):

1Set<Object> execute = redisTemplate.execute(new RedisCallback<Set<Object>>() { 2 3 @Override 4 public Set<Object> doInRedis(RedisConnection connection) throws DataAccessException { 5 6 Set<Object> binaryKeys = new HashSet<>(); 7 8 Cursor<byte[]> cursor = connection.scan( new ScanOptions.ScanOptionsBuilder().match("test*").count(1000).build()); 9 while (cursor.hasNext()) { 10 binaryKeys.add(new String(cursor.next())); 11 } 12 return binaryKeys; 13 } 14});

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

ScanOptions有两个参数,一个是match,另一个是count,分别对应scan命令的两个参数。

Scan命令源码:

1 /* Handle the case of a hash table. */ 2 ht = NULL; 3 if (o == NULL) {//键扫描 4 ht = c->db->dict; 5 } else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) { 6 ht = o->ptr; 7 } else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) { 8 ht = o->ptr; 9 count *= 2; /* We return key / value for this type. */ 10 } else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) { 11 zset *zs = o->ptr; 12 ht = zs->dict; 13 count *= 2; /* We return key / value for this type. */ 14 } 15//由于redis的ziplist, intset等类型数据量挺少,所以可用一次返回的。下面的else if 做这个事情。全部返回一个key 。 16 if (ht) {//一般的存储,不是intset, ziplist 17 void *privdata[2]; 18 19 /* We pass two pointers to the callback: the list to which it will 20 * add new elements, and the object containing the dictionary so that 21 * it is possible to fetch more data in a type-dependent way. */ 22 privdata[0] = keys; 23 privdata[1] = o; 24 do { 25 //一个个扫描,从cursor开始,然后调用回调函数将数据设置到keys返回数据集里面。 26 cursor = dictScan(ht, cursor, scanCallback, privdata); 27 } while (cursor && listLength(keys) < count); } else if (o->type == REDIS_SET) { 28 int pos = 0; 29 int64_t ll; 30 31 while(intsetGet(o->ptr,pos++,&ll))//将这个set里面的数据全部返回,因为它是压缩的intset,会很小的。 32 listAddNodeTail(keys,createStringObjectFromLongLong(ll)); 33 cursor = 0; 34 } else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {//那么一定是ziplist了,字符串表示的数据结构,不会太大。 35 unsigned char *p = ziplistIndex(o->ptr,0); 36 unsigned char *vstr; 37 unsigned int vlen; 38 long long vll; 39 40 while(p) {//扫描整个键,然后全部返回这一条。并且返回cursor为0表示没东西了。其实这个就等于没有遍历 41 ziplistGet(p,&vstr,&vlen,&vll); 42 listAddNodeTail(keys, 43 (vstr != NULL) ? createStringObject((char*)vstr,vlen) : createStringObjectFromLongLong(vll)); 44 p = ziplistNext(o->ptr,p); 45 } 46 cursor = 0; 47 } else { 48 redisPanic("Not handled encoding in SCAN."); 49 }

可以看出,Redis的SCAN操作由于其整体的数据设计,无法提供特别准的scan操作,仅仅是一个“can ‘ t guarantee , just do my best”的实现:

  • 提供键空间的遍历操作,支持游标,复杂度O(1), 整体遍历一遍只需要O(N);
  • 提供结果模式匹配;
  • 支持一次返回的数据条数设置,但仅仅是个hints,有时候返回的会多;
  • 弱状态,所有状态只需要客户端需要维护一个游标;
  • 无法提供完整的快照遍历,也就是中间如果有数据修改,可能有些涉及改动的数据遍历不到;
  • 每次返回的数据条数不一定,极度依赖内部实现;
  • 返回的数据可能有重复,应用层必须能够处理重入逻辑;上面的示例代码中,redisTemplate.execute方法是个Set,相当于已经对于返回的key去重
  • count是每次扫描的key个数,并不是结果集个数。count要根据扫描数据量大小而定,Scan虽然无锁,但是也不能保证在超过百万数据量级别搜索效率;count不能太小,网络交互会变多,count要尽可能的大。在搜索结果集1万以内,建议直接设置为与所搜集大小相同
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid