Redis是如何對外提供服務(wù)的

??????我們很多人都用過 Redis,自己肯定也搭建過 Redis 的環(huán)境,然后使用 set 命令設(shè)置一個值,設(shè)置完之后 Redis 會給你返回 "OK"。就這樣我們 Redis 環(huán)境就搭建成功了,成功之后肯定有人會有這樣的疑問,一個 ’set name marvel’ 命令到達 Redis 服務(wù)器的時候,服務(wù)器是如何返回’OK’ 的?里面命令處理的流程如何,具體細節(jié)怎么樣?下面我們就來解決這些問題吧!
??????獲取我們換一種思路,Java 程序員都知道現(xiàn)在流行 SpringBoot 應(yīng)用,那么我們時如何開發(fā)一個 SpringBoot 應(yīng)用對外提供服務(wù)的呢?下面我就來分析一下吧。

  1. 我們要啟動我們的 Application 類
  2. 通過啟動日志我們會發(fā)現(xiàn),SpringBoot 會去加載我們的類信息
  3. 加載我們的配置信息
  4. Tomcat 監(jiān)聽我們的 8080 端口,接收請求
  5. 給用戶返回響應(yīng)

??????簡單分析完 SpringBoot 應(yīng)用,我們再來看看 Redis 是怎樣提供服務(wù)的吧,首先肯定一點,Redis 啟動肯定也有對呀的 Application 類,也就是 main 函數(shù)了啦,我們看看我們的 main 函數(shù)吧。

/**
 * todo: 服務(wù)端 main 方法
 *
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char **argv) {
    
    // ...
    /*
        We need to initialize our libraries, and the server configuration.
        我們需要初始化我們的庫和服務(wù)器配置
    */
     //...
    // 設(shè)定默認的參數(shù)值
    initServerConfig();
    
    // 加載配置文件,也就是加載 redis.conf 文件
    loadServerConfig(configfile, options);

    /* 
     * 我們現(xiàn)在需要初始化sentinel來解析配置文件
     * 在哨兵模式中將具有填充哨兵的效果具有要監(jiān)控的主節(jié)點的數(shù)據(jù)結(jié)構(gòu)。
     * 如果我們是處于哨兵模式,那么就需要加載哨兵配置文件
     */
    if (server.sentinel_mode) {
        initSentinelConfig();
        initSentinel();
    }

    // ...

    // todo: 對 server 進行初始化
    initServer();
    
     // ...

    /* 警告用戶可疑的maxmemory設(shè)置(用戶可以設(shè)置內(nèi)存大小) */
    if (server.maxmemory > 0 && server.maxmemory < 1024 * 1024) {
        serverLog(LL_WARNING,
                  "WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?",
                  server.maxmemory);
    }

    /*
     * 在進入事件循環(huán)之前,為服務(wù)器設(shè)置每次事件循環(huán)之前都要執(zhí)行的一個函數(shù) beforeSleep()
     * 該函數(shù)一開始就會執(zhí)行集群的 clusterBeforeSleep() 函數(shù)
     */
    aeSetBeforeSleepProc(server.el, beforeSleep);
    aeSetAfterSleepProc(server.el, afterSleep);
    // 進入事件循環(huán),一開始就會執(zhí)行之前設(shè)置的 beforeSleep() 函數(shù),之后就等待事件發(fā)生,處理就緒的事件。
    aeMain(server.el);
    // aeMain 里面會不斷循環(huán)的等待事件的到來,直到遇到 stop 事件才會跳出循環(huán)
    // 然后執(zhí)行刪除事件
    aeDeleteEventLoop(server.el);
    return 0;
}

上面我只列出來一些我們熟悉的操作。下面我們就來看看他們分別做了什么?

initServerConfig

/**
 * 用于設(shè)置默認值
 * 下面我只介紹一些我們常用參數(shù)的默認值
 */
