Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
由于工作需要,需要深入研究此开源代码用于选型及设计代码,先从main函数开始吧。
参考版本:Redis-3.2.3
首先从main函数开始@server.c
int main(int argc, char **argv) {
// 全局变量 struct redisServer server; /* server global state */ 初始化
initServerConfig();
/* Check if we need to start in redis-check-rdb mode. We just execute
* the program main. However the program is part of the Redis executable
* so that we can easily execute an RDB check on loading errors. */
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv);
// 重置server.saveparams
resetServerSaveParams();
// 加载redis.conf作为配置文件
loadServerConfig(configfile,options);
// 决定是否以daemon方式启动redis
server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background)
daemonize();
// 初始化服务器--主要工作是createSharedObjects();及aeCreateEventLoop创建事件Loop
initServer();
// 判断是否需要从aof文件或者rdb装载数据
if (!server.sentinel_mode) {
loadDataFromDisk();
...
}else{
sentinelIsRunning();
}
// 设置每次进入事件处理函数之前需要执行的函数
aeSetBeforeSleepProc(server.el,beforeSleep);
// 进入事件循环主函数,永不退出,除非服务被终止
aeMain(server.el);
// main函数结束、服务器退出
aeDeleteEventLoop(server.el);
return 0;
}
下面对子过程进行详细分析:
1、客户端连接处理
initServer()@server.c 这个函数中会调用anetTcpServer和anetUnixServer,分别建立tcp端口和unix域套接字的监听。
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
{
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
{
// 建立TCP套接字监听
s = socket(p->ai_family,p->ai_socktype,p->ai_protocol);
anetSetReuseAddr(err,s);
anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog);
return s;
}
以上只是设置要监听的端口、地址、和地址族,再调用anetListen()函数绑定地址并监听端口,这些工作完成后
anetTcpServer 函数返回,并将创建的套接字复制给server.ipfd.
listenToPort(server.port,server.ipfd,&server.ipfd_count)
-->
int listenToPort(int port, int *fds, int *count) {
fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
server.tcp_backlog);
}
前面提到有一个EventLoop的事件队列、这个fd将会在此被使用。
void initServer(void) {
// 建立主事件队列--EventLoop
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
...
/* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
serverPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
}
首先,从eventLoop的event这个aeFileEvent数组里,取出当前fd对应的acFileEvent,
主要是为了在下边给它设置对应事件的处理函数;即根据传入的mask来判断是哪一类事件。
这里就是 server.ipfd 加入事件监控中、当发生AE_READABLE可读事件时则触发 acceptTcpHandler 函数。
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
aeFileEvent *fe = &eventLoop->events[fd];
// 加入异步事件监控中
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
fe->mask |= mask;
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
fe->clientData = clientData;
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
此时监控建立完毕、此时客户端一个新的连接到来,此时将会触发事件。
主循环事件函数:[email protected]
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
// 只要stop标志一直为0则循环永远运行下去
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
/* Process every pending time event, then every pending file event
* (that may be registered by time event callbacks just processed).
* Without special flags the function sleeps until some file event
* fires, or when the next time event occurs (if any).
*
* If flags is 0, the function does nothing and returns.
* if flags has AE_ALL_EVENTS set, all the kind of events are processed.
* if flags has AE_FILE_EVENTS set, file events are processed.
* if flags has AE_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
// 最重要的轮询函数!!!
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask); // 可读回调
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask); // 可写回调
}
processed++;
}
}
客户端新的连接则触发可读事件、即acceptTcpHandler函数
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd, max = MAX_ACCEPTS_PER_CALL; // 当前redis服务器的最大连接总数
char cip[NET_IP_STR_LEN];
while(max--) {
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", server.neterr);
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
// 接收新的连接后处理函数
acceptCommonHandler(cfd,0,cip);
}
}
利用anetTcpAccept产生新的fd并加入到事件队列中
static void acceptCommonHandler(int fd, int flags, char *ip) {
client *c = createClient(fd);
// 客户端数目限定
if (listLength(server.clients) > server.maxclients) {
char *err = "-ERR max number of clients reached\r\n";
..
}
}
-->
client *createClient(int fd) {
client *c = zmalloc(sizeof(client));
/* passing -1 as fd it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (fd != -1) {
anetNonBlock(NULL,fd);
anetEnableTcpNoDelay(NULL,fd);
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive);
// 给前面获取的客户端连接的套接字注册 AE_READABLE事件,并设置事件处理函数readQueryFromClient
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
}
...
}
这里结束后则 readQueryFromClient()便是redis处理客户端发送的命令的起始点了。