Redis Scan迭代器遍历操作原理(一)
Redis在2.8.0版本新增了众望所归的scan操作,从此再也不用担心敲入了keys*, 然后举起双手看着键盘等待漫长的系统卡死了···
命令的官方介绍在这里, 中文版由huangz同学细心翻译了,作者Antirez的介绍在这里:Finally Redis collections are iterable (我又邪恶的想到了之前他那次机器down机的事故了···)。
具体的使用参考上面的链接即可,这里大概介绍一下Scan操作的实现原理。
Redis的SCAN操作由于其整体的数据设计,无法提供特别准的scan操作,仅仅是一个“can ‘ t guarantee , just do my best”的实现,优缺点如下:
- 优点:
- 提供键空间的遍历操作,支持游标,复杂度O(1), 整体遍历一遍只需要O(N);
- 提供结果模式匹配;
- 支持一次返回的数据条数设置,但仅仅是个hints,有时候返回的会多;
- 弱状态,所有状态只需要客户端需要维护一个游标;
- 缺点:
- 无法提供完整的快照遍历,也就是中间如果有数据修改,可能有些涉及改动的数据遍历不到;
- 每次返回的数据条数不一定,极度依赖内部实现;
- 返回的数据可能有重复,应用层必须能够处理重入逻辑;
所以结论是Scan是一个不错的但也让人又爱又恨的命令···。下面来介绍一下代码。
首先scanCommand 函数处理简单的scan操作,其他类似hscan函数跟这个的区别就是hscan需要取获取一遍key对应的空间或者说域,他们主要都是嚼用了通用的scan操作函数:scanGenericCommand 。
scanGenericCommand 函数分4步:
第一步当然就是解析参数了,比如count, match匹配参数;
第二部是需要去做真正的扫描键 的操作了,redis为了性能考虑,对于小数据结构会转换为ziplist,intset数据结构因此需要区分这2类,对于后者,由于其本身比较小,因此可完全可以在这一次scan操作的时候返还所有的数据,反正不大的。
另外一类就是正常的hash表所代表的扫描了,其扫描路径比较复杂,好吧,我看了好几次都没有看明白这到底是怎么扫描的,这几天啃也要啃出来!
/* 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_SET或者REDIS_HASH或者REDIS_ZSET行事存储的话,那么只需要扫描所有的键,然后一个个将其加入到临时的列表里面,以备返回给客户端。
最难的地方在于dictScan 函数,里面是各种位运算。
随后第三步就是进行结果的过滤了,一般就是用match参数代表的字符串去做匹配,看是否需要过滤数据。
第四步就是将收集到的数据返回给客户端。然后就完成了请求。
dictScan 原理:
好吧,我看了2次,没看懂·····先做饭··
ps: 写着写着发现一篇文章写不完,所以令起一篇了:Redis Scan迭代器遍历操作原理(二)–dictScan反向二进制迭代器 , 希望能讲清楚.

Redis Scan迭代器遍历操作原理(一)的更多相关文章
- Redis Scan迭代器遍历操作原理(二)
续上一篇文章 Redis Scan迭代器遍历操作原理(一)–基础 ,这里着重讲一下dictScan函数的原理,其实也就是redis SCAN操作最有价值(也是最难懂的部分). 关于这个算法的源头,来自 ...
- Redis Scan命令
原地址:https://www.cnblogs.com/tekkaman/p/4887293.html [Redis Scan命令] SCAN cursor [MATCH pattern] [COUN ...
- Redis SCAN命令实现有限保证的原理
SCAN命令可以为用户保证:从完整遍历开始直到完整遍历结束期间,一直存在于数据集内的所有元素都会被完整遍历返回,但是同一个元素可能会被返回多次.如果一个元素是在迭代过程中被添加到数据集的,又或者是在迭 ...
- Java List中迭代器遍历
在java中,List接口从Collection接口中继承了 iterator()函数,返回值是一个T类型的迭代器(泛型),T是List中元素的类型 public class TestListAndI ...
- java 迭代器遍历List Set Map
Iterator接口: 所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现Iterator接口的对象 Iterator对象称作为迭代器,用以方便的对容器内元素的遍历 ...
- Java 中List 集合索引遍历与迭代器遍历
package yzhou.iterator; import java.util.ArrayList; import java.util.HashSet; import java.util.Itera ...
- 有关map中使用iterate迭代器遍历的不保序问题和list remove(object)的细节问题
今天在做项目的过程中发现了如下两个问题: 一 使用map的iterator迭代器对map进行遍历得到的结果是不保序的,也就是每次输出结果都是不一样的.针对这个问题,看以下iterator迭代器的源码. ...
- 迭代器遍历【List、Set、Map】
迭代器遍历[List.Set.Map] example package boom.collection; import java.util.ArrayList; import java.util.Ha ...
- 迭代器:遍历集合元素的操作. iterator()
package seday11; import java.util.ArrayList;import java.util.Collection;import java.util.Iterator; / ...
随机推荐
- python常用模块详解2
序列化模块补充: 1.json格式的限制,json格式的key必须是字符串数据类型 2.json格式的字符串必须是"" 如果数字是key,那么dump之后会强转成字符串数据类型 i ...
- Teaching Machines to Understand Us 让机器理解我们 之三 自然语言学习及深度学习的信仰
Language learning 自然语言学习 Facebook’s New York office is a three-minute stroll up Broadway from LeCun’ ...
- oracle查询数据库所有用户信息
看到网上说的查询用户的语句: SELECT * FROM dba_users;这句好像只能系统管理员才能成功执行,普通用户无法执行 SELECT count(*) FROM all_users; , ...
- windows8和windows server2012不联网安装.net 3.5(包括2.0和3.0)
安装完win8后 发现系统默认没有安装.net3.5 如果使用在线更新的话需要很久才能完成,特别是当前的网速以及微软的服务器.速度很忙,其实我们利用win8的安装盘就可以不需要联网更新,而且几分钟就搞 ...
- 20181016-4 Alpha阶段第1周/共2周 Scrum立会报告+燃尽图 04
此作业要求https://edu.cnblogs.com/campus/nenu/2018fall/homework/2248 Scrum master:徐常实 一.小组介绍 组长:王一可 组员:范靖 ...
- Python语言基础
一.Python简介 Python是跨平台动态语言 特点:优雅.明确.简单 适用:web网站和网络服务:系统工具和脚步:包装其他语言开发的模块 不适用:贴近硬件(首选C):移动开发:IOS/Andro ...
- 石家庄铁道大学网站首页UI分析
今天的软件工程王老师讲了UI的设计,以前狭隘的认为只有移动设备上的界面叫UI,百度一下才发现UI其实有这么多含义:UI即User Interface的简称.泛指用户的操作界面,UI设计主要指界面的样式 ...
- Java final用法
//继承弊端:打破了封装性. /* final关键字: 1,final是一个修饰符,可以修饰类,方法,变量. 2,final修饰的类不可以被继承. 3,final修饰的方法不可以被覆盖. 4,fina ...
- JNDI和JDBC
没有JNDI的做法:程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库.就像以下代码这 ...
- SpringMVC项目中获取所有URL到Controller Method的映射
Spring是一个很好很强大的开源框架,它就像是一个容器,为我们提供了各种Bean组件和服务.对于MVC这部分而言,它里面实现了从Url请求映射控制器方法的逻辑处理,在我们平时的开发工作中并不需要太多 ...