Redis源码研究—代码总体架构

【注】 本文的源代码分析是基于redis-2.4.3版本的。redis

 

1. Redis server基本数据结构

redisServer主要记录了server的全局信息,如数据库,连入的client,支持的全部操做,从配置文件中读取的配置信息等。数据库

1数据结构

2架构

3并发

4app

5异步

6socket

7函数

8spa

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//redis.h

 

struct redisServer {

 

  pthread_t mainthread; //主线程

 

  int port; //端口号

 

  char *bindaddr; //地址

 

  …….

 

  int ipfd; //主线程的文件描述符

 

  ……

 

  redisDb *db;

 

  ……

 

  list *clients; /* 当前接入的client列表 */

 

  dict *commands;             /*支持的全部操做*/

 

  

 

};

redisClient主要记录了某个接入客户端的状态信息,如客户端链接句柄,操做的数据库,发送的命令,返回的结果列表等。为每一个client创建一个这种数据结构,能够很方便的支持多用户并发访问。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

typedef struct redisClient {

 

  int fd;  //客户端链接句柄

 

  redisDb *db; //操做的数据库

 

  ……

 

  sds querybuf; //用户命令缓冲区

 

  int argc;

 

  robj **argv;

 

  struct redisCommand *cmd;

 

  int reqtype;

 

  int multibulklen;       /* number of multi bulk arguments left to read */

 

  long bulklen;           /* length of bulk argument in multi bulk request */

 

  list *reply; //用户命令的执行结果,会被异步的反馈给用户

 

  ……

 

};

2. Redis代码架构

下图描述了client与redis server的整个交互过程,图中只描述了代码关键路径。

(1)首先,从redis.c文件中的main函数开始:

Main函数首先调用:

1

initServerConfig();

该函数主要完成如下功能:设定默认的参数值,并读取配置文件redis.conf,若用户配置了某个参数,则用该参数值替换默认值。

接下来,调用:

1

initServer();

该函数主要对server进行初始化,初始化内容包括:

调用anetTcpServer函数建立socket server做为redis server,并将该server的句柄加到epoll/kqueue的监听队列中。一旦有client接入,便会对该client触发操做acceptTcpHandler,该操做是调用aeCreateFileEvent注册的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

…..

 

if (server.port != 0) {

 

  server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);

 

  if (server.ipfd == ANET_ERR) {

 

    redisLog(REDIS_WARNING, "Opening port %d: %s",

 

      server.port, server.neterr);

 

    exit(1);

 

  }

 

}

 

……

 

aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);

 

if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,

 

  acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");

 

  ……

【注】aeCreateFileEvent函数用于注册监听链接事件,即某个client向server发起链接或者发出一个命令后,会触发这个事件;而aeCreateTimeEvent函数用于注册定时任务serverCron,该函数每隔100 毫秒执行一次,主要进行一些后台处理,如:记日志,清除无效key,清除无效连接。

acceptTcpHandler函数会调用acceptCommonHander,而acceptCommonHander又会调用createClient来为该client建立一个redisClient对象….,最终,redis会根据用户输入的命令调用已经写好的命令执行函数,这些函数已经被写死,保存到一个全局只读表中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

struct redisCommand readonlyCommandTable[] = {

 

  {"get",getCommand,2,0,NULL,1,1,1},

 

  {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},

 

  {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},

 

  {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0},

 

  {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},

 

  ……

 

};

用户输入get命令,redis最终会调用getCommand函数,用户输入set命令,redis最终会调用setCommand函数…..。该表是在initServerConfig()函数中,被加载到一个hash table中的,以便于后面的查找:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//initServerConfig()

 

……

 

populateCommandTable();

 

……

 

// populateCommandTable()

 

int j;

 

int numcommands = sizeof(readonlyCommandTable)/sizeof(struct redisCommand);

 

for (j = 0; j < numcommands; j++) {

 

  struct redisCommand *c = readonlyCommandTable+j;

 

  int retval;

 

  retval = dictAdd(server.commands, sdsnew(c->name), c);

 

  assert(retval == DICT_OK);

 

}

 

}

(2)redis执行完用户的一个命令后,会将结果写入到redisClient对象中的reply list中,而sendReplyToClient函数会不断的从该list中数据,异步地发送给client。须要注意的是,sendReplyToClient函数也是经过aeCreateFileEvent注册的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

int _installWriteEvent(redisClient *c) {

 

  if (c->fd <= 0) return REDIS_ERR;

 

  if (c->bufpos == 0 && listLength(c->reply) == 0 &&

 

    (c->replstate == REDIS_REPL_NONE ||

 

      c->replstate == REDIS_REPL_ONLINE) &&

 

        aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,

 

          sendReplyToClient, c) == AE_ERR) return REDIS_ERR;

 

  return REDIS_OK;

 

}

 

void addReply(redisClient *c, robj *obj) {

 

  if (_installWriteEvent(c) != REDIS_OK) return;

 

  ……

 

}

上面只是粗略讲了一下代码架构,只算是抛砖引玉了,若是读者想更进一步了解redis代码架构,最好亲自读一下代码。整体而言,redis代码结构很清晰,架构也是比较简单。

相关文章
相关标签/搜索