【注】 本文的源代码分析是基于redis-2.4.3版本的。
redisServer主要记录了server的全局信息,如数据库,连入的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
|
//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;
//用户命令的执行结果,会被异步的反馈给用户
……
};
|
下图描述了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代码结构很清晰,架构也是比较简单。
(1) Redis: under the hood:http://pauladamsmith.com/articles/redis-under-the-hood.html
(2) redis源代码分析 – event library :http://www.hoterran.info/redis_eventlibrary
(3) redis源代码分析 – protocol:http://www.hoterran.info/redis_protocol
原创文章,转载请注明: 转载自董的博客
本文链接地址: http://dongxicheng.org/nosql/redis-code-architecture/