简言

1. 线上环境keys命令不可用,会导致redis卡死。scan命令因为可以分批遍历,比较实用

2. scan命令包括多个

遍历整个数据库的scan命令,处理函数 scanCommand(),最终调用scanGenericCommand()

遍历hash对象的hscan命令,处理函数 hscanCommand(),最终调用scanGenericCommand()

遍历set对象的sscan命令,处理函数 sscanCommand(),最终调用scanGenericCommand()

遍历zset对象的zscan命令,处理函数 zscanCommand(),最终调用scanGenericCommand()

3. 命令格式(0,1,2,3,4,5用来表下标,代码中会根据下标解析参数match,count等)

 0      1      2      3       4     5
SCAN cursor [MATCH pattern] [COUNT count]0     1    2       3      4        5     6
SSCAN KEY cursor [MATCH pattern] [COUNT count]
HSCAN KEY cursor [MATCH pattern] [COUNT count]
ZSCAN KEY cursor [MATCH pattern] [COUNT count]

所以我们重点分析函数 scanGenericCommand(),笔者redis版本是6.0.0

// 这个函数用来实现 SCAN,HSCAN,SSCAN,ZSCAN命令
// 如果参数 o 有值,说明对象一定是hash, set 或者 zset;如果参数 o 为空,说明是遍历当前整个数据库
void scanGenericCommand(client *c, robj *o, unsigned long cursor) {int i, j;// 新建一个list,用来暂存一定量的未筛选的keylist *keys = listCreate();listNode *node, *nextnode;// 如果客户端不传count值,那么默认是10long count = 10;// pat 表 要match的字符串sds pat = NULL;sds typename = NULL;//  patlen 表 match字符串的长度,use_pattern 表是否使用是通配符*,1表是*int patlen = 0, use_pattern = 0;dict *ht;// Assert断言,o只能是NULL, set对象,hash对象,zset对象之一serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH ||o->type == OBJ_ZSET);// i用来指向第一个参数下标,o为NULL说明是遍历当前整个库,不需要传key,所以跳过i = (o == NULL) ? 2 : 3; // 第一步:解析参数while (i < c->argc) {// j表剩余参数个数,j一定要是偶数,因为参数名字,参数值是成对的j = c->argc - i;// 解析参数 count,不区分大小写if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {// i指向count,那么i+1指向count的值,做解析后保存在count变量中if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)!= C_OK){goto cleanup;}// count小于1则报错if (count < 1) {addReply(c,shared.syntaxerr);goto cleanup;}// 因为参数是成对的,所以跳2i += 2;} // 解析参数 match,不区分大小写else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {pat = c->argv[i+1]->ptr;patlen = sdslen(pat);// 若是*,那么use_pattern置为1use_pattern = !(pat[0] == '*' && patlen == 1);// 因为参数是成对的,所以跳2i += 2;} // 解析参数 type, 不区分大小写else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {/* SCAN for a particular type only applies to the db dict */typename = c->argv[i+1]->ptr;i+= 2;} // 都不是,说明参数传错了,直接返回错误else {addReply(c,shared.syntaxerr);goto cleanup;}}// 第二步:遍历集合ht = NULL;// 如果参数 o  // 为 NULL// 为 set  且内部编码为 OBJ_ENCODING_HT;// 为 hash 且内部编码为 OBJ_ENCODING_HT;// 为 zset 且内部编码为 OBJ_ENCODING_SKIPLIST;// 这三种情况下,说明元素个数很多,遍历时为防止redis卡顿,需要增量遍历,此时让count起效,给ht赋值// 其他情况,比如用ziplist(压缩列表)实现的对象,说明元素个数很少,直接全部遍历就行了,此时count不起效if (o == NULL) {ht = c->db->dict;} else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) {ht = o->ptr;} else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) {ht = o->ptr;count *= 2; /* We return key / value for this type. */} else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) {zset *zs = o->ptr;ht = zs->dict;count *= 2; /* We return key / value for this type. */}if (ht) {void *privdata[2];// 最多遍历次数,防止过多占用cpulong maxiterations = count*10;// 传入的参数:存储key的列表,字典对象privdata[0] = keys;privdata[1] = o;// 遍历,每次取出一部分key放进keys中,并更新cursor,再每次判断keys的个数,需不能超过count限定// 由此可见其实返回的个数是有可能超过count的do {cursor = dictScan(ht, cursor, scanCallback, NULL, privdata);} while (cursor &&maxiterations-- &&listLength(keys) < (unsigned long)count);// 注意此时cursor并不一定是0} // set 对象,且是压缩列表实现的,全遍历,此时count不起效else if (o->type == OBJ_SET) {int pos = 0;int64_t ll;while(intsetGet(o->ptr,pos++,&ll))listAddNodeTail(keys,createStringObjectFromLongLong(ll));// 全遍历,则 cursor 置为0cursor = 0;} // has,zset 对象,且是压缩列表实现的,全遍历,此时count不起效else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {unsigned char *p = ziplistIndex(o->ptr,0);unsigned char *vstr;unsigned int vlen;long long vll;while(p) {ziplistGet(p,&vstr,&vlen,&vll);listAddNodeTail(keys,(vstr != NULL) ? createStringObject((char*)vstr,vlen) :createStringObjectFromLongLong(vll));p = ziplistNext(o->ptr,p);}// 全遍历,则 cursor 置为0cursor = 0;} else {serverPanic("Not handled encoding in SCAN.");}// 第三步:筛选keys,很简单,keys是个列表,逐个元素遍历即可node = listFirst(keys);while (node) {robj *kobj = listNodeValue(node);nextnode = listNextNode(node);int filter = 0;if (!filter && use_pattern) {// 是sds对象的话,直接比较字符串if (sdsEncodedObject(kobj)) {if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))filter = 1;} else { // 否则把数字对象,转换为string后再比较字符串char buf[LONG_STR_SIZE];int len;serverAssert(kobj->encoding == OBJ_ENCODING_INT);len = ll2string(buf,sizeof(buf),(long)kobj->ptr);if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;}}/* Filter an element if it isn't the type we want. */if (!filter && o == NULL && typename){robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);char* type = getObjectTypeName(typecheck);if (strcasecmp((char*) typename, type)) filter = 1;}// 判断是否键过期,过期的话,filter置为1if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;// 过期的话则删除if (filter) {decrRefCount(kobj);         // 减少引用次数listDelNode(keys, node);    // 从list中删除这个节点}// 是zset,hash对象时,如果需要被删除,这里还需要删除valueif (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) {node = nextnode;nextnode = listNextNode(node);if (filter) {kobj = listNodeValue(node);decrRefCount(kobj);listDelNode(keys, node);}}node = nextnode;}// 回复信息,注意这里把cursor返回给了客户端addReplyArrayLen(c, 2);addReplyBulkLongLong(c,cursor);addReplyArrayLen(c, listLength(keys));// 整理keys,一一压入回复消息while ((node = listFirst(keys)) != NULL) {robj *kobj = listNodeValue(node);addReplyBulk(c, kobj);decrRefCount(kobj);listDelNode(keys, node);}// 清理操作
cleanup:listSetFreeMethod(keys,decrRefCountVoid);listRelease(keys);
}

