这个文件我在今天分析学习的时候,一直有种似懂非懂的感觉,代码量700+的代码,最后开放给系统的就是一个process()方法。这里说的说的数据库检測,是针对key的检測,会用到,以下提到的结构体:

/* Data type to hold opcode with optional key name an success status */
/* 用于key的检測时使用。兴许检測操作都用到了entry结构体 */
typedef struct {
//key的名字
char* key;
//类型
int type;
//是否是成功状态
char success;
} entry;

兴许所涉及到的非常多API都是与这个结构体相关。此代码终于检測的事实上是一个叫dump.rdb的文件,在检測的后面还会加上循环冗余校验CRC64。以下亮出API:

int checkType(unsigned char t) /* 每当加入一个新的obj类型时,都要检測这个类型是否合理 */
int readBytes(void *target, long num) /* 在当前文件偏移量位置往后读取num个字节位置 */
int processHeader(void) /* 读取快照文件的头部,检測头部名称或版本是否正确 */
int loadType(entry *e) /* 为entry赋上obj的Type */
int peekType() /* 弹出版本 */
int processTime(int type) /* 去除用来表示时间的字节 */
uint32_t loadLength(int *isencoded) /* 分type读取长度 */
char *loadIntegerObject(int enctype) /* 依据当前整型的编码方式,获取数值,以字符形式返回 */
char* loadLzfStringObject() /* 获得解压后的字符串 */
char* loadStringObject() /* 获取当前文件信息字符串对象 */
int processStringObject(char** store) /* 将字符串对象赋给所传入的參数 */
double* loadDoubleValue() /* 文件里读取double类型值 */
int processDoubleValue(double** store) /* 对double类型进行赋予给參数 */
int loadPair(entry *e) /* 读取键值对 */
entry loadEntry() /* 获取entry的key结构体 */
void printCentered(int indent, int width, char* body) /* 输出界面对称的信息 */
void printValid(uint64_t ops, uint64_t bytes) /* 输出有效信息 */
void printSkipped(uint64_t bytes, uint64_t offset) /* 输出Skipped跳过bytes字节信息 */
void printErrorStack(entry *e) /* 输出错误栈的信息 */
void process(void) /* process方法是运行检測的主要方法 */

方法里面好多loadXXX()方法。这几个load方法的确比較实用,在这个检測文件里,编写者又非常人性化的构造了error的结构体。用于模拟错误信息栈的输出。

/* Hold a stack of errors */
/* 错误信息结构体 */
typedef struct {
//详细的错误信息字符串
char error[16][1024];
//内部偏移量
size_t offset[16];
//错误信息等级
size_t level;
} errors_t;
static errors_t errors;

不同的level等级相应不同的出错信息。在API里有个比較关键的方法。loadEntry,获取key相关的结构体;

/* 获取entry的key结构体 */
entry loadEntry() {
entry e = { NULL, -1, 0 };
uint32_t length, offset[4]; /* reset error container */
errors.level = 0; offset[0] = CURR_OFFSET;
//此处赋值type
if (!loadType(&e)) {
return e;
} offset[1] = CURR_OFFSET;
if (e.type == REDIS_SELECTDB) {
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
SHIFT_ERROR(offset[1], "Error reading database number");
return e;
}
if (length > 63) {
SHIFT_ERROR(offset[1], "Database number out of range (%d)", length);
return e;
}
} else if (e.type == REDIS_EOF) {
if (positions[level].offset < positions[level].size) {
SHIFT_ERROR(offset[0], "Unexpected EOF");
} else {
e.success = 1;
}
return e;
} else {
/* optionally consume expire */
if (e.type == REDIS_EXPIRETIME ||
e.type == REDIS_EXPIRETIME_MS) {
if (!processTime(e.type)) return e;
if (!loadType(&e)) return e;
} offset[1] = CURR_OFFSET;
//调用loadPair为Entry赋值key
if (!loadPair(&e)) {
SHIFT_ERROR(offset[1], "Error for type %s", types[e.type]);
return e;
}
} /* all entries are followed by a valid type:
* e.g. a new entry, SELECTDB, EXPIRE, EOF */
offset[2] = CURR_OFFSET;
if (peekType() == -1) {
SHIFT_ERROR(offset[2], "Followed by invalid type");
SHIFT_ERROR(offset[0], "Error for type %s", types[e.type]);
e.success = 0;
} else {
e.success = 1;
} return e;
}

当中里面的关键的赋值key,value在loadPair()方法:

