關於Redis的啟動過程

来源:http://www.cnblogs.com/chenpingzhao/archive/2016/03/02/5229129.html
-Advertisement-
Play Games

一、簡介 Redis的啟動也就是main函數的執行,程式的入口在redis.c中,啟動流程: 1. 初始化預設伺服器配置,如果是sentinel模式還需進行額外的配置 2. 修改配置文件或配置選項,這其中包括處理諸如-h/--help,-v/--version,--test-memory的特殊選項,


一、簡介

Redis的啟動也就是main函數的執行,程式的入口在redis.c中,啟動流程:

1. 初始化預設伺服器配置,如果是sentinel模式還需進行額外的配置

2. 修改配置文件或配置選項,這其中包括處理諸如-h/--help,-v/--version,--test-memory的特殊選項,獲取給定的配置文件,設定的配置選項,然後取得配置文件的絕對路徑,重置保存條件,載入配置文件

3. 對伺服器進行設置具體的包括:設置伺服器為守護進程,創建並初始化伺服器中的數據結構(如集群模式),為伺服器進程設置名字,列印ASCII LOGO等等

4. 檢查maxmemory配置,載入數據、運行事件處理器、監聽事件

二、初始化過程

1、執行下述語句

#ifdef INIT_SETPROCTITLE_REPLACEMENT
    spt_init(argc, argv);
#endif

其中INIT_SETPROCTITLE_REPLACEMENT的定義config.h中

/* Check if we can use setproctitle().
 * BSD systems have support for it, we provide an implementation for
 * Linux and osx. */
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#define USE_SETPROCTITLE
#endif

#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
#define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT
void spt_init(int argc, char *argv[]);
void setproctitle(const char *fmt, ...);
#endif

實現了對Linux和OSX的該功能的擴展(BSD系統已經支持該功能,而Linux和APPLE不支持)

2、setlocale

setlocale(LC_COLLATE,"");//系統調用,用來配置本地化信息。

3、zmalloc 的一些配置

zmalloc_enable_thread_safeness(); //開啟了記憶體分配管理的線程安全變數,當記憶體分配時,
//redis會統計一個總記憶體分配量,這是一個共用資源,所以需要原子性操作,
//在redis的記憶體分配代碼里,當需要原子操作時,就需要打開線程安全變數。
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
//是一個記憶體分配錯誤處理,當無法得到需要的記憶體量時,會調用redisOutOfMemoryHandler函數。

4、隨機函數種子

srand(time(NULL)^getpid());//設置隨機種子
gettimeofday(&tv,NULL);//獲取當前日期
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
//設置哈希函數需要使用的隨機種子 伺服器的啟動模式:單機模式、Cluster模式、sentinel模式

5、初始化伺服器配置

initServerConfig();
//在該函數中除了進行屬性的初始化外,主要初始化了命令表。
//調用了函數populateCommandTable,將命令集分佈到一個hash table中。
//避免使用if分支來做命令處理的效率底下問題,而放到hash table中,在理想的情況下只需一次就能定位命令的處理函數。

initServerConfig()功能詳細介紹:

  • 初始化伺服器的狀態

  • 初始化LRU時間

  • 設置保存條件

  • 初始化和複製相關的狀態

  • 初始化PSYNC命令使用的backlog(回溯)

  • 設置客戶端的輸出緩衝區限制

  • 初始化浮點常量

  • 初始化命令表

  • 初始化慢查詢日誌

  • 初始化調試項

  • 初始化記憶體Swap相關設置

這個函數初始化了一個全局變數 struct redisServer server

