華文網

Redis 記憶體管理與事件處理

1 Redis記憶體管理

Redis記憶體管理相關檔為zmalloc.c/zmalloc.h,其只是對C中記憶體管理函數做了簡單的封裝,遮罩了底層平臺的差異,並增加了記憶體使用情況統計的功能。

void *zmalloc(size_t size) {

// 多申請的一部分記憶體用於存儲當前分配了多少自己的記憶體

void *ptr = malloc(size+PREFIX_SIZE);

if (!ptr) zmalloc_oom_handler(size);

#ifdef HAVE_MALLOC_SIZE

update_zmalloc_stat_alloc(zmalloc_size(ptr));

return ptr;

#else

*((size_t*)ptr) = size;

// 記憶體分配統計

update_zmalloc_stat_alloc(size+PREFIX_SIZE);

return (char*)ptr+PREFIX_SIZE;

#endif

}

記憶體佈局圖示:

2 事件處理

Redis的事件類型分為時間事件和檔事件,檔事件也就是網路連接事件。時間事件的處理是在epoll_wait返回處理檔事件後處理的,每次epoll_wait的超時時間都是Redis最近的一個計時器時間。

Redis在進行事件處理前,首先會進行初始化,初始化的主要邏輯在main/initServer函數中。初始化流程主要做的工作如下:

設置信號回呼函數。

創建事件迴圈機制,

即調用epoll_create。

創建服務監聽埠,創建定時事件,並將這些事件添加到事件機制中。

void initServer(void) {

int j;

// 設置信號對應的處理函數

signal(SIGHUP, SIG_IGN);

signal(SIGPIPE, SIG_IGN);

setupSignalHandlers();

...

createSharedObjects();

adjustOpenFilesLimit();

// 創建事件迴圈機制,及調用epoll_create創建epollfd用於事件監聽

server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);

server.db = zmalloc(sizeof(redisDb)*server.dbnum);

/* Open the TCP listening socket for the user commands. */

// 創建監聽服務埠,socket/bind/listen

if (server.port != 0 &&

listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)

exit(1);

...

/* Create the Redis databases, and initialize other internal state. */

for (j = 0; j < server.dbnum;="" j++)="">

server.db[j].dict = dictCreate(&dbDictType,NULL);

server.db[j].expires = dictCreate(&keyptrDictType,NULL);

server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);

server.db[j].ready_keys = dictCreate(&setDictType,NULL);

server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);

server.db[j].eviction_pool = evictionPoolAlloc();

server.db[j].id = j;

server.db[j].avg_ttl = 0;

}

...

/* Create the serverCron() time event, that's our main way to process

* background operations. 創建定時事件 */

if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {

serverPanic("Can't create the serverCron time event.");

exit(1);

}

/* 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.");

}

}

// 將事件加入到事件機制中,調用鏈為 aeCreateFileEvent/aeApiAddEvent/epoll_ctl

if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,

acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");

/* Open the AOF file if needed. */

if (server.aof_state == AOF_ON) {

server.aof_fd = open(server.aof_filename,

O_WRONLY|O_APPEND|O_CREAT,0644);

if (server.aof_fd == -1) {

serverLog(LL_WARNING, "Can't open the append-only file: %s",

strerror(errno));

exit(1);

}

}

...

}

事件處理流程

事件處理函數鏈:aeMain / aeProcessEvents / aeApiPoll / epoll_wait。

常見的事件機制處理流程是:調用epoll_wait等待事件來臨,然後遍歷每一個epoll_event,提取epoll_event中的events和data域,data域常用來存儲fd或者指標,

不過一般的做法是提取出events和data.fd,然後根據fd找到對應的回呼函數,fd與對應回呼函數之間的映射關係可以存儲在特定的資料結構中,比如陣列或者雜湊表,然後調用事件回呼函數來處理。

Redis中用了一個陣列來保存fd與回呼函數的映射關係,使用陣列的優點就是簡單高效,但是陣列一般使用在建立的連接不太多情況,而Redis正好符合這個情況,一般Redis的檔事件大都是用戶端建立的連接,

而用戶端的連接個數是一定的,該數量通過配置項maxclients來指定。

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {

aeApiState *state = eventLoop->apidata;

int retval, numevents = 0;

retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,

tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

if (retval > 0) {

int j;

numevents = retval;

for (j = 0; j < numevents;="" j++)="">

int mask = 0;

struct epoll_event *e = state->events+j;

if (e->events & EPOLLIN) mask |= AE_READABLE;

if (e->events & EPOLLOUT) mask |= AE_WRITABLE;

if (e->events & EPOLLERR) mask |= AE_WRITABLE;

if (e->events & EPOLLHUP) mask |= AE_WRITABLE;

eventLoop->fired[j].fd = e->data.fd;

eventLoop->fired[j].mask = mask;

}

}

return numevents;

}

int aeProcessEvents(aeEventLoop *eventLoop, int flags)

{

numevents = aeApiPoll(eventLoop, tvp);

for (j = 0; j < numevents;="" j++)="">

// 從eventLoop->events陣列中查找對應的回呼函數

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++;

}

...

}

文件事件的監聽

