single_node_architecture

第八章 Redis单节点体系结构(Single Node Architecture)

1 引言

在了解了Redis的数据结构、命令、和通信协议(RESP)之后,我们可以大致的学习一下Redis单节点的架构和处理过程。在这个单节点架构中,我们将简略的介绍命令的处理流程,以及在这个流程中的两个重要的关键点:“Event Loop”和“Redis是一个大型的哈希表”。

2 Redis单节点架构

在单节点模式下,Redis服务器主要有三个关键模块:Event Loop模块、哈希表模块,和命令表模块。这三个模块能搭建起Redis服务器的基本功能。当Redis客户端与Redis服务器端建立起连接后,Redis服务器通过Event Loop模块监听客户端发来的请求。因为Event Loop的基本原理是使用了操作系统提供的I/O多路复用技术(I/O Multiplexing),所以,Event Loop能高效的同时监听多个客户端的连接。当Event Loop收到一个命令后,它会从命令表中查询对应的处理流程。如果是插入操作的话,新数据会被插入到Redis的哈希表中;如果是删除命令,则该命令会从Redis的哈希表中删除数据;如果是查询命令,则该命令会从Redis的哈希表中查询数据。

图一 Redis单节点架构 图一 Redis单节点架构。

以下是一个具体的Redis服务器运行的步骤(该步骤略去了无关的内容)。

  1. 启动Redis服务器,读取配置文件、监听服务器网络端口。
  2. Redis客户端与Redis服务器建立连接;Redis服务器的Event Loop模块监听客户端发来的命令。
  3. 当Redis服务器收到一条命令后,解析该命令,并从命令表中查找对应的处理流程。
  4. 执行该流程。该命令可能是向哈希表中插入一条数据,也可能是删除一条数据,或者查询某些数据。
  5. Redis服务器将命令的执行结果返回给客户端。
  6. Redis服务器继续监听客户端,如果再次收到一条命令,则转第二步继续运行。

3 Event Loop模式

Event Loop是软件开发中常用的一种设计模式。它主要用于等待并分发消息或者事件。使用Event Loop的好处是它能使单线程的程序高效的同时等待和处理来自多个事件源的事件,而无需使用多线程来同时等待和处理多个可能被阻塞的事件源(blocking I/O event source)。对Linux编程或者I/O Multiplexing技术感兴趣的读者可参考Linux系统调用epoll()的用法。

下面是一些Redis中Event Loop的实现细节。在Redis服务器中,经初始化后,Redis会在main()函数中调用aeMain()进入Event Loop。此时,Redis服务器已经开始监听客户端的连接了。在aeMain()函数中,Redis一直不断的处理收到的事件。其中,这些事件可能是客户端发来的命令,也可能是定时器超时的事件(支持Key过期的功能(Key Expiration Feature)),也可能是在分布式模式下,其他节点发来的消息,也可能是数据复制的消息事件等。另外,也有可能是写事件,例如:向客户端返回消息,写日志等事件。总之,所有的事件都由这个Event Loop来分发处理。

Event Loop是一个无限的循环;它会反复的读取和处理新收到的事件,直到Redis退出。

// Redis 5.0.8 版本
// Main函数在server.c中
int main(int argc, char **argv) {
    ...
    aeMain(server.el);
    ...
}

// Event Loop的代码大部分在ae.h和ae.c中
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {  //不断的监听和处理事件
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

4 Redis是一个大型的哈希表

当Redis从Event Loop中收到一个从客户端发来的命令时,Redis会在aeProcessEvents函数中获取客户端发来的命令。当获得命令的名称后,Redis会从命令表中查询该命令对应的处理函数。最后,调用这个处理函数,并将结果发回给客户端。

Redis服务器是由结构体struct redisServer表达的。在该结构体中包含了很多字段;其中三个重要字段与本文的内容关系密切。db包含着Redis数据库中的主要数据,其中db->dict是一个哈希表,表示的是Redis数据库的所有的Key和Value数据;commands是Redis的命令表,从该表中查询命令对应的处理函数;el则是Redis服务器的Event Loop结构。

在初始化过程中,Redis将命令表加载入redisServer结构体中的commands字段。Redis命令以及其对应的处理函数则定义在redisCommandTable数组中。第一列是命令的名称,第二列是对用的处理函数。

// Redis 5.0.8 版本
// server.h 文件
struct redisServer {
    ...
    redisDb *db;
    dict *commands;             /* Command table */
    ...
    aeEventLoop *el;
    ...
}

// server.c 文件
struct redisCommand redisCommandTable[] = {
    {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
    {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
    ...
}

当Redis收到一个客户端的命令后,Redis会通过lookupCommand()函数查询命令表,找到对应的处理函数,并调用这个函数。这个处理函数则完成该命令的任务。如果是查询命令,则处理函数会从Redis哈希表中查找数据。如果是插入或者删除命令,则该处理函数会从Redis哈希表中插入或者删除数据。该处理函数是在call()函数中调用执行的。

// Redis 5.0.8 版本
// server.c
struct redisCommand *lookupCommand(sds name) {
    return dictFetchValue(server.commands, name);
}

// server.c中call函数调用命令对应的处理函数
void call(client *c, int flags) {
    ...
    c->cmd->proc(c);
    ...
}

我们再简单的介绍一下查询命令的处理过程。插入和删除命令的工作方式与查询命令类似。如果Redis收到了一个查询命令的话,Redis会在c->db中,通过传入的key值查找到对应的value,并将value返回给客户端。这里,c->db就是在redisServer结构体中定义的db;它包含了整个Redis服务器存放的所有的Key和Value。

这里仅给出Redis查询命令的处理流程。其他的命令的处理流程可查看对应的处理函数,它们定义在redisCommandTable表中。

// Redis 5.0.8 版本
// t_string.c 文件
int getGenericCommand(client *c) {
    robj *o;

    // 查询操作在lookupKeyReadOrReply函数中完成,函数返回值o就是key对应的value,
    // 查询操作的Key存放在c->argv[1]中。
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return C_OK;

    if (o->type != OBJ_STRING) {
        addReply(c,shared.wrongtypeerr);
        return C_ERR;
    } else {
        addReplyBulk(c,o);
        return C_OK;
    }
}

robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
    // 查询Key对应的Value;c->db是一个哈希表
    robj *o = lookupKeyRead(c->db, key);
    if (!o) addReply(c,reply);
    return o;
}

5 小节

本章简略的介绍了Redis单节点的体系结构,并指出了Redis服务器处理一个客户端命令流程中的关键点。Event Loop是较为重要的一个环节,也是Redis能够高效的支持多个客户端的关键所在。在数据存储方面,Redis其实就是一个大型的哈希表,每当Redis收到命令后,就会根据命令从这个哈希表中查询,插入或者删除数据。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.