/* 读取键值对 */
int loadPair(entry *e) {
uint32_t offset = CURR_OFFSET;
uint32_t i; /* read key first */
//首先从文件里读取key值
char *key;
if (processStringObject(&key)) {
e->key = key;
} else {
SHIFT_ERROR(offset, "Error reading entry key");
return 0;
} uint32_t length = 0;
if (e->type == REDIS_LIST ||
e->type == REDIS_SET ||
e->type == REDIS_ZSET ||
e->type == REDIS_HASH) {
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
SHIFT_ERROR(offset, "Error reading %s length", types[e->type]);
return 0;
}
} //读取key值后面跟着的value值
switch(e->type) {
case REDIS_STRING:
case REDIS_HASH_ZIPMAP:
case REDIS_LIST_ZIPLIST:
case REDIS_SET_INTSET:
case REDIS_ZSET_ZIPLIST:
case REDIS_HASH_ZIPLIST:
//由于相似ziplist,zipmap等结构体事实上是一个个结点连接而成的超级字符串。所以是直接读取
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading entry value");
return 0;
}
break;
case REDIS_LIST:
case REDIS_SET:
//而上面这2种是传统的结构,要分结点读取
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading element at index %d (length: %d)", i, length);
return 0;
}
}
break;
case REDIS_ZSET:
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length);
return 0;
}
offset = CURR_OFFSET;
if (!processDoubleValue(NULL)) {
SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length);
return 0;
}
}
break;
case REDIS_HASH:
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length);
return 0;
}
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length);
return 0;
}
}
break;
default:
SHIFT_ERROR(offset, "Type not implemented");
return 0;
}
/* because we're done, we assume success */
//仅仅要运行过了,我们就认定为成功
e->success = 1;
return 1;
}

假设e-success=1则说明这个key的检測就过关了。为什么这么说呢,我们来看主检測方法process()方法:

/* process方法是运行检測的主要方法 */
void process(void) {
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
entry entry;
//读取文件头部获取快照文件版本
int dump_version = processHeader(); /* Exclude the final checksum for RDB >= 5. Will be checked at the end. */
if (dump_version >= 5) {
if (positions[0].size < 8) {
printf("RDB version >= 5 but no room for checksum.\n");
exit(1);
}
positions[0].size -= 8;
} level = 1;
while(positions[0].offset < positions[0].size) {
positions[1] = positions[0]; entry = loadEntry();
if (!entry.success) {
//假设Entry不为成功状态
printValid(num_valid_ops, num_valid_bytes);
printErrorStack(&entry);
num_errors++;
num_valid_ops = 0;
num_valid_bytes = 0; /* search for next valid entry */
uint64_t offset = positions[0].offset + 1;
int i = 0; //接着寻找后面3个有效entries
while (!entry.success && offset < positions[0].size) {
positions[1].offset = offset; /* find 3 consecutive valid entries */
//寻找3个有效的entries
for (i = 0; i < 3; i++) {
entry = loadEntry();
if (!entry.success) break;
}
/* check if we found 3 consecutive valid entries */
if (i < 3) {
offset++;
}
} /* print how many bytes we have skipped to find a new valid opcode */
if (offset < positions[0].size) {
printSkipped(offset - positions[0].offset, offset);
} positions[0].offset = offset;
} else {
num_valid_ops++;
num_valid_bytes += positions[1].offset - positions[0].offset; /* advance position */
positions[0] = positions[1];
}
free(entry.key);
} /* because there is another potential error,
* print how many valid ops we have processed */
printValid(num_valid_ops, num_valid_bytes); /* expect an eof */
if (entry.type != REDIS_EOF) {
/* last byte should be EOF, add error */
errors.level = 0;
SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]); /* this is an EOF error so reset type */
entry.type = -1;
printErrorStack(&entry); num_errors++;
} /* Verify checksum */
//版本>=5的时候,须要检验校验和
if (dump_version >= 5) {
uint64_t crc = crc64(0,positions[0].data,positions[0].size);
uint64_t crc2;
unsigned char *p = (unsigned char*)positions[0].data+positions[0].size;
crc2 = ((uint64_t)p[0] << 0) |
((uint64_t)p[1] << 8) |
((uint64_t)p[2] << 16) |
((uint64_t)p[3] << 24) |
((uint64_t)p[4] << 32) |
((uint64_t)p[5] << 40) |
((uint64_t)p[6] << 48) |
((uint64_t)p[7] << 56);
if (crc != crc2) {
SHIFT_ERROR(positions[0].offset, "RDB CRC64 does not match.");
} else {
printf("CRC64 checksum is OK\n");
}
} /* print summary on errors */
if (num_errors) {
printf("\n");
printf("Total unprocessable opcodes: %llu\n",
(unsigned long long) num_errors);
}
}

假设想了解检測的具体原理。事先了解dump.rdb的文件内容结构或许会对我们又非常大帮助。

