注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Redis本质论(Redis: under the hood 译文)[2]  

2011-07-31 11:14:29|  分类: Linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Back up to main()

         如果在配置中指定服务器为守护进程,Redis现在就会将pid写入pid文件(路径是可以配置的,默认为/var/run/redis.pid)

         此时,服务器已经启动了,Redis将会这个情况记录到日志中。但是在Redis完全准备好之前还有一些要做的。

Restoring data

         如果有一个AOF或是一个数据库dump文件(比如,dump.rdb),它将会被载入,这些文件是前一个session的保存数据(如果都存在,优先保存AOF)

// redis.c:1452

if (server.appendonly) {

    if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK)

        redisLog(REDIS_NOTICE,"DB loaded from append only file: %ld seconds",

time(NULL)-start);

} else {

    if (rdbLoad(server.dbfilename) == REDIS_OK)

        redisLog(REDIS_NOTICE,"DB loaded from disk: %ld seconds",time(NULL)-start);

}

         现在服务器可以开始接受请求了。

Event loop setup

         Redis在每次进入事件循环都会注册一个回调函数beforeSleep()(因为进程当它在等待事件时,本质上是在休眠)。beforeSleep()会做两件事:如果启用了虚拟内存系统,它会处理请求中被交换到磁盘上keys,并且它会将AOF写入磁盘。写AOF的操作由flushAppendOnlyFile函数处理。这个函数封装了许多关于flush AOF的缓冲区的精巧逻辑(写入的频率由用户配置决定)

Entering the event loop

         Redis现在调用使用参数server.el(这个成员指向一个aeEventLoop),aeMain()函数进入事件主循环,在每次循环中如果有任何时间(比如,server cron)事件或是文件事件,它们的相应的注册的回调函数会被调用。aeProcessEvent()函数对将时间事件使用自定义逻辑处理,而文件事件由底层的epollkqueue或是select I/O事件通知系统处理封装到一起。

         因为Redis需要既需要处理时间事件也需要处理I/O事件,它实现了一个自定义的事件polling循环,aeMain()。通过查看是否有定时事件需要处理,或是有I/O事件通知,当这些事件处理完成后,Redis就会休眠,而不会一直循环消耗CPU

Processing a request & returning a response

         我们现在在Redis的事件主循环中,在一个端口上监听并等待用户连接。现在是时候来看Redis是如何处理一个命令请求的了。

Handling a new connection

         回到initServer()Redis注册了acceptHandler()回调函数,当有服务器正在监听的socket文件描述符上有相应的I/O事件发生(比如,一个socket有数据要读或写)acceptHandler()创建了一个client对应,指向一个redisClient,这个结构在redis.h中定义,它表示一个新的client连接。

// networking.c:347

cfd = anetAccept(server.neterr, fd, cip, &cport);

if (cfd == AE_ERR) {

    redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);

    return;

}

redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);

if ((c = createClient(cfd)) == NULL) {

    redisLog(REDIS_WARNING,"Error allocating resoures for the client");

    close(cfd); /* May be already closed, just ingore errors */

    return;

}

         createClient()用于给client对象分配空间并初始化,它默认选择数据库0(因为一个服务器至少有一个数据库),它与client对象中的文件描述符与相连,这个文件描述符由accept(2)产生。其它的标志和成员被初始化后,最后client对象会被加到server.clients这个全局的clients列表中。RediscreateClients()函数中所做的关键是注册一个事件回调函数readQueryFromClient(),它处理当client有数据可读时的逻辑。

// networking.c:20

if (aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c) == AE_ERR)

{

    close(fd);

    zfree(c);

    return NULL;

}

Reading a command from a client

         readQueryFromClient()client有一个命令请求时由事件主循环调用。(当你用GDB调试时,这是一个设置断点的好位置)。它尽可能地读取命令内容——一次最最多1024字节——到一个临时缓冲区中,这个然后将缓冲区中的内容追加到一个因client而异的请求(query)缓冲区中。这使得Redis可以处理payload(命令名加上参数)大于1024字节的命令,或是因为I/O的原因,要将它分成多个读事件。然后它调用processInputBuffer(),将client对象作为一个参数传入。