redis的scan命令的源码分析,实现原理相关推荐

  1. 如何将镜像烧写至iNand(fastboot命令的源码分析)

    以下内容源于网络资源的学习与整理,如有侵权请告知删除. 参考博客 u-boot sdfuse命令烧录分析----从SD卡加载内核_white_bugs的博客-CSDN博客 一.将镜像文件烧写至iNan ...

  2. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  3. Tomcat7.0源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多早期的J2EE项目,由程序 ...

  4. WPF(六) Command 命令模型源码分析

    1.ICommand源码分析 ​ 在之前 WPF(三) WPF命令 中我们已经分析过了 WPF 的命令系统,包括WPF默认的 RoutedCommand 以及我们自定义的 ICommand 命令实现. ...

  5. 04特性源码分析-ReentrantReadWriteLock原理-AQS-并发编程(Java)

    文章目录 1 锁重入 2 锁重入计数 2.1 读锁加锁计数 2.2 读锁解锁计数 3 公平与非公平锁 3.1 非公平锁 3.2 公平锁 4 锁降级与锁升级 4.1 锁升级 4.2 锁降级 5 full ...

  6. 线性判别结合源码分析LDA原理

    1. LDA的思想 LDA线性判别分析也是一种经典的降维方法,LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的.这点和PCA不同.PCA是不考虑样本类别输出的无监督降维技术. ...

  7. redis底层数据结构(redis底层存储结构、源码分析)

    文章目录 前言 一.redis为什么快? 二.redis的底层数据结构 2.1.redis的底层存储的扩容机制 2.1.1.扩容时间 2.1.2.扩容多大 2.1.3.扩容后的rehash 2.1.4 ...

  8. Linux kernel 3.10内核源码分析--slab原理及相关代码

    1.基本原理 我们知道,Linux保护模式下,采用分页机制,内核中物理内存使用buddy system(伙伴系统)进行管理,管理的内存单元大小为一页,也就是说使用buddy system分配内存最少需 ...

  9. docker stats命令源码分析结果

    2019独角兽企业重金招聘Python工程师标准>>> 本文是基于docker 1.10.3版本的源码,对docker stats命令进行源码分析,看看docker stats命令输 ...

最新文章

  1. 《大话数据结构》第9章 排序 9.7 堆排序(下)
  2. delphi中的bpl开发注意事项
  3. 关于 Python 列表操作,最常见问答Top10
  4. Meaven的pom文件配置
  5. vim函数跳转 php,求助!! vim-gvim中如何让其显示函数及其参数!!
  6. java panel frame_Java 版 (精华区)--Frame和Panel的区别【转载】
  7. 能查阅国外文献的8个论文网站(最新整理)
  8. 11年艺术学习“转投”数学,他出版首本TensorFlow中文教材,成为蚂蚁金服技术大军一员...
  9. 金圣叹“不亦快哉”三十三则
  10. SmartPLS软件如何做有调节的中介作用模型?
  11. 特斯拉灯光秀指南「GitHub 热点速览 v.22.01」
  12. python_分类_category方法
  13. 认同和确定性矩阵(Ralph Stacey's Agreement and Certainty Matrix)-译
  14. K-均值聚类(K-means)
  15. 【图文排版】微信文章怎样可以合理布局?
  16. MiniGUI学习整理
  17. python calu_python自动重采样数据
  18. 我用Python爬取了妹子网200G的套图
  19. SpringBootSecurity安全相关
  20. 【物理】半导体物理 西安电子科技大学 柴常春等主讲-[笔记P1-P9]

热门文章

  1. 工具设置Unity3D系列教程--使用免费工具在Unity3D中开发2D游戏 第一节
  2. JVM编译时和运行时状态
  3. 一直无法使用D版的Boson Netsim
  4. CodeForces - 609E Minimum spanning tree for each edge(最小生成树+树链剖分+线段树/树上倍增)
  5. 中石油训练赛 - Bad Treap(数学)
  6. CodeForces - 1339C Powered Addition(思维+贪心)
  7. nexus 代理阿里云_Azure容器镜像代理服务失效
  8. 排序算法-06堆排序(python实现)
  9. Python实战-获取鼠标键盘事件
  10. register---C语言中最快的关键字