Redis源代码分析(十二)--- redis-check-dump本地数据库检測的更多相关文章

  1. Redis源代码分析(二十八)--- object创建和释放redisObject物

    今天的学习更有效率.该Rio分析过,学习之间的另一种方式RedisObject文件,只想说RedisObject有些生成和转换.都是很类似的.列出里面长长的API列表: /* ------------ ...

  2. Redis源代码分析(二十四)--- tool工具类(2)

    在上篇文章中初步的分析了一下,Redis工具类文件里的一些使用方法,包含2个随机算法和循环冗余校验算法,今天,继续学习Redis中的其它的一些辅助工具类的使用方法.包含里面的大小端转换算法,sha算法 ...

  3. Redis源代码分析(二十二)--- networking网络协议传输

    上次我仅仅分析了Redis网络部分的代码一部分,今天我把networking的代码实现部分也学习了一遍,netWorking的代码很多其它偏重的是Clientclient的操作.里面addReply( ...

  4. Redis源代码分析(二十)--- ae事件驱动

    事件驱动的术语出现更频繁.听起来非常大的,今天我把Redis内部驱动器模型来研究它,奖励的感觉啊.一个ae.c主程序,加4文件的事件类型,让你彻底弄清楚,Redis是怎样处理这些事件的. 在Redis ...

  5. Redis源代码分析(二十七)--- rio制I/O包裹

    I/O每个操作系统,它的一个组成部分.和I/O业务质量,在一定程度上也影响了系统的效率. 今天,我在了解了Redis中间I/O的,相同的,Redis在他自己的系统中.也封装了一个I/O层.简称RIO. ...

  6. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

  7. hostapd源代码分析(二):hostapd的工作机制

    [转]hostapd源代码分析(二):hostapd的工作机制 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004433 在我的上一 ...

  8. OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa

    元数据最本质.最抽象的定义为:data about data (关于数据的数据).它是一种广泛存在的现象,在许多领域有其具体的定义和应用. JDBC中的元数据,有数据库元数据(DatabaseMeta ...

  9. Redis源代码分析(十)--- testhelp.h小测试框架和redis-check-aof.c 日志检测

    周期分析struct结构体redis代码.最后,越多越发现很多的代码其实大同小异.于struct有袋1,2不分析文件,关于set集合的一些东西,就放在下次分析好了,在选择下个分析的对象时,我考虑了一下 ...

随机推荐

  1. LoadRunner 自带订票系统flights 功能空白、1080端口被占用的解决办法

    LoadRunner 自带订票系统flights 功能空白.1080端口被占用的解决办法 安装LoadRunner8.1后运行Mercury Web Tours Application,点击fligh ...

  2. Django实战(10):单元测试

    尽早进行单元测试(UnitTest)是比较好的做法,极端的情况甚至强调“测试先行”.现在我们已经有了第一个model类和Form类,是时候开始写测试代码了. Django支持python的单元测试(u ...

  3. 8-2 Party Games uva1610 (贪心)

    题意: 给出n个串(n为偶数): 要构造一个串,使n串中有一半小于等于它,另外一半大于它: 要求这个串长度尽量小,同时字典序小: 一开始我的优先级是放左   其实优先级是放左加一. 如 AAAA AA ...

  4. 005 爬虫(requests与beautifulSoup库的使用)

    一:知识点 1.安装requests库 2.Brautiful soup 可以提供一些简单的,python式的函数来处理导航,搜索,修改分析树等功能. 她是一个工具箱,通过解析文档为用户提供需要抓去的 ...

  5. CentOS 配置自启动Redis

    第一步: 在/etc/init.d/目录下建立一个名字为 redis 的启动脚本 cd /etc/init.d touch redis 然后在这个脚本中添加如下脚本  <注意修改自己的PIDFI ...

  6. webview内部跳转判断

    重写webview内的方法 webView.setWebViewClient(new WebViewClient() { @Override // 在点击请求的是链接是才会调用,重写此方法返回true ...

  7. idea导入或打开项目配置问题

    learn项目遇到问题: 1.IntelliJ Idea编译报错:请使用 -source 7 或更高版本以启用 diamond 运算符 file - project structure或者直接快捷键: ...

  8. 命令:less

    与more的区别 more在man手册中的英文原文是文件熟读过滤器(file perusal filter),其实可以理解为一种文本查看器. 它存在一些缺点: 必须事先加载完整个文件.因此在遇到大文件 ...

  9. iOS Sprite Kit教程之场景的切换

    iOS Sprite Kit教程之场景的切换 Sprite Kit中切换场景 每一个场景都不是单独存在的.玩家可以从一个场景中切换到另外一个场景中.本小节,我们来讲解场景切换.在每一个游戏中都会使用到 ...

  10. Ubuntu 16.04 64位 tftp服务器搭建

    TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂.开销不大的文件传输服务 ...