// networking.c:754

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {

    redisClient *c = (redisClient*) privdata;

    char buf[REDIS_IOBUF_LEN];

    int nread;

    // ...

 

    nread = read(fd, buf, REDIS_IOBUF_LEN);

    // ...

    if (nread) {

        size_t oldlen = sdslen(c->querybuf);

        c->querybuf = sdscatlen(c->querybuf, buf, nread);

        c->lastinteraction = time(NULL);

        /* Scan this new piece of the query for the newline. We do this

         * here in order to make sure we perform this scan just one time

         * per piece of buffer, leading to an O(N) scan instead of O(N*N) */

        if (c->bulklen == -1 && c->newline == NULL)

            c->newline = strchr(c->querybuf+oldlen,'\n');

    } else {

        return;

    }

    Processinputbuffer(c);

}

         processInputBuffer()将从client得到的原始的请求解析成Redis命令的参数,它首先要应付clientB{L,R}POP命令堵塞的可能,如果是这种情况,就放弃。函数然后将原始请求缓冲区解析成参数,为每个参数创建Redis字符串对象并将它们保存到client对象的数组中。请求采用的是Redis协议。processInputBuffer()实际是一个的协议解析器,调用processCommand完整解析请求。让人糊涂的是,源码中的注释写到解析“multi bulk command type”,另一种可用的协议是可以使用如MSET这样的命令,但现在所有的命令几乎都用Redis协议,它是二进制安全的并很容易解析和debug。现在是时候通过在client对象上调用processCommand,来真正执行client发出的命令。

         processCommand()使用client处得到的命令参数执行。在它真正执行命令之前,它会做许多检查——如果检查失败,它追加一个错误信息到clientreply 列表,并返回给调用者processInputBuffer()。在特殊处理QUIT命令后(为安全关闭client)后,processComamnd()commandTable中查找命令名。如果它是一个未知命令或是client错误地使用了命令,则它是一个错误。虽然不常用,但Redis可以配置在接受命令前要求用密码认证一个client,如果是这种情况,这时候Redis会检查client是否被认证,而决定是否发送错误。如果Redis配置中指定了最大内存使用量,它会在这里尝试释放内存(释放free列表上的对象并移除过期的keys),否则server超过了这个限制,它就不会处理设置DEDIS_CMD_DENYOOM标志的命令(主要是写命令,比如SETINCRPUSHHZADD,等等),同样,返回错误。最后一个检查是一个client在订阅模式下只能使用SUBSCRIBE或是UNSUBSCRIBE命令,否则,就是错误。如果所有的检查都通过了,就用调用call()函数使用client对象和命令参数来执行命令。

Executing the command and responding

         call(),从command对象的proc成员,得到redisCommandProc类型的一个函数指针,这个函数带一个client对象参数,Redis通过这种方式命令执行。

// redis.c:864

void call(redisClient *c, struct redisCommand *cmd) {

    long long dirty;

 

    dirty = server.dirty;

    cmd->proc(c);

    dirty = server.dirty-dirty;

}

// ...

         写命令,比如SETZADD,使服务器“dirty”,换句话说,服务器要标记内存页上的数据被改变了,这对自动保存进程来说是很重要的,自动保存进程要跟踪有多少keys在一定时间被改变,或是写入AOF。如果AOF被启用函数调用feedAppendOnlyFileclient的命令缓冲区写入AOF,所以命令是可以重现的,如果有从服务器连接,call()命令会发送这个命令到它们,使这个命令在从服务器上执行,参见replicationFeedSlaves(),类似的,如果有client使用了MONITOR命令,Redis会加上时间戳同步信息给主服务器,参见replicationFeedMonitors()

// redis.c:871 (call() cont.'d)

    // ...

    if (server.appendonly && dirty)

        feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc);

    if ((dirty || cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&

        listLength(server.slaves))

        replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);

    if (listLength(server.monitors))

        replicationFeedMonitors(server.monitors,c->db->id,c->argv,c->argc);

    server.stat_numcommands++;

}

         之后返回到调用函数processInputBuffer(),它会为接下来的命令处理而重置client对象。(译注:原文是processCommand(),不知道是笔误,还是版本问题)

         先前提到过,Redis命令处理过程要求它自己对响应client进行过程进行处理。在readQueryFromClient()退出后,Redis返回aeMain()中的事件循环,aeProcessEvent()会在写缓冲区中选择一个正在等待的响应,并将它复制到相应的client socket

         这就是结束了!响应发送后,clientserver都回到处理前的状态。

 

  评论这张
 
阅读(2305)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017