void initServerConfig(void) {
     ...
    // 默認端口號是 6379
    server.port = CONFIG_DEFAULT_SERVER_PORT;
    // 默認 tcp 監(jiān)聽的 backlog 為 511 個
    server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;

    ...

    // 默認 AOF 重寫緩存區(qū)為 64M
    server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
  
    ...

    // 默認壓縮鏈表中節(jié)點個數(shù)為 512 
    server.hash_max_ziplist_entries = OBJ_HASH_MAX_ZIPLIST_ENTRIES;
    // 默認壓縮列表中存儲的 value 大小為 64
    
    ...    

    // 重置 RDB save 的保存參數(shù)
    resetServerSaveParams();

    // 設(shè)置 rdb save 保存參數(shù)
    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 */

    ... 

    /* 
     * 命令表-我們可以在這里初始化它,因為它是初始配置的一部分,
     * todo: 命令名稱可以通過 redis.conf 使用 rename-command 指令修改
     */
    server.commands = dictCreate(&commandTableDictType, NULL);
    server.orig_commands = dictCreate(&commandTableDictType, NULL);
    // 填充命令表格,根據(jù)協(xié)議填充
    populateCommandTable();
    server.delCommand = lookupCommandByCString("del");
    server.multiCommand = lookupCommandByCString("multi");
    server.lpushCommand = lookupCommandByCString("lpush");
    server.lpopCommand = lookupCommandByCString("lpop");
    server.rpopCommand = lookupCommandByCString("rpop");
    server.zpopminCommand = lookupCommandByCString("zpopmin");
    server.zpopmaxCommand = lookupCommandByCString("zpopmax");
    server.sremCommand = lookupCommandByCString("srem");
    server.execCommand = lookupCommandByCString("exec");
    server.expireCommand = lookupCommandByCString("expire");
    server.pexpireCommand = lookupCommandByCString("pexpire");
    server.xclaimCommand = lookupCommandByCString("xclaim");

    // todo: slowlog 慢日志
    /* Slow log 和日志相關(guān)的配置 */
    // slow log 時間限制(紀錄)默認值是 10000
    server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
    // showlog 最大長度,默認值是 128
    server.slowlog_max_len = CONFIG_DEFAULT_SLOWLOG_MAX_LEN;

    /* Latency monitor 延遲監(jiān)控 */
    server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;

    /* Debugging */
    server.assert_failed = "<no assertion failed>";
    server.assert_file = "<no file>";
    server.assert_line = 0;
    server.bug_report_start = 0;
    server.watchdog_period = 0;
}

給上面參數(shù)設(shè)置完默認值之后,肯定就是加載 redis.conf 文件,獲取我們設(shè)置的值。

loadServerConfig

void loadServerConfig(char *filename, char *options) {

     ...

    // 這里才是真正的加載 redis.conf 文件
    loadServerConfigFromString(config);
    sdsfree(config);
}

我們看到真正加載 redis.conf 實際是 loadServerConfigFromString 這個方法。

/**
 * 加載 redis.conf 配置文件
 *
 * @param config redis.conf 配置文件
 */
void loadServerConfigFromString(char *config) {
    
    ...   // 忽略局部變量定義的方法

    // 根據(jù) \n 換行符切割字符串,存到 lines 數(shù)組里面 totlines: 表示多少行
    lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
    // 循環(huán)遍歷每一行字符串
    for (i = 0; i < totlines; i++) {
        // sds 指針數(shù)組
        sds *argv;
        // argv 存儲元素的個數(shù)
        int argc;

        linenum = i+1;
        // 移除 sds 字符串中的 \t\r\n 字符,方便后面解析參數(shù)
        lines[i] = sdstrim(lines[i]," \t\r\n");

        /*
         * todo:跳過配置文件中以 # 或者以空格開頭的配置
         * 這里就會有個坑,有的人配置文件的時候不小心多了個空格,
         * 這樣就會導(dǎo)致自己配置的內(nèi)容不會生效
         *
         * 為什么起始字符不能有空格呢?
         * 因為后面解析數(shù)據(jù)的時候是根據(jù)空格解析的
         *
         * 注意:lines 只是一個指針數(shù)組,為什么可以 lines[i][0]
         * 因為 lines 里面存的都是sds 結(jié)構(gòu),然后 sds 就是一個字符數(shù)組
         * lines[i][0]: 表示第i個 sds 中下標為0的字符
         */
        if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
    }

    // 將 redis.conf 中我們自己配置的信息將默認值覆蓋(忽略代碼,很簡單)
    ...
      
    // 釋放 lines,totlines 的內(nèi)存
    sdsfreesplitres(lines,totlines);
    return;

    // 打印出錯誤信息
loaderr:
    fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
    fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
    fprintf(stderr, ">>> '%s'\n", lines[i]);
    fprintf(stderr, "%s\n", err);
    exit(1);
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文是我自己在秋招復(fù)習(xí)時的讀書筆記,整理的知識點,也是為了防止忘記,尊重勞動成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 3,483評論 0 40
  • 超強、超詳細Redis入門教程 轉(zhuǎn)載2017年03月04日 16:20:02 16916 轉(zhuǎn)載自: http://...
    邵云濤閱讀 17,638評論 3 313
  • 【本教程目錄】 1.redis是什么2.redis的作者3.誰在使用redis4.學(xué)會安裝redis5.學(xué)會啟動r...
    徐猿猿閱讀 1,923評論 0 35
  • 文章已經(jīng)放到github上 ,如果對您有幫助 請給個star[https://github.com/qqxuanl...
    尼爾君閱讀 2,347評論 0 22
  • 近期工作有些忙,一直沒有動筆。一直覺得自己在工作中是個努力上進,積極做好本職工作的人。但不知道是怎么了,近期好些事...
    安亦然2020閱讀 1,994評論 4 3

友情鏈接更多精彩內(nèi)容