Redis 記憶體管理與事件處理

来源:http://www.cnblogs.com/luoxn28/archive/2017/07/01/7101725.html
-Advertisement-
Play Games

1 Redis記憶體管理 Redis記憶體管理相關文件為zmalloc.c/zmalloc.h,其只是對C中記憶體管理函數做了簡單的封裝,屏蔽了底層平臺的差異,並增加了記憶體使用情況統計的功能。 void *zmalloc(size_t size) { // 多申請的一部分記憶體用於存儲當前分配了多少自己的內 ...


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來查找數據,然後根據響應消息格式將查詢結果填充到響應消息中。  

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 <luoxn28@163.com>
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 != '\0') {
+        if (!(('0' <= *c) && (*c <= '9'))) {
+            return false;
+        }
+        c++;
+    }
+
+    return true;
+}
+
+/**
+ * command: random n
+ * return a random num < n, if n <= 0, return 0
+ * @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
結果如下所示:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 而這就是我們最初設定的願景。 加速高質量的交付,提升開發者的價值。我們技術團隊所做的每一個步驟、每一個過程都是疊加的、遞增的,日拱一卒,功不唐捐。 ...
  • 絕對值函數ABS() 圓周率PI(),小數預設6位 平方根函數SQRT() 求餘函數MOD(x,y) 向上取整CEIL(x),CEILING(x) 向下取整FLOOR(x) 隨機數RAND(),RAND(x) :產生0~1之間的浮點數,有參數時,相同的參數產生的隨機數相等 四捨五入ROUND(x) ...
  • Memcached是一個自由開源的,高性能,分散式記憶體對象緩存系統。 key-value存儲方式。 Memcached只有String的存儲結構。 ...
  • 本文出處:http://www.cnblogs.com/wy123/p/7102128.html (保留出處並非什麼原創作品權利,本人拙作還遠遠達不到,僅僅是為了鏈接到原文,因為後續對可能存在的一些錯誤進行修正或補充,無他) 本文的資料庫版本是MySQL5.7.18,簡單介紹一下MySQL數據文件目 ...
  • ACID:資料庫事務正確執行所必須滿足的四個基本要素的縮寫: 原子性(atomicity,或叫不可分割性),一致性(consistency),隔離性(isolation,又稱獨立性),持久性(durability)。 四大特性 原子性:一個事務(transaction)中的所有操作,要麼全部完成,要 ...
  • 資料庫事務的隔離等級,英語叫做 Transaction Isolation Level。 最近在給客戶維護項目的時候,對一個表在兩個進程中同時做更新和查詢時碰到了死鎖(DeadLock),數據表裡有幾百萬上千萬條記錄,上面的處理當時是更新幾千條記錄, 查詢整張表。 這是前提,為了搞明白這個死鎖,大概 ...
  • 查詢的格式: select [distinct] *(所有)| 欄位名... from 表名 [where 條件過濾]查詢指定欄位信息pname priceselect pname,price from products; 查詢表中所有欄位select * from products; 去除金額重覆 ...
  • 導讀: 分類問題是機器學習應用中的常見問題,而二分類問題是其中的典型,例如垃圾郵件的識別。本文基於UCI機器學習資料庫中的銀行營銷數據集,從對數據集進行探索,數據預處理和特征工程,到學習模型的評估與選擇,較為完整的展示瞭解決分類問題的大致流程。文中包含了一些常見問題的處理方式,例如缺失值的處理、非數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...