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代碼結構很清晰,架構也是比較簡單。

相關文章
相關標籤/搜索