struct redisServer {
    /* General */
    redisDb *db;
    dict *commands;             /* Command table hahs table */
    aeEventLoop *el;
    unsigned lruclock:22;       /* Clock incrementing every minute, for LRU */
    unsigned lruclock_padding:10;
    int shutdown_asap;          /* SHUTDOWN needed ASAP */
    int activerehashing;        /* Incremental rehash in serverCron() */
    char *requirepass;          /* Pass for AUTH command, or NULL */
    char *pidfile;              /* PID file path */
    int arch_bits;              /* 32 or 64 depending on sizeof(long) */
    int cronloops;              /* Number of times the cron function run */
    char runid[REDIS_RUN_ID_SIZE+1];  /* ID always different at every exec. */
    int sentinel_mode;          /* True if this instance is a Sentinel. */
    /* Networking */
    int port;                   /* TCP listening port */
    char *bindaddr;             /* Bind address or NULL */
    char *unixsocket;           /* UNIX socket path */
    mode_t unixsocketperm;      /* UNIX socket permission */
    int ipfd;                   /* TCP socket file descriptor */
    int sofd;                   /* Unix socket file descriptor */
    int cfd;                    /* Cluster bus lisetning socket */
    list *clients;              /* List of active clients */
    list *clients_to_close;     /* Clients to close asynchronously */
    list *slaves, *monitors;    /* List of slaves and MONITORs */
    redisClient *current_client; /* Current client, only used on crash report */
    char neterr[ANET_ERR_LEN];  /* Error buffer for anet.c */
    ……
    int bug_report_start; /* True if bug report header was already logged. */
    int watchdog_period;  /* Software watchdog period in ms. 0 = off */
};

在這個函數中,對server變數進行了部分成員的初始化,其中:

  • runid:運行該redis伺服器端程式的唯一標識,即每次啟動都會一個唯一ID,用來區分不同的redis伺服器端程式

  • maxidletime:最大空閑時間,就是client連接到server時,如果超出這個值,就會被自動斷開,當然,master和slave節點不包括;如果client有阻塞命令在運行,也不會斷開

saveparams:這個存儲的是redis伺服器端程式從配置文件中讀取的持久化參數,如配置文件所述

save 900 1
save 300 10
save 60 10000

lruclock:是redis實現LRU演算法所需的,每個redis object都帶有一個lruclock,用來從記憶體中移除空閑的對象

6、sentinel_mode

是否開啟redis的哨兵模式,也就是是否監測,通知,自動錯誤恢復,是用來管理多個redis實例的方式。

if (server.sentinel_mode) {
    initSentinelConfig();
    initSentinel();
}

7、讀取參數選項

