`
forenroll
  • 浏览: 12118 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

Redis源码分析:初步分析get命令

阅读更多

Redis源码分析:初步分析get命令(一)

文章允许非商业性质转载,但请标明出处:http://forenroll.iteye.com/

我刚刚才开始适应在Linux环境下工作,本来想用codeblocks来看代码,但是用它却不能调试,每次我一执行build就出错,于是我就用vim看代码,添加“调试信息”,然后make clean && make。整个过程就是简单粗暴。而且我的C语言基础基本忘光了,在后面调试的时候遇到很多问题都没解决。下面的代码我都尽量贴的原始的,只是为了篇幅删了一些注释,避免用我的“调试信息”污染大家的眼睛。如果谁有好的调试方法或工具还希望分享一下。

要了解get命令的整个处理过程,必须了解Redis中的各种数据结构。下面是在get命令的处理过程中会用到的一些数据结构:

redisClient

redisServer

redisObject

下面是我自己写的一个函数,用来打印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 3.0的源码,探讨其核心组件和设计理念。 一、Redis的数据结构 Redis支持多种数据结构,如字符串(String)、哈希表(Hash)、列表(List)、集合...

    Redis命令实践:深入探索Redis的强大功能.pdf

    为了帮助开发者更好地理解和运用Redis的强大能力,本文将通过实际操作与案例分析的方式,详细介绍Redis命令的使用方法及其最佳实践。 #### Redis基础命令实践 Redis提供了多种数据类型,包括字符串、列表、集合、...

    易语言的Redis协议实现:JimStone 谢栋 Redis协议客户端模块:STRedisClient

    STRedisClient模块提供了丰富的Redis命令支持,包括但不限于字符串操作(如`GET`, `SET`, `INCR`)、哈希表操作(如`HSET`, `HGETALL`)、列表操作(如`LPUSH`, `LPOP`)、集合操作(如`SADD`, `SMEMBERS`)以及有序...

    Redis Windows源码

    在Windows环境下,Redis的源码分析和部署对于开发者来说具有重要意义,尤其是在Windows服务端开发中。以下是对"Redis Windows源码"的详细解析: 1. Redis核心架构: Redis基于单线程模型,通过事件驱动机制处理...

    Redis - Redis深度历险.pdf.zip

    12. **命令监控与限制**:Redis提供了`INFO`命令获取服务器状态,以及`CLIENT KILL`等命令限制客户端行为,以防止DoS攻击。 通过深入学习《Redis深度历险》,读者可以掌握如何高效利用Redis的特性来解决实际问题,...

    redis源码资源下载

    Redis(Remote Dictionary Server),即远程字典服务,是一个开源的、高性能的、基于内存的Key-Value数据库,它使用ANSI C语言编写,支持网络,并提供了多种语言的API。Redis以其丰富的数据结构、高性能、持久化特性...

    redis源码阅读中文分析注释

    redis源码阅读中文分析注释

    redis/phpredis源码及文档

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 附件里面包括redis源码,phpredis源码,redis指令及文档

    redis-namespace:此gem添加了Redis :: Namespace类,该类可用于命名空间Redis键

    Redis命名空间Redis的::命名空间提供了一个接口,您的命名空间的子集密钥空间(例如,具有共同的开始键),并要求宝石。 require 'redis-namespace'# =&gt; trueredis_connection = Redis . new# =&gt; #&lt;Redis&gt;namespaced...

    redis 连接报错 GET_LIKE_ERROR 处理过程.rar

    redis 连接报错 GET_LIKE_ERROR 处理过程.rar

    Redis开发实战:基于Redis的实时排行榜系统的实验心得与案例解析

    - **命令优化**:合理使用Redis命令,避免使用时间复杂度高的命令,如`KEYS`命令。 - **数据备份**:定期备份Redis数据,以防数据丢失。 - **安全设置**:设置密码和限制网络访问,保护Redis服务器的安全。 #### 五...

    redis源码日志(源码分析)

    "redis源码日志(源码分析)"是针对Redis源码进行深入剖析的学习资料,旨在帮助开发者理解如何通过源码实现Redis的高并发处理能力和海量数据存储功能。 在Redis中,高并发主要依赖于以下几点: 1. **单线程模型**...

    Redis深度历险:核心原理和应用实践 全部代码实现

    redis基本命令 Redis深度历险:核心原理和应用实践 全部代码实现 1.线程10模型,md 11.RedisDistributeLock.md 12.md 15.Redis主从同步,md 17.Redis Codis.md 18.Redis Cluster.md 19.Redis info.md 2.通信协议.md 3...

    在ASP.NET MVC中使用Redis 的Demo:通过Redis实现用户登陆,并保持登陆状态。

    在ASP.NET MVC框架中,Redis常被用作一个高效的缓存和会话存储解决方案,以提升网站性能并处理用户登录状态。本示例将详细解释如何利用Redis来实现在ASP.NET MVC应用程序中用户登录功能,并保持用户的登录状态,同时...

    Redis命令实践 085720.zip

    1. 下载并安装Redis:https://redis.io/download 2. 启动Redis服务:在命令行中输入redis-server 3. 连接Redis客户端:在命令行中输入redis-cli 基本命令: 1. SET key value:设置键值对 2. GET key:获取键对应...

    Ansible PlayBook Redis 单机版

    例如,`redis.conf.j2`可能包含Redis的默认配置,如端口、日志路径等,而`redis.service`定义了Redis服务的启动和停止命令。 此外,为了使Ansible能正确执行PlayBook,你需要确保Ansible的主机清单(`hosts.ini`)...

Global site tag (gtag.js) - Google Analytics