一、簡介 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