本文所引用的源碼全部來自Redis2.8.2版本。
Redis源碼整體運行流程的相關文件是:redis.h, redis.c, networking.c, ae.h, ae.c。
轉載請註明,本文出自:http://blog.csdn.net/acceptedxukai/article/details/17842119
Redis Server端處理Client請求的流程圖
main函數
main函數主要的功能爲:調用initServerConfig函數,進行默認的redisServer數據結構的參數初始化;調用daemonize函數,爲服務器開始守護進程,對於守護進行相關詳細信息見http://blog.csdn.net/acceptedxukai/article/details/8743189;調用initServer函數,初始化服務器;調用loadServerConfig函數,讀取Redis的配置文件,使用配置文件中的參數替換默認的參數值;調用aeMain函數,開啓事件循環,整個服務器開始工作。
initServer函數
該函數主要爲初始化服務器,需要初始化的內容比較多,主要有:
1、創建事件循環
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
2、創建TCP與UDP Server,啓動服務器,完成bind與listen
/* Open the TCP listening socket for the user commands. */
//server.ipfd是個int數組,啓動服務器,完成bind,listen
if (listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1);
/* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
if (server.sofd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
exit(1);
}
}
Redis2.8.2 TCP同時支持IPv4與IPv6,同時與之前版本的Redis不同,此版本支持多個TCP服務器,listenToPort函數主要還是調用anetTcpServer函數,完成socket()-->bind()-->listen(),下面詳細查看下TCPServer的創建,UDP直接忽略吧,我也不知道UDP具體用在哪。
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
//綁定bind
if (bind(s,sa,len) == -1) {
anetSetError(err, "bind: %s", strerror(errno));
close(s);
return ANET_ERR;
}
/* Use a backlog of 512 entries. We pass 511 to the listen() call because
* the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
* which will thus give us a backlog of 512 entries */
//監聽
if (listen(s, 511) == -1) {
anetSetError(err, "listen: %s", strerror(errno));
close(s);
return ANET_ERR;
}
return ANET_OK;
}
static int _anetTcpServer(char *err, int port, char *bindaddr, int af)
{
int s, rv;
char _port[6]; /* strlen("65535") */
struct addrinfo hints, *servinfo, *p;
snprintf(_port,6,"%d",port);
memset(&hints,0,sizeof(hints));
hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM;
//套接字地址用於監聽綁定
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
//可以加上hints.ai_protocol = IPPROTO_TCP;
/**getaddrinfo(const char *hostname, const char *servicename,
const struct addrinfo *hint,struct addrinfo **res);
hostname:主機名
servicename: 服務名
hint: 用於過濾的模板,僅能使用ai_family, ai_flags, ai_protocol, ai_socktype,其餘字段爲0
res:得到所有可用的地址
*/
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
anetSetError(err, "%s", gai_strerror(rv));
return ANET_ERR;
}
//輪流嘗試多個地址,找到一個允許連接到服務器的地址時便停止
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue;
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
//設置套接字選項setsockopt,採用地址複用
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
//bind, listen
if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;
goto end;
}
if (p == NULL) {
anetSetError(err, "unable to bind socket");
goto error;
}
error:
s = ANET_ERR;
end:
freeaddrinfo(servinfo);
return s;
}
//if server.ipfd_count = 0, bindaddr = NULL
int anetTcpServer(char *err, int port, char *bindaddr)
{
return _anetTcpServer(err, port, bindaddr, AF_INET);
}
3、將listen的端口加入到事件監聽中,進行監聽,由aeCreateFileEvent函數完成,其註冊的listen端口可讀事件處理函數爲acceptTcpHandler,這樣在listen端口有新連接的時候會調用acceptTcpHandler,後者在accept這個新連接,然後就可以處理後續跟這個客戶端連接相關的事件了。
/* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
//文件事件,用於處理響應外界的操作請求,事件處理函數爲acceptTcpHandler/acceptUnixHandler
//在networking.c
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");
acceptTcpHandler函數
上面介紹了,initServer完成listen端口後,會加入到事件循環中,該事件爲可讀事件,並記錄處理函數爲fe->rfileProc = acceptTcpHandler;該函數分兩步操作:用acceptTcpHandler接受這個客戶端連接;然第二部初始化這個客戶端連接的相關數據,將clientfd加入事件裏面,設置的可讀事件處理函數爲readQueryFromClient,也就是讀取客戶端請求的函數。
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
char cip[REDIS_IP_STR_LEN];
REDIS_NOTUSED(el);//無意義
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
//cfd爲accept函數返回的客戶端文件描述符,accept使服務器完成一個客戶端的鏈接
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
//將cfd加入事件循環並設置回調函數爲readQueryFromClient,並初始化redisClient
acceptCommonHandler(cfd,0);
}
第一步很簡單即完成accept,主要關注第二步acceptCommonHandler函數
static void acceptCommonHandler(int fd, int flags) {
redisClient *c;
if ((c = createClient(fd)) == NULL) {//創建新的客戶端
redisLog(REDIS_WARNING,
"Error registering fd event for the new client: %s (fd=%d)",
strerror(errno),fd);
close(fd); /* May be already closed, just ignore errors */
return;
}
/* If maxclient directive is set and this is one client more... close the
* connection. Note that we create the client instead to check before
* for this condition, since now the socket is already set in non-blocking
* mode and we can send an error for free using the Kernel I/O */
//當前連接的客戶端數目大於服務器最大運行的連接數,則拒絕連接
if (listLength(server.clients) > server.maxclients) {
char *err = "-ERR max number of clients reached\r\n";
/* That's a best effort error message, don't check write errors */
if (write(c->fd,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */
}
server.stat_rejected_conn++;
freeClient(c);
return;
}
server.stat_numconnections++;
c->flags |= flags;
}
createClient函數
redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(redisClient));
/* passing -1 as fd it is possible to create a non connected client.
* This is useful since all the Redis 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. */
/**
因爲 Redis 命令總在客戶端的上下文中執行,
有時候爲了在服務器內部執行命令,需要使用僞客戶端來執行命令
在 fd == -1 時,創建的客戶端爲僞終端
*/
if (fd != -1) {
//下面三個都是設置socket屬性
anetNonBlock(NULL,fd);//非阻塞
anetEnableTcpNoDelay(NULL,fd);//no delay
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive);//keep alive
//創建一個accept fd的FileEvent事件,事件的處理函數是readQueryFromClient
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
}
selectDb(c,0);//默認選擇第0個db, db.c
c->fd = fd;//文件描述符
c->name = NULL;
c->bufpos = 0;//將指令結果發送給客戶端的字符串長度
c->querybuf = sdsempty();//請求字符串初始化
c->querybuf_peak = 0;//請求字符串頂峯時的長度值
c->reqtype = 0;//請求類型
c->argc = 0;//參數個數
c->argv = NULL;//參數內容
c->cmd = c->lastcmd = NULL;//操作指令
c->multibulklen = 0;//塊個數
c->bulklen = -1;//每個塊的長度
c->sentlen = 0;
c->flags = 0;//客戶類型的標記,比較重要
c->ctime = c->lastinteraction = server.unixtime;
c->authenticated = 0;
c->replstate = REDIS_REPL_NONE;
c->reploff = 0;
c->repl_ack_off = 0;
c->repl_ack_time = 0;
c->slave_listening_port = 0;
c->reply = listCreate();//存放服務器應答的數據
c->reply_bytes = 0;
c->obuf_soft_limit_reached_time = 0;
listSetFreeMethod(c->reply,decrRefCountVoid);
listSetDupMethod(c->reply,dupClientReplyValue);
c->bpop.keys = dictCreate(&setDictType,NULL);//下面三個參數在list數據阻塞操作時使用
c->bpop.timeout = 0;
c->bpop.target = NULL;
c->io_keys = listCreate();
c->watched_keys = listCreate();//事務命令CAS中使用
listSetFreeMethod(c->io_keys,decrRefCountVoid);
c->pubsub_channels = dictCreate(&setDictType,NULL);
c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
// 如果不是僞客戶端,那麼將客戶端加入到服務器客戶端列表中
if (fd != -1) listAddNodeTail(server.clients,c);//添加到server的clients鏈表
initClientMultiState(c);//初始化事務指令狀態
return c;
}
客戶端的請求指令字符串始終存放在querybuf中,在對querybuf解析後,將指令參數的個數存放在argc中,具體的指令參數存放在argv中;但是服務器應答的結果有兩種存儲方式:buf字符串、reply列表。
readQueryFromClient函數
readQueryFromClient函數用來讀取客戶端的請求命令行數據,並調用processInputBuffer函數依照redis通訊協議對數據進行解析。服務器使用最原始的read函數來讀取客戶端發送來的請求命令,並將字符串存儲在querybuf中,根據需要對querybuf進行擴展。
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = (redisClient*) privdata;
int nread, readlen;
size_t qblen;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
server.current_client = c;
readlen = REDIS_IOBUF_LEN; //1024 * 16
/* If this is a multi bulk request, and we are processing a bulk reply
* that is large enough, try to maximize the probability that the query
* buffer contains exactly the SDS string representing the object, even
* at the risk of requiring more read(2) calls. This way the function
* processMultiBulkBuffer() can avoid copying buffers to create the
* Redis Object representing the argument. */
if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
&& c->bulklen >= REDIS_MBULK_BIG_ARG)
{
int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);
if (remaining < readlen) readlen = remaining;
}
qblen = sdslen(c->querybuf);
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
//對querybuf的空間進行擴展
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
//讀取客戶端發來的操作指令
nread = read(fd, c->querybuf+qblen, readlen);
if (nread == -1) {
if (errno == EAGAIN) {
nread = 0;
} else {
redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));
freeClient(c);
return;
}
} else if (nread == 0) {
redisLog(REDIS_VERBOSE, "Client closed connection");
freeClient(c);
return;
}
if (nread) {
//改變querybuf的實際長度和空閒長度,len += nread, free -= nread;
sdsIncrLen(c->querybuf,nread);
c->lastinteraction = server.unixtime;
if (c->flags & REDIS_MASTER) c->reploff += nread;
} else {
server.current_client = NULL;
return;
}
//客戶端請求的字符串長度大於服務器最大的請求長度值
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
sds ci = getClientInfoString(c), bytes = sdsempty();
bytes = sdscatrepr(bytes,c->querybuf,64);
redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
sdsfree(ci);
sdsfree(bytes);
freeClient(c);
return;
}
//解析請求
processInputBuffer(c);
server.current_client = NULL;
}
processInputBuffer函數主要用來處理請求的解析工作,redis有兩種解析方式;行指令解析與多重指令解析,行指令解析直接忽略,下面詳解多重指令解析。
void processInputBuffer(redisClient *c) {
/* Keep processing while there is something in the input buffer */
while(sdslen(c->querybuf)) {
/* Immediately abort if the client is in the middle of something. */
if (c->flags & REDIS_BLOCKED) return;
/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
* written to the client. Make sure to not let the reply grow after
* this flag has been set (i.e. don't process more commands). */
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
/* Determine request type when unknown. */
//當請求類型未知時,先確定屬於哪種請求
if (!c->reqtype) {
if (c->querybuf[0] == '*') {
c->reqtype = REDIS_REQ_MULTIBULK;//多重指令解析
} else {
c->reqtype = REDIS_REQ_INLINE;//按行解析
}
}
if (c->reqtype == REDIS_REQ_INLINE) {
if (processInlineBuffer(c) != REDIS_OK) break;
} else if (c->reqtype == REDIS_REQ_MULTIBULK) {
if (processMultibulkBuffer(c) != REDIS_OK) break;
} else {
redisPanic("Unknown request type");
}
/* Multibulk processing could see a <= 0 length. */
if (c->argc == 0) {
resetClient(c);
} else {
/* Only reset the client when the command was executed. */
//執行相應指令
if (processCommand(c) == REDIS_OK)
resetClient(c);
}
}
}
多重指令解析的處理函數爲processMultibulkBuffer,下面先簡單介紹下Redis的通訊協議
以下是這個協議的一般形式:
*< 參數數量 > CR LF
$< 參數 1 的字節數量 > CR LF
< 參數 1 的數據 > CR LF
...
$< 參數 N 的字節數量 > CR LF
< 參數 N 的數據 > CR LF
舉個例子,以下是一個命令協議的打印版本:
*3
$3
SET
$3
foo
$3
bar
這個命令的實際協議值如下:
"*3\r\n$3\r\nSET\r\n$3\r\foo\r\n$3\r\bar\r\n"
/**
例:querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
*/
int processMultibulkBuffer(redisClient *c) {
char *newline = NULL;
int pos = 0, ok;
long long ll;
if (c->multibulklen == 0) {//參數數目爲0,表示這是新的請求指令
/* The client should have been reset */
redisAssertWithInfo(c,NULL,c->argc == 0);
/* Multi bulk length cannot be read without a \r\n */
newline = strchr(c->querybuf,'\r');
if (newline == NULL) {
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big mbulk count string");
setProtocolError(c,0);
}
return REDIS_ERR;
}
/* Buffer should also contain \n */
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
return REDIS_ERR;
/* We know for sure there is a whole line since newline != NULL,
* so go ahead and find out the multi bulk length. */
redisAssertWithInfo(c,NULL,c->querybuf[0] == '*');
//將字符串轉爲long long整數,轉換得到的結果存到ll中,ll就是後面參數的個數
ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
if (!ok || ll > 1024*1024) {
addReplyError(c,"Protocol error: invalid multibulk length");
setProtocolError(c,pos);
return REDIS_ERR;
}
pos = (newline-c->querybuf)+2;//跳過\r\n
if (ll <= 0) {//參數個數小於0,表示後面的參數數目大於等於絕對值ll
/** s = sdsnew("Hello World");
* sdsrange(s,1,-1); => "ello World"
*/
sdsrange(c->querybuf,pos,-1);//querybuf="$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
return REDIS_OK;
}
c->multibulklen = ll;//得到指令參數個數
/* Setup argv array on client structure */
if (c->argv) zfree(c->argv);
c->argv = zmalloc(sizeof(robj*) * c->multibulklen);//申請參數內存空間
}
redisAssertWithInfo(c,NULL,c->multibulklen > 0);
/**
開始抽取字符串
querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
pos = 4
*/
while(c->multibulklen) {
/* Read bulk length if unknown */
if (c->bulklen == -1) {//參數的長度爲-1,這裏用來處理每個參數的字符串長度值
/**newline = "\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"*/
newline = strchr(c->querybuf+pos,'\r');
if (newline == NULL) {
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big bulk count string");
setProtocolError(c,0);
}
break;
}
/* Buffer should also contain \n */
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
break;
//每個字符串以$開頭,後面的數字表示其長度
if (c->querybuf[pos] != '$') {
addReplyErrorFormat(c,
"Protocol error: expected '$', got '%c'",
c->querybuf[pos]);
setProtocolError(c,pos);
return REDIS_ERR;
}
//得到字符串的長度值,ll
ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
if (!ok || ll < 0 || ll > 512*1024*1024) {
addReplyError(c,"Protocol error: invalid bulk length");
setProtocolError(c,pos);
return REDIS_ERR;
}
//pos = 8
pos += newline-(c->querybuf+pos)+2;//跳過\r\n "SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"
if (ll >= REDIS_MBULK_BIG_ARG) {//字符串長度超過1024*32,需要擴展
size_t qblen;
/* If we are going to read a large object from network
* try to make it likely that it will start at c->querybuf
* boundary so that we can optimize object creation
* avoiding a large copy of data. */
/**
sdsrange(querybuf,pos,-1)是將[pos,len-1]之間的字符串使用memmove前移,
然後後面的直接截斷
*/
sdsrange(c->querybuf,pos,-1);//"SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"
pos = 0;
qblen = sdslen(c->querybuf);
/* Hint the sds library about the amount of bytes this string is
* going to contain. */
if (qblen < ll+2)//這裏只會到最後一個字符串纔可能爲True,並且數據不完整,數據不完整是由於redis使用非阻塞的原因
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
}
c->bulklen = ll;
}
/* Read bulk argument */
//讀取參數,沒有\r\n表示數據不全,也就是說服務器接收到的數據不完整
if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
/* Not enough data (+2 == trailing \r\n) */
break;
} else {//數據完整
/* Optimization: if the buffer contains JUST our bulk element
* instead of creating a new object by *copying* the sds we
* just use the current sds string. */
if (pos == 0 &&
c->bulklen >= REDIS_MBULK_BIG_ARG &&
(signed) sdslen(c->querybuf) == c->bulklen+2)
{//數據剛好完整,那麼就直接使用c->querybuf,然後清空querybuf,注意這裏只可能在最後一個字符串纔可能出現
c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf);
sdsIncrLen(c->querybuf,-2); /* remove CRLF */
c->querybuf = sdsempty();
/* Assume that if we saw a fat argument we'll see another one
* likely... */
c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2);
pos = 0;
} else {
//抽取出具體的字符串,比如SET,建立一個stringObject
c->argv[c->argc++] =
createStringObject(c->querybuf+pos,c->bulklen);
pos += c->bulklen+2;//跳過\r\n
}
c->bulklen = -1;
c->multibulklen--;
}
}
/**
由於採用的是非阻塞讀取客戶端數據的方式,那麼如果c->multibulklen != 0,那麼就表示
數據沒有接收完全,首先需要將當前的querybuf數據截斷
*/
/* Trim to pos */
if (pos) sdsrange(c->querybuf,pos,-1);
/* We're done when c->multibulk == 0 */
if (c->multibulklen == 0) return REDIS_OK;
/* Still not read to process the command */
return REDIS_ERR;
}
processCommand與call函數
客戶端指令解析完之後,需要執行該指令,執行指令的兩個函數爲processCommand與call函數,這兩個函數除了單純的執行指令外,還做了許多其他的工作,這裏不詳解,看代碼僅僅找到指令如何執行還是很簡單的。
指令執行完之後,需要將得到的結果集返回給客戶端,這部分是如何工作的,下面開始分析。
在networking.c中可以發現許多以addRelpy爲前綴的函數名,這些函數都是用來處理各種不同類型的結果的,我們以典型的addReply函數爲例,進行分析。
addReply函數
該函數第一步工作就是調用prepareClientToWrite函數爲客戶端創建一個寫文件事件,事件的處理函數即將結果集發送給客戶端的函數爲sendReplyToClient.
int prepareClientToWrite(redisClient *c) {
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
if ((c->flags & REDIS_MASTER) &&
!(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
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;
}
第二步,就是根據相應的條件,將得到的結果rboj數據存儲到buf中或者reply鏈表中。對於存儲的策略:redis優先將數據存儲在固定大小的buf中,也就是redisClient結構體buf[REDIS_REPLY_CHUNK_BYTES]裏,默認大小爲16K。如果有數據沒有發送完或c->buf空間不足,就會放到c->reply鏈表裏面,鏈表每個節點都是內存buf,後來的數據放入最後面。具體的處理函數爲_addReplyToBuffer和_addReplyStringToList兩個函數。
void addReply(redisClient *c, robj *obj) {
if (prepareClientToWrite(c) != REDIS_OK) return;
/* This is an important place where we can avoid copy-on-write
* when there is a saving child running, avoiding touching the
* refcount field of the object if it's not needed.
*
* If the encoding is RAW and there is room in the static buffer
* we'll be able to send the object to the client without
* messing with its page. */
if (obj->encoding == REDIS_ENCODING_RAW) {//字符串類型
//是否能將數據追加到c->buf中
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);//添加到c->reply鏈表中
} else if (obj->encoding == REDIS_ENCODING_INT) {//整數類型
/* Optimization: if there is room in the static buffer for 32 bytes
* (more than the max chars a 64 bit integer can take as string) we
* avoid decoding the object and go for the lower level approach. */
//追加到c->buf中
if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {
char buf[32];
int len;
len = ll2string(buf,sizeof(buf),(long)obj->ptr);//整型轉string
if (_addReplyToBuffer(c,buf,len) == REDIS_OK)
return;
/* else... continue with the normal code path, but should never
* happen actually since we verified there is room. */
}
obj = getDecodedObject(obj);//64位整數,先轉換爲字符串
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
decrRefCount(obj);
} else {
redisPanic("Wrong obj->encoding in addReply()");
}
}
/**
Server將數據發送給Client,有兩種存儲數據的緩衝形式,具體參見redisClient結構體
1、Response buffer
int bufpos; //回覆
char buf[REDIS_REPLY_CHUNK_BYTES]; //長度爲16 * 1024
2、list *reply;
unsigned long reply_bytes; Tot bytes of objects in reply list
int sentlen; 已發送的字節數
如果已經使用reply的形式或者buf已經不夠存儲,那麼就將數據添加到list *reply中
否則將數據添加到buf中
*/
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos;//計算出c->buf的剩餘長度
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;
/* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return REDIS_ERR;
/* Check that the buffer has enough space available for this string. */
if (len > available) return REDIS_ERR;
//回覆數據追加到buf中
memcpy(c->buf+c->bufpos,s,len);
c->bufpos+=len;
return REDIS_OK;
}
/**
1、如果鏈表長度爲0: 新建一個節點並直接將robj追加到鏈表的尾部
2、鏈表長度不爲0: 首先取出鏈表的尾部節點
1)、尾部節點的字符串長度 + robj中ptr字符串的長度 <= REDIS_REPLY_CHUNK_BYTES:
將robj->ptr追加到尾節點的tail->ptr後面
2)、反之: 新建一個節點並直接將robj追加到鏈表的尾部
*/
void _addReplyObjectToList(redisClient *c, robj *o) {
robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
//鏈表長度爲0
if (listLength(c->reply) == 0) {
incrRefCount(o);//增加引用次數
listAddNodeTail(c->reply,o);//添加到鏈表末尾
c->reply_bytes += zmalloc_size_sds(o->ptr); //計算o->ptr的佔用內存大小
} else {
//取出鏈表尾中的數據
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
// 如果最後一個節點所保存的回覆加上新回覆內容總長度小於等於 REDIS_REPLY_CHUNK_BYTES
// 那麼將新回覆追加到節點回復當中。
if (tail->ptr != NULL &&
sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
{
c->reply_bytes -= zmalloc_size_sds(tail->ptr);
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
c->reply_bytes += zmalloc_size_sds(tail->ptr);
} else {//爲新回覆單獨創建一個節點
incrRefCount(o);
listAddNodeTail(c->reply,o);
c->reply_bytes += zmalloc_size_sds(o->ptr);
}
}
// 如果突破了客戶端的最大緩存限制,那麼關閉客戶端
asyncCloseClientOnOutputBufferLimitReached(c);
}
sendReplyToClient函數
終於到了最後一步,把c->buf與c->reply中的數據發送給客戶端即可,發送同樣使用的是最原始的write函數。發送完成之後,redis將當前客戶端釋放,並且刪除寫事件,代碼比較簡單,不詳細解釋。
小結
本文粗略的介紹了Redis整體運行的流程,從服務器的角度,介紹Redis是如何初始化,創建socket,接收客戶端請求,解析請求及指令的執行,反饋執行的結果集給客戶端等。如果讀者想更深入的瞭解Redis的運行機制,需要親自閱讀源碼,本文可以用作參考。同時也是學習linux socket編程的好工具,原本簡簡單單的socket->bind->listen->accept->read->write也可以用來做許多高效的業務,是Linux socket學習的不二選擇。