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

本篇文章已作废,请参考我的最新文章解析:https://blog.csdn.net/zhxdick/article/details/119990463

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

Set<Object> execute = redisTemplate.execute(new RedisCallback<Set<Object>>() {

    @Override
    public Set<Object> doInRedis(RedisConnection connection) throws DataAccessException {
    
    	Set<Object> binaryKeys = new HashSet<>();
    
    	Cursor<byte[]> cursor = connection.scan( new ScanOptions.ScanOptionsBuilder().match("test*").count(1000).build());
    	while (cursor.hasNext()) {
    		binaryKeys.add(new String(cursor.next()));
    	}
    	return binaryKeys;
    }
});

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

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

Scan命令源码:

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

可以看出,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万以内,建议直接设置为与所搜集大小相同
  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
Spring Boot是一个用于创建独立的、基于Spring的应用程序的框架。它简化了Spring应用程序的开发过程,提供了自动配置和约定优于配置的原则。Redis是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和消息中间件。 在Spring Boot中使用Redis可以通过集成Spring Data Redis来实现。Spring Data Redis提供了一组简化的API,使得与Redis进行交互变得更加方便。 Redis的SCAN命令用于迭代遍历Redis中的键。它可以按照指定的模式匹配进行键的筛选,并返回匹配的键列表。SCAN命令可以分批次地返回结果,避免一次性返回大量的键,从而减少网络传输的开销。 在Spring Boot中使用Redis的SCAN命令可以通过使用RedisTemplate或者StringRedisTemplate来实现。以下是一个使用StringRedisTemplate进行SCAN操作的示例代码: ```java @Autowired private StringRedisTemplate redisTemplate; public List<String> scanKeys(String pattern) { List<String> keys = new ArrayList<>(); ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); Cursor<String> cursor = redisTemplate.opsForValue().getOperations().scan(options); while (cursor.hasNext()) { keys.add(cursor.next()); } return keys; } ``` 以上代码中,我们通过StringRedisTemplate获取Redis连接,并使用opsForValue()方法获取操作字符串类型数据的接口。然后,我们使用scan()方法进行键的迭代遍历,并将匹配的键添加到列表中返回。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值