本文分析redis的基礎原理,暫不做深入分析,後續再詳細分析。我們從main函數開始。
int main(int argc, char **argv) {
initServerConfig();
initServer();
aeCreateFileEvent(server.el, server.fd, AE_READABLE, acceptHandler, NULL, NULL)
aeMain(server.el);
return 0;
}
下面就四個函數進行分析。
1 initServerConfig
static void initServerConfig() {
server.dbnum = REDIS_DEFAULT_DBNUM;
server.port = REDIS_SERVERPORT;
server.verbosity = REDIS_DEBUG;
server.maxidletime = REDIS_MAXIDLETIME;
server.saveparams = NULL;
server.logfile = NULL; /* NULL = log on standard output */
server.bindaddr = NULL;
server.glueoutputbuf = 1;
server.daemonize = 0;
server.pidfile = "/var/run/redis.pid";
server.dbfilename = "dump.rdb";
server.requirepass = NULL;
server.shareobjects = 0;
server.maxclients = 0;
ResetServerSaveParams();
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */
appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
/* Replication related */
server.isslave = 0;
server.masterhost = NULL;
server.masterport = 6379;
server.master = NULL;
server.replstate = REDIS_REPL_NONE;
}
該函數是對redis核心結構體server(struct redisServer server)進行初始化。
2 initServer
static void initServer() {
int j;
// 修改這兩個信號的處理函數爲忽略,即不處理,如果不設置但又收到這個信號,會導致進程退出
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
// clients列表,每次收到請求的時候會初始化一個client
server.clients = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
server.objfreelist = listCreate();
createSharedObjects();
// 創建一個事件循環的結構體
server.el = aeCreateEventLoop();
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
server.sharingpool = dictCreate(&setDictType,NULL);
server.sharingpoolsize = 1024;
// 啓動服務器
server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr);
for (j = 0; j < server.dbnum; j++) {
server.db[j].dict = dictCreate(&hashDictType,NULL);
server.db[j].expires = dictCreate(&setDictType,NULL);
server.db[j].id = j;
}
server.cronloops = 0;
server.bgsaveinprogress = 0;
server.lastsave = time(NULL);
server.dirty = 0;
server.usedmemory = 0;
server.stat_numcommands = 0;
server.stat_numconnections = 0;
server.stat_starttime = time(NULL);
// 增加一個定時器
aeCreateTimeEvent(server.el, 1000, serverCron, NULL, NULL);
}
我們主要看initServer函數中的anetTcpServer這個函數。
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "socket: %s\n", strerror(errno));
return ANET_ERR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr) {
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
close(s);
return ANET_ERR;
}
}
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 64) == -1) {
anetSetError(err, "listen: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
這個函數的邏輯就是經典的網絡socket編程流程。最後通過listen函數,啓動服務器。。
3 aeCreateFileEvent(server.el, server.fd, AE_READABLE, acceptHandler, NULL, NULL)
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
aeFileEvent *fe;
fe = zmalloc(sizeof(*fe));
if (fe == NULL) return AE_ERR;
fe->fd = fd;
fe->mask = mask;
fe->fileProc = proc;
fe->finalizerProc = finalizerProc;
fe->clientData = clientData;
fe->next = eventLoop->fileEventHead;
eventLoop->fileEventHead = fe;
return AE_OK;
}
aeCreateFileEvent函數在事件驅動那篇文章分析過,他就封裝一個結構體,然後在事件循環的時候註冊到事件驅動模塊(本版本是使用select函數)。這裏是註冊可讀事件,回調函數是acceptHandler。
4 aeMain
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
該函數在事件驅動文章也分析過,就是註冊事件到事件驅動模塊,然後開始等待事件發生,接着處理回調,根據3的分析,當有連接到來,會執行acceptHandler。
static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
char cip[128];
redisClient *c;
// 調用accept摘下一個請求
cfd = anetAccept(server.neterr, fd, cip, &cport);
// 創建一個client
if ((c = createClient(cfd)) == NULL) {
//
}
}
該函數摘下一個請求的節點,然後創建一個client處理該請求。
static redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(*c));
anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
if (!c) return NULL;
selectDb(c,0);
c->fd = fd;
c->querybuf = sdsempty();
c->argc = 0;
c->argv = NULL;
c->bulklen = -1;
c->sentlen = 0;
c->flags = 0;
c->lastinteraction = time(NULL);
c->authenticated = 0;
c->replstate = REDIS_REPL_NONE;
if ((c->reply = listCreate()) == NULL) oom("listCreate");
listSetFreeMethod(c->reply,decrRefCount);
listSetDupMethod(c->reply,dupClientReplyValue);
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c, NULL) == AE_ERR) {
freeClient(c);
return NULL;
}
if (!listAddNodeTail(server.clients,c)) oom("listAddNodeTail");
return c;
}
我們接着看aeCreateFileEvent(server.el, c->fd, AE_READABLE,readQueryFromClient, c, NULL)這一句,在accept的fd(和客戶端通信的fd)上註冊可讀事件(即等待客戶端發送數據過來),然後執行readQueryFromClient。readQueryFromClient函數主要是讀取客戶端發送過來的數據,然後調processCommand。
static int processCommand(redisClient *c) {
struct redisCommand *cmd;
long long dirty;
// 退出命令
if (!strcasecmp(c->argv[0]->ptr,"quit")) {
freeClient(c);
return 0;
}
// 根據客戶端的命令找到對應的命令配置
cmd = lookupCommand(c->argv[0]->ptr);
// ...
// 執行對應的處理函數
cmd->proc(c);
return 1;
}
上面函數根據用戶發送的命令執行對應的函數。redis的命令配置表爲。
static struct redisCommand cmdTable[] = {
{"get",getCommand,2,REDIS_CMD_INLINE},
...
};
假設客戶端發送的是get命令,則執行的函數爲getCommand。
static void getCommand(redisClient *c) {
// 讀取客戶端發送的數據
robj *o = lookupKeyRead(c->db,c->argv[1]);
addReply(c,o);
}
static void addReply(redisClient *c, robj *obj) {
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c, NULL)
}
給客戶端fd註冊可寫事件,準備給客戶端回覆信息。事件觸發時執行的函數是sendReplyToClient。
static void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = privdata;
//...
nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
}
發送回包給客戶端。
這就是redis啓動到處理客戶端信息的整體流程。