Redis源码分析:初步分析get命令(一)
文章允许非商业性质转载,但请标明出处:http://forenroll.iteye.com/
我刚刚才开始适应在Linux环境下工作,本来想用codeblocks来看代码,但是用它却不能调试,每次我一执行build就出错,于是我就用vim看代码,添加“调试信息”,然后make clean && make。整个过程就是简单粗暴。而且我的C语言基础基本忘光了,在后面调试的时候遇到很多问题都没解决。下面的代码我都尽量贴的原始的,只是为了篇幅删了一些注释,避免用我的“调试信息”污染大家的眼睛。如果谁有好的调试方法或工具还希望分享一下。
要了解get命令的整个处理过程,必须了解Redis中的各种数据结构。下面是在get命令的处理过程中会用到的一些数据结构:
下面是我自己写的一个函数,用来打印redisObject。但只能打印类型是字符串,编码是字符串或整型的对象。因为我还没弄清楚其他的类型和编码是怎么存储,结构是什么样的。
/*
* === FUNCTION ==================================================================
* Name: printRedisObject
* Description: added by forenroll
* file position:src/redis.c
* =================================================================================
*/
void printRedisObject (robj *o )
{
unsigned type = o->type;
unsigned encoding = o->encoding;
if(type!=REDIS_STRING)
{
printf("the robj is not string type, could not print it now\n");
return;
}
switch(encoding){
case REDIS_ENCODING_RAW:
printf("the robj is string type, and its value is %s, and its length is %d\n",o->ptr,sdslen(o->ptr));
break;
case REDIS_ENCODING_INT:
printf("the robj is long type, and its value is %ld\n",(long)o->ptr);
break;
default:
printf("could not print the robj\n");
break;
}
return;
} /* ----- end of function printRedisObject ----- */
get命令的实现函数非常简单,它调用了getGenericCommand函数:
void getCommand(redisClient *c) {
getGenericCommand(c);
}
getGenericCommand函数的内容也比较简单:
int getGenericCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
{
return REDIS_OK;
}
if (o->type != REDIS_STRING) {
addReply(c,shared.wrongtypeerr);
return REDIS_ERR;
} else {
addReplyBulk(c,o);
return REDIS_OK;
}
}
这两个函数都在src/t_strint.c文件中定义。
(src/db.c)lookupKeyReadOrReply函数根据key去db。这个函数的名字也比较奇怪,lookupKeyRead or Reply,下面是它的实现:
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReply(c,reply);
return o;
}
从实现上看,只有找不到key的时候才会reply。这个地方我有一个疑问:为什么查询的方法还要负责reply。而不是直接在getGenericCommand函数里调用lookupKeyRead,或者不管怎么样这个函数就不应该加一个reply的职责。这种设置可能是由于某种原因,因为我还不够深入才未发现。
get命令取回的值都是字符串类型的,所以getGenericCommand函数中走的是else语句。那我们来看看(src/networking.c)addReplyBulk函数都做了些什么:
/* Add a Redis Object as a bulk reply */
void addReplyBulk(redisClient *c, robj *obj) {
addReplyBulkLen(c,obj);
addReply(c,obj);
addReply(c,shared.crlf);
}
(src/networking.c)addReplyBulkLen告诉客户端,接下来的消息的长度,第一个(src/networking.c)addReply发送真正的消息,第二个addReply发送的是一个"\r\n",这应该是一个消息的结束标志。 addReplyBulkLen函数:
/* Create the length prefix of a bulk reply, example: $2234 */
void addReplyBulkLen(redisClient *c, robj *obj) {
size_t len;
printRedisObject(obj);
if (obj->encoding == REDIS_ENCODING_RAW) {
len = sdslen(obj->ptr);
} else {
long n = (long)obj->ptr;
/* Compute how many bytes will take this integer as a radix 10 string */
len = 1;
if (n < 0) {
len++;
n = -n;
}
while((n = n/10) != 0) {
len++;
}
}
addReplyLongLongWithPrefix(c,len,'$');
}
这个函数就是用来计算消息的长度,并且将长度以”$n\r\n”的形式返回给客户端的。如果消息长度为4,则返回”$4\r\n”。addReplyBulkLen这个函数貌似只在get命令中用来计算消息长度,其他命令可能也有相应的计算函数。注意:在第一个addReply函数中并没有在消息后面加上”\r\n”。
Redis源码分析:初步分析get命令(二)
redisDb类型的定义:
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id;
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
redisDb类型中有两个field是与本次分析相关的,dict、expires,这两个变量分别存储所有的key-value和key-expiretime。所有的key-value都存储在dict这个“字典”里,而expires里则存储了所有设置有过期时间的key和它的过期时间。
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
expireIfNeeded(db,key);
val = lookupKey(db,key);
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;
}
(src/db.c)lookupKeyReadOrReply函数直接调用(src/db.c)lookupKeyRead函数。程序会在lookupKeyRead函数中通过调用函数(src/db.c)expireIfNeeded判断key的过期时间,如果过期则将其设置为过期,如果日志开启就将过期信息写入日志,并告知所有的slave。
int expireIfNeeded(redisDb *db, robj *key) {
long long when = getExpire(db,key);//这个函数会查询db->expires里key的过期时间。
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
if (server.masterhost != NULL) { //当前server是slave时,也同样对待
return mstime() > when;
}
/* Return when this key has not expired */
if (mstime() <= when) return 0;
/* Delete the key */
server.stat_expiredkeys++;
propagateExpire(db,key);//这个函数的作用是写日志和通知slave。
return dbDelete(db,key);
}
这里我又产生了一个疑惑:既然是优先执行了expire操作(注意expireIfNeeded在lookupKeyRead中的调用顺序),为什么不设置一个expire操作的返回值,告诉主程序(lookupKeyRead)库里存在这个key,但是刚刚检测到已经过期,并且已经删除了。那么后面就不用再去查询了。 可能这种设置有某种我还未了解到的原因吧。
lookupKeyRead调用了lookupKey,而lookupKey则直接调用了Redis里面的字典API:dictFind。 (src/dict.c)dictFind函数的实现:
dictEntry *dictFind(dict *d, const void *key)
{
dictEntry *he;
unsigned int h, idx, table;
if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx];
while(he) {
if (dictCompareKeys(d, key, he->key))
return he;
he = he->next;
}
if (!dictIsRehashing(d)) return NULL;
}
return NULL;
}
这个函数就是一个hash查找的过程,搞清楚了Redis的“字典”的数据结构,就可以搞清楚这个过程。在expireIfNeeded函数中调用的getExpire函数也调用这个API,因为key的过期时间也存储在相同的数据结构中。
我是一个Linux、C语言的新手。在看了这篇文章才开始翻看Redis的源代码,希望能学习更多的东西。在看源代码的过程发现,自己基础(Linux、c)差很多。这篇文章是作为自己的一个笔记,也有一点点思考的东西在里面,希望跟大家讨论,也希望大家能给出意见。谢谢。
Written with StackEdit.
相关推荐
声明:此文档源自钱文品老师所著《Redis深度历险:核心原理和应用实践》,只供个人学习所用,不得私自用于商业用途Redis 深度历险:核心原理与应用实践 | 钱
Redis深度历险:核心原理和应用实践 比较稀缺的紫瑶
总结《Redis深度历险:核心原理和应用实践》的xmind图
redis源码级别的分析。需要具备c语言基础的开发学习,十分详细,带你全程深入了解redis。
redis源码分析
Redis 的源码分析第二部分 By SinSay
Redis 的源码分析第一部分 By SinSay
阿里云Redis的规范:键值设计、命令使用、客户端使用、相关工具.docx
通过易语言操作Redis 来自JimStone 谢栋 Redis协议客户端模块:STRedisClient
在ASP.NET MVC中使用Redis 的Demo:通过Redis实现用户登陆,并保持登陆状态,设置过期时间,检测在线用户。
redis源码阅读中文分析注释
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 附件里面包括redis源码,phpredis源码,redis指令及文档
redis 连接报错 GET_LIKE_ERROR 处理过程.rar
Redis::Fast - Redis 数据库的 Perl 绑定 概要 ## Defaults to $ENV{REDIS_SERVER} or 127.0.0.1:6379 my $redis = Redis::Fast->new; my $redis = Redis::Fast->new(server => 'redis.example.com:8080'); ## Set ...
Redis命名空间Redis的::命名空间提供了一个接口,您的命名空间的子集密钥空间(例如,具有共同的开始键),并要求宝石。 require 'redis-namespace'# => trueredis_connection = Redis . new# => #<Redis>namespaced...
主要介绍了Redis源码解析:集群手动故障转移、从节点迁移的相关内容,涉及通过集群定时器函数clusterCron实现从节点迁移等知识,具有一定参考价值,需要的朋友可以了解。
redis-port(Linux 64 位) 是一组开源工具集合,主要用于 Redis 节点间的数据库同步、数据导入、数据导出,支持 Redis 的跨版本数据迁移。其包括以下工具: redis-sync:支持在 Redis 实例之间进行数据迁移。 redis...
springBoot集成Redis源码,springBoot集成Redis源码,springBoot集成Redis源码
Redis源码解读,讲解Redis内部实现机制。 Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
redis源码日志是对redis源码的分析解读,适用于学习。 介绍了redis是如何实现高并发、海量数据存贮的。 资源是PDF格式,有完整书签,也很清晰。