if (argc >= 2) {
    int j = 1; /* First option to parse in argv[] */
    sds options = sdsempty();
    char *configfile = NULL;

    /* Handle special options --help and --version */
    if (strcmp(argv[1], "-v") == 0 ||
        strcmp(argv[1], "--version") == 0) version();
    if (strcmp(argv[1], "--help") == 0 ||
        strcmp(argv[1], "-h") == 0) usage();
    if (strcmp(argv[1], "--test-memory") == 0) {
        if (argc == 3) {
            memtest(atoi(argv[2]),50);
            exit(0);
        } else {
            fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
            fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
            exit(1);

        configfile = argv[j++];
    /* All the other options are parsed and conceptually appended to the
     * configuration file. For instance --port 6380 will generate the
     * string "port 6380\n" to be parsed after the actual file name
     * is parsed, if any. */
    while(j != argc) {
        if (argv[j][0] == '-' && argv[j][1] == '-') {
            /* Option name */
            if (sdslen(options)) options = sdscat(options,"\n");
            options = sdscat(options,argv[j]+2);
            options = sdscat(options," ");
        } else {
            /* Option argument */
            options = sdscatrepr(options,argv[j],strlen(argv[j]));
            options = sdscat(options," ");
        }
        j++;
    }
    if (server.sentinel_mode && configfile && *configfile == '-') {
        redisLog(REDIS_WARNING,
            "Sentinel config from STDIN not allowed.");
        redisLog(REDIS_WARNING,
            "Sentinel needs config file on disk to save state.  Exiting...");
        exit(1);
    }
    if (configfile) server.configfile = getAbsolutePath(configfile);
    resetServerSaveParams();
    loadServerConfig(configfile,options);
    sdsfree(options);
} else {
    redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "se
ntinel" : "redis");
}

8、伺服器初始化

if (server.daemonize) daemonize();//daemonize()用於在shell啟動時後臺運行
initServer();
if (server.daemonize) createPidFile();
redisAsciiArt();

initServer主要做以下工作:

  • 設置信號處理程式,如sighup, sigpipe等

  • 打開系統日誌文件

  • 繼續初始化server結構,如server.clients, server.slaves, server.monitors等

  • 創建共用對象,這裡的共用對象是一個struct sharedObjectsStruct shared;它用於全局文本信息的保存,避免每次發送固定格式的信息給clients都需要創建一個新的字元串。如:

shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n"));
shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n"));

還包括了從1到10000的redis 整數對象的創建,在這部分範圍的整數經常被用到,所以先創建了,可以反覆重用。

  • 調整系統對文件操作參數的約束大小,如最大打開的文件數。

  • 創建ae事件迴圈

  • 初始化server.db

  • 開啟監聽redis埠或者unix socket

  • 創建Pub/Sub通道
  • 初始化server結構的統計變數,如執行的命令數,連接數,過期鍵等等,還有跟蹤每秒操作的時間和命令數

  • 創建ae時間事件,也是redis的核心迴圈,該過程是serverCron,每秒調用次數由一個叫REDIS_HZ的巨集決定,預設是每10微秒超時,即每10微秒該ae時間事件處理過程serverCron會被過期調用

  • 創建ae文件事件,對redis的TCP或者unix socket埠進行監聽,使用相應的處理函數註冊。每次得到clients連接後,都會創建ae文件事件,非同步接收命令

  • 針對配置文件,設置是否開啟aof和最大使用記憶體

  • 如果有集群設置,初始化集群。初始化lua腳本處理,初始化slowlog和bio(background io)。bio是非同步io操作,用於redis讀取或存取時的io操作

  • 如果開啟了VM,則初始化虛擬記憶體相關的IO/Thread

9、serverCron核心迴圈

#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ))))

這個巨集類似於條件判斷,每ms時間執行一次後續的操作。如:

run_with_period(100) trackOperationsPerSecond();

每百微秒,執行一次跟蹤操作函數,記錄這段時間的命令執行情況。這個迴圈有以下任務需要執行:

  • 如果設置了watchdog_period,那麼每過watchdog_period,都會發送sigalrm信號,該信號又會得到處理,來記錄此時執行的命令。這個過程主要是為了瞭解一些過長命令的執行影響伺服器的整體運行,是一個debug過程。

  • 每百微秒記錄過去每秒的命令執行情況。

  • 更新統計變數,如記憶體使用總數,更新server.lruclock

  • 是否得到關閉程式的信號,如果是,就進入關閉程式的節奏,如aof,rdb文件的處理,文件描述符的關閉等

  • 每5秒輸出一次redis資料庫的使用情況,連接數,總鍵值數

  • 每次都嘗試resize每個db,resize是讓每個db的dict結構進入rehash狀態,rehash是為了擴容dict或者縮小dict。然後每次都嘗試執行每個db的rehash過程一微秒。

  • 每次調用clientCron常式,這是一個對server.clients列表進行處理的過程。再每次執行clientCron時,會對server.clients進行迭代,並且保證 1/(REDIS_HZ*10) of clients per call。也就是每次執行clientCron,如果clients過多,clientCron不會遍歷所有clients,而是遍歷一部分clients,但是保證每個clients都會在一定時間內得到處理。處理過程主要是檢測client連接是否idle超時,或者block超時,然後會調解每個client的緩衝區大小。

  • 對aof,rdb等過程進行開啟或終結。

  • 如果是master節點的話,就開始對過期的鍵值進行處理,與處理clients類似,不是多所有有時間限制的鍵值進行迭代,而是在一個限定的數量內迭代一部分,保證一定時間內能檢測所有鍵值。

  • 對非同步io過程中可能需要關閉的clients進行處理。

  • 每秒調用複製常式和集群常式,每0.1秒調用哨兵常式。

10、aeMain

aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);

在每次ae迴圈進入阻塞時,都會先執行beforeSleep(),在該函數中,會對unblock的clients(指使用blpop等阻塞命令的clients)進行處理,並且執行fsync函數,同步記憶體到磁碟上。

11、總結

redis啟動->初始化server結構部分變數->從命令行和配置文件中讀取配置選項進行初始化->創建ae事件迴圈->創建ae時間事件調用redis運行的必需任務(serverCron)和創建ae文件事件監聽埠->收到client連接時,創建對應的文件事件來納入ae事件迴圈進行非同步接受->收到關閉請求,在serverCron中執行關閉步驟->redis關閉

作者在代碼中處處對運行常式進行約束,保證不過長的陷入某一個不友好的命令中,如檢查過期鍵值和處理過多的clients。

  

 參考文章

http://olylakers.iteye.com/blog/1228198

http://blog.csdn.net/zwan0518/article/details/50281175

http://www.wzxue.com/%E8%A7%A3%E8%AF%BBredis%E8%BF%90%E8%A1%8C%E6%A0%B8%E5%BF%83%E5%BE%AA%E7%8E%AF%E8%BF%87%E7%A8%8B/

http://studentdeng.github.io/blog/2013/08/19/redis-start-up/

http://olylakers.iteye.com/blog/1277256


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

-Advertisement-
Play Games
更多相關文章
  • InnoDB行存儲的三個組成部分(說明: F字元表示列的數量) 名稱(Name) 大小(Size) Field Start Offsets (F*1) or (F*2) bytes Extra Bytes 6 bytes Field Contents 取決於內容 1: FIELD START OFF
  • 網站近日經常遭到攻擊,好幾次資料庫掛馬,前幾天把論壇升級了,今天又升級了資料庫,把之前的MSSQL 2000 升級到MSSQL 2005,用的是資料庫還原功能還原的,遇到了這個帳號孤立的問題。 什麼是孤立用戶的問題? 比如,以前的資料庫的很多表是用戶test建立的,但是當我們恢複數據庫後,test用
  • Memcached的特點 Memcached的緩存是一種分散式的,可以讓不同主機上的多個用戶同時訪問, 因此解決了共用記憶體只能單機應用的局限,更不會出現使用資料庫做類似事情的時候,磁碟開銷和阻塞的發生。
  • 通常ISV在面對本地客戶時對時間相關的處理,一般都時區信息都是不敏感的。但是現在雲的世界里為了讓大家把時間處理的方式統一起來,雲上的服務都是以UTC時間為準的,現在如果作為一個ISV來說就算你面對的客戶只是本地用戶但是你打算利用雲來為你進行的應用提供更多的功能和便捷性時,你就需要採用UTC時間來處理
  • 在Azure上面的PaaS時間都是以UTC時間為準(雲的世界里基本上都是以UTC時間為標準的),所以以前在本地SQL Server上面常用的GetDate()方法會碰到問題,在中國獲取的時間會被當前時間少了8個小時,因為Azure上的時間都是UTC之間,中國的時區是+8.所以你通過GetDate()
  • 我們知道很多事情都存在一個分治的思想,同樣的道理我們也可以用到數據表上,當一個表很大很大的時候,我們就會想到將表拆 分成很多小表,查詢的時候就到各個小表去查,最後進行彙總返回給調用方來加速我們的查詢速度,當然切分可以使用橫向切分,縱向 切分,比如我們最熟悉的訂單表,通常會將三個月以外的訂單放到歷史訂
  • CentOS7.0中MariaDB的簡單安裝與配置方法
  • 當資料庫出現嚴重的性能問題或者hang了的時候,伺服器端sqlplus也無法連接時,此時如果想獲取資料庫當前的狀態信息,以便事後診斷,那麼我們非常需要通過systemstate dump來知道進程在做什麼,在等待什麼,誰是資源的持有者,誰阻塞了別人。在出現上述問題時,及時收集systemstate ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...