Redis整理--啓動順序

關於Redis的啓動過程

一、簡介

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

1. 初始化默認服務器配置,如果是sentinel模式還需進行額外的配置

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

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

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

5.啓動完成,AE會定時間去查詢各個客戶端是否有輸入,如果有讀取客戶端輸入並且對命令進行解析,命令信息保存在Hash表中

幾個重要的函數

  • initServerConfig() 設定默認的參數值,並讀取配置文件redis.conf,若用戶配置了某個參數,則用該參數值替換默認值

  • initServer() 該函數主要對server進行初始化,內容包括: 調用anetTcpServer()函數創建socket server作爲redis server,並將該server的句柄加到epoll/kqueue的監聽隊列中。 一旦有client接入,便會對該client觸發操作acceptTcpHandler,該操作是調用aeCreateFileEvent註冊的

  • anetTcpServer() 爲建立網絡套接字服務器的方法,對socket(), bind()和listen()等函數進行了封裝

  • aeMain(aeEventLoop *eventLoop) 是啓動事件輪詢的入口,內部實現爲一循環,不斷處理來自客戶的請求

二、初始化過程

1、執行下述語句

1
2
3
#ifdef INIT_SETPROCTITLE_REPLACEMENT
    spt_init(argc, argv);
#endif

其中INIT_SETPROCTITLE_REPLACEMENT的定義config.h中

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 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

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

3、zmalloc 的一些配置

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

4、隨機函數種子

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

5、初始化服務器配置

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

initServerConfig()功能詳細介紹:

  • 初始化服務器的狀態

  • 初始化LRU時間

  • 設置保存條件

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

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

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

  • 初始化浮點常量

  • 初始化命令表

  • 初始化慢查詢日誌

  • 初始化調試項

  • 初始化內存Swap相關設置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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服務器端程序從配置文件中讀取的持久化參數,如配置文件所述

1
2
3
save 900 1
save 300 10
save 60 10000

lruclock:是redis實現LRU算法所需的,每個redis object都帶有一個lruclock,用來從內存中移除空閒的對象

6、sentinel_mode

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

1
2
3
4
if (server.sentinel_mode) {
    initSentinelConfig();
    initSentinel();
}

7、讀取參數選項

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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、服務器初始化

1
2
3
4
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都需要創建一個新的字符串。如:

1
2
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核心循環

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

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

1
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

1
2
3
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


文章出自:https://www.cnblogs.com/chenpingzhao/p/5229129.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章