順風車運營研發團隊 閆昌
一. Redis編譯安裝時指定參數, 防止gdb時被優化, 在make時, 增長參數nooptredis
make noopt
二. 客戶端通信協議數據庫
1.客戶端與服務端的通信協議是創建在TCP之上的
2.Redis指定了RESP(Redis SerializationProtocol, Redis序列化協議)實現客戶端與服務端的正常交互
3.命令格式: *<參數數量>rn$ <參數1的字節數量>\r\n參數1\r\n$<參數2的字節數量>rn參數2rn
例如: set hello world *3rn$5\r\nset\r\n$5rnworldrn
4.返回結果
狀態回覆: 在RESP中第一個字節爲"+"
錯誤回覆: 在RESP中第一個字節爲"-"
整數回覆: 在RESP中第一個字符爲":"
字符串回覆: 在RESP中第一個字符爲"$"
多條字符串回覆: 在RESP中第一個字節爲"*"app
redis-cli只能看到最終的執行結果, 由於redis-cli自己就是按照RESP進行告終果解析, 因此看不到中間結果
三. set命令執行流程: set hello worldsocket
1.redis-server啓動後, 會進行server.c的main主函數裏
2.當初始化服務端參數以後, 會進入aeMain函數裏
3.aeMain函數裏while循環調用aeProcessEvents, aeProcessEvents裏調用aeApiPoll等待epoll_wait返回可用fd
4.當有客戶端鏈接請求過來以後, 會調用networking.c的createClient函數, 此函數裏調用aeCreateFileEvent, 將第四個參數指定爲: readQueryFromClient
5.當客戶端鏈接成功以後, aeApiPoll返回可用的fd後, 進入aeProcessEvents的for (j = 0; j < numevents; j++)循環, 在此例中, 進入rfileProc函數, 此時的rfileProc爲上一步的readQueryFromClient方法
6.readQueryFromClient函數, ---- networking.c函數
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { client *c = (client*) privdata; int nread, readlen; size_t qblen; ... readlen = PROTO_IOBUF_LEN;//16K ... ... qblen = sdslen(c->querybuf);//此時的值爲0 if (c->querybuf_peak < qblen) c->querybuf_peak = qblen; c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);//給querybuf分配16K的空間 nread = read(fd, c->querybuf+qblen, readlen);//從fd中讀取16K的內容, 此時的fd爲客戶端向服務端的socket套接字 if (nread == -1) { if (errno == EAGAIN) {//errno是全局變量, 在read函數後生成的變量, 此變量用一次以後就會消失, 此時EAGAIN表示讀取失敗 return; } else { serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno)); freeClient(c); return; } } else if (nread == 0) { serverLog(LL_VERBOSE, "Client closed connection"); freeClient(c); return; } else if (c->flags & CLIENT_MASTER) { c->pending_querybuf = sdscatlen(c->pending_querybuf, c->querybuf+qblen,nread); } sdsIncrLen(c->querybuf,nread);//擴展querybuf的長度, 使其長度爲讀取出的命令的長度 c->lastinteraction = server.unixtime; .... .... if (!(c->flags & CLIENT_MASTER)) { processInputBuffer(c);//處理命令 } else { size_t prev_offset = c->reploff; processInputBuffer(c); size_t applied = c->reploff - prev_offset; if (applied) { replicationFeedSlavesFromMasterStream(server.slaves, c->pending_querybuf, applied); sdsrange(c->pending_querybuf,applied,-1); } } }
void processInputBuffer(client *c) { server.current_client = c; while(sdslen(c->querybuf)) { ...... ...... if (c->argc == 0) { resetClient(c); } else { if (processCommand(c) == C_OK) {//這裏進入執行命令函數時 if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) { c->reploff = c->read_reploff - sdslen(c->querybuf); } if (!(c->flags & CLIENT_BLOCKED) || c->btype != BLOCKED_MODULE) resetClient(c); } if (server.current_client == NULL) break; } } server.current_client = NULL; }
8.processCommand函數(server.c)的最後調用了call(server.c)方法oop
9.call方法裏調用了c->cmd->proc方法, 而這個方法就是t_string.c的setCommand方法優化
c->cmd->proc(c);
10.setCommand方法(t_string.c)裏調用了setGenericCommand方法
11.setGenericCommand方法調用了setKey方法
12.setKey方法調用了dbAdd或dbOverwrite方法, 將key和val設置到redis數據庫spa
四. 執行流程unix