Redis監聽埠的事件回呼函數鏈是:acceptTcpHandler / acceptCommonHandler / createClient / aeCreateFileEvent / aeApiAddEvent / epoll_ctl。

在Reids監聽事件處理流程中,會將用戶端的連接fd添加到事件機制中,並設置其回呼函數為readQueryFromClient,該函數負責處理用戶端的命令請求。

命令處理流程

命令處理流程鏈是:readQueryFromClient / processInputBuffer / processCommand / call / 對應命令的回呼函數(c->cmd->proc),比如get key命令的處理回呼函數為getCommand。getCommand的執行流程是先到client對應的資料庫字典中根據key來查找資料,

然後根據回應訊息格式將查詢結果填充到回應訊息中。

IT學習就選優就業

3 如何添加自訂命令

如何在Redis中添加自定的命令呢?其中只需要改動以下幾個地方就行了,比如自訂命令random xxx,然後返回redis: xxx,因為hello xxx和get key類似,所以就依葫蘆畫瓢。

random命令用來返回一個小於xxx的隨機值。

首先在redisCommandTable陣列中添加自訂的命令,redisCommandTable陣列定義在server.c中。然後在getCommand定義處後面添加randomCommand的定義,getCommand定義在t_string.c中。最後在server.h中添加helloCommand的聲明。整個修改patch檔如下,代碼基於redis-2.8.9版本。

From 5304020683078273c1bc6cc9666dab95efa18607 Mon Sep 17 00:00:00 2001

From: luoxn28

Date: Fri, 30 Jun 2017 04:43:47 -0700

Subject: [PATCH] add own command: random num

---

src/server.c | 3 ++-

src/server.h | 1 +

src/t_string.c | 44 ++++++++++++++++++++++++++++++++++++++++++++

3 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/src/server.c b/src/server.c

index 609f396..e040104 100644

--- a/src/server.c

+++ b/src/server.c

@@ -296,7 +296,8 @@ struct redisCommand redisCommandTable[] = {

{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},

{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},

{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},

- {"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0}

+ {"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0},

+ {"random",randomCommand,2,"rF",0,NULL,1,1,1,0,0}

};

struct evictionPoolEntry *evictionPoolAlloc(void);

diff --git a/src/server.h b/src/server.h

index 3fa7c3a..427ac92 100644

--- a/src/server.h

+++ b/src/server.h

@@ -1485,6 +1485,7 @@ void setnxCommand(client *c);

void setexCommand(client *c);

void psetexCommand(client *c);

void getCommand(client *c);

+void randomCommand(client *c);

void delCommand(client *c);

void existsCommand(client *c);

void setbitCommand(client *c);

diff --git a/src/t_string.c b/src/t_string.c

index 8c737c4..df4022d 100644

--- a/src/t_string.c

+++ b/src/t_string.c

@@ -173,6 +173,50 @@ void getCommand(client *c) {

getGenericCommand(c);

}

+static bool checkRandomNum(char *num)

+{

+ char *c = num;

+

+ while (*c != '') {

+ if (!(('0' <= *c)="" &&="" (*c=""><= '9')))="">

+ return false;

+ }

+ c++;

+ }

+

+ return true;

+}

+

+/**

+ * command: random n

+ * return a random num < n,="" if="" n=""><= 0,="" return="">

+ * @author: luoxiangnan

+ */

+void randomCommand(client *c)

+{

+ char buff[64] = {0};

+ int num = 0;

+ robj *o = NULL;

+

+ if (!checkRandomNum(c->argv[1]->ptr)) {

+ o = createObject(OBJ_STRING, sdsnewlen("sorry, it's not a num :(",

+ strlen("sorry, it's not a num :(")));

+ addReplyBulk(c, o);

+ return;

+ }

+

+ sscanf(c->argv[1]->ptr, "%d", &num);

+ if (num > 0) {

+ num = random() % num;

+ } else {

+ num = 0;

+ }

+

+ sprintf(buff, "%s %d", "redis: ", num);

+ o = createObject(OBJ_STRING, sdsnewlen(buff, strlen(buff)));

+ addReplyBulk(c, o);

+}

+

void getsetCommand(client *c) {

if (getGenericCommand(c) == C_ERR) return;

c->argv[2] = tryObjectEncoding(c->argv[2]);

--

1.8.3.1

結果如下所示:

更多優質內容推薦:

體驗別樣旅遊感受 中公教育推出全新互聯網+北京遊,零利潤體驗10天9晚帝都風情:

http://www.ujiuye.com/zt/qgsqxly/?wt.bd=zdy35845tt

有錢任性,某公司豪擲500萬幫助20左右年輕人找工作,起因是做善良的人:

http://www.ujiuye.com/zt/jyfc/?wt.bd=zdy35845tt

學安卓,免學費!50天興趣課程等你來搶!

http://www.ujiuye.com/xydt/2017/13042.html?wt.bd=zdy35845tt

http://www.ujiuye.com/zt/jyfc/?wt.bd=zdy35845tt

學安卓,免學費!50天興趣課程等你來搶!

http://www.ujiuye.com/xydt/2017/13042.html?wt.bd=zdy35845tt