關於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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...