標籤:nosql資料庫 記憶體資料庫 redis
每個系統都會有類似一個config設定檔,config檔案裡的內容想想都知道,一定就是那麼一些固定的一行行的屬性代碼了,今天在看redis代碼中的config屬性,那拉下來的一筆,的確多,目測在50至100個屬性左右。如果就此將config每個屬性代表什麼意思不是我的風格,也一定是很乏味的,所以我的特點就是在代碼中去理解程式員在寫這類代碼時的思路,和茫茫代碼中的亮點。我們知道,redis啟動並執行環境包括很多種的,windows,Linux,mac os等等,不同的作業系統,當然有些屬性就不能支援了,所以在redis中的config.h標頭檔中跟據電腦所屬於的作業系統,做了很多的預先處理,比如說,在Linux等系統上,是可以支援修改進程名稱的:
/* Check if we can use setproctitle(). * BSD systems have support for it, we provide an implementation for * Linux and osx. *//* 檢查是否能調用setproctitle(),這個方法是Linux上修改進程名稱的方法 *//* 這裡通過判斷是否是BSD系統,BSD是Berkeley Systems Distrobution的縮寫,是一種UNIX版本 */#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)#define USE_SETPROCTITLE#endif#if (defined __linux || defined __APPLE__)#define USE_SETPROCTITLE#define INIT_SETPROCTITLE_REPLACEMENTvoid spt_init(int argc, char *argv[]);void setproctitle(const char *fmt, ...);#endif
當然這隻是我提到的一點,也就是說,redis在異構系統的處理上,考慮的還是非常周全的。我們主要看看config主要的操作檔案,先看看裡面的一些API:
/* Config file API */int yesnotoi(char *s) /* 判斷字元是否為yes */void appendServerSaveParams(time_t seconds, int changes) /* 追加server save參數 */void resetServerSaveParams(void) /* 重設server的save參數,即釋放server的serverParams */void loadServerConfigFromString(char *config) /* 從字串中載入server屬性配置 */void loadServerConfig(char *filename, char *options) /* 從檔案中載入server配置 */void configSetCommand(redisClient *c) /* 根據redisClient中的參數設定server的配置 */#define config_get_string_field(_name,_var) /* 宏定義了擷取字串範圍的方法 */#define config_get_bool_field(_name,_var) /* 宏定義了擷取布爾範圍的方法,值在這裡值為yes或no */#define config_get_numerical_field(_name,_var) /* 宏定義了擷取數字類型範圍的方法 */void configGetCommand(redisClient *c) /* 擷取配置資訊命令,以Replay給用戶端的方式 */void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) /* 添加配置字串行 */void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) /* 添加字典line-option */void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) /* rewriteConfigState重寫option選項 */struct rewriteConfigState *rewriteConfigReadOldFile(char *path) /* 讀取老設定檔資訊,檔案如果不可讀或不存在,返回NULL */void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) /* 是否覆蓋configline */int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) /* 格式化byte大小的顯示,避免long long 類型超長的顯示 */void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) /* 往config中寫入某類型配置,後面的幾個方法類似 */void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) /* 同上 */void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, char *value, char *defvalue) /* 同上 */void rewriteConfigNumericalOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) /* 同上 */void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, int value, int defvalue) /* 同上 */void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, ...) /* 同上 */void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigSaveOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigDirOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigSlaveofOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state) /* 同上 */void rewriteConfigBindOption(struct rewriteConfigState *state) /* 同上 */sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) /* confifstate中擷取配置資訊字串 */void rewriteConfigReleaseState(struct rewriteConfigState *state) /* configstate釋放空間 */void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) /* 置空state 的line配置 */int rewriteConfigOverwriteFile(char *configfile, sds content) /* 字串屬性寫入覆蓋源檔案 */int rewriteConfig(char *path) /* 將當前的屬性讀入到檔案中,步驟:(1).將當前server屬性讀入configstate(2).configstate屬性變為字串(3).將字串寫入檔案 */void configCommand(redisClient *c) /* 用戶端config命令調用方法 */
上面的API的數量確實有點恐怖,我大體總結一下,在config.c中的主要操作:
1.從config設定檔中讀取配置到server屬性中
2.將當前server的設定的屬性寫入配置中
上面的其他方法都是為上面的2個要求服務的。所以redis在這裡設計一個叫rewriteConfigState的結構體角色,裡面儲存了屬性配置的字串數組,一個字串數組代表一種屬性設定。比如現在要把當前的配置讀入設定檔中操作:
int rewriteConfig(char *path) /* 將當前的屬性讀入到檔案中,步驟:(1).將當前server屬性讀入configstate(2).configstate屬性變為字串(3).將字串寫入檔案 */
下面看看redis代碼中的這個configstate結構體的構造:
/* The config rewrite state. */struct rewriteConfigState {//這裡存放著option-line的映射 dict *option_to_line; /* Option -> list of config file lines map */ dict *rewritten; /* Dictionary of already processed options */ //當前設定檔中的行數 int numlines; /* Number of lines in current config */ //當前行字串 sds *lines; /* Current lines as an array of sds strings */ int has_tail; /* True if we already added directives that were not present in the original config file. */};
其中的lines就是具體的屬性,第一個opition-line的映射指的是哪行對應什麼屬性名稱。在config操作檔案中,還提到了一個“maxmemory”的概念,中文意思可以瞭解為“最大記憶‘:
/* We use the following dictionary type to store where a configuration * option is mentioned in the old configuration file, so it's * like "maxmemory" -> list of line numbers (first line is zero). *//* 下面定義了幾個字典類型用來儲存老的設定檔中的一些資訊,像記錄類似,like "maxmemory" */
也就是在redis檔案中,可以存在老的設定檔,舊的屬性可以通過老檔案讀入再次寫入新配置中,達到了記錄曆史配置記錄的作用。老設定檔讀出的屬性同樣是存在於中間的configstate結構體中:
/* Read the old file, split it into lines to populate a newly created * config rewrite state, and return it to the caller. * * If it is impossible to read the old file, NULL is returned. * If the old file does not exist at all, an empty state is returned. *//* 讀取老設定檔資訊,檔案如果不可讀或不存在,返回NULL */struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { FILE *fp = fopen(path,"r"); struct rewriteConfigState *state = zmalloc(sizeof(*state)); char buf[REDIS_CONFIGLINE_MAX+1]; int linenum = -1; if (fp == NULL && errno != ENOENT) return NULL; state->option_to_line = dictCreate(&optionToLineDictType,NULL); state->rewritten = dictCreate(&optionSetDictType,NULL); state->numlines = 0; state->lines = NULL; state->has_tail = 0; if (fp == NULL) return state; /* Read the old file line by line, populate the state. */ while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL) { int argc; sds *argv; sds line = sdstrim(sdsnew(buf),"\r\n\t "); linenum++; /* Zero based, so we init at -1 */ /* Handle comments and empty lines. */ //處理注釋和空行 if (line[0] == '#' || line[0] == '\0') { if (!state->has_tail && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE)) state->has_tail = 1; rewriteConfigAppendLine(state,line); continue; } /* Not a comment, split into arguments. */ argv = sdssplitargs(line,&argc); if (argv == NULL) { /* Apparently the line is unparsable for some reason, for * instance it may have unbalanced quotes. Load it as a * comment. */ sds aux = sdsnew("# ??? "); aux = sdscatsds(aux,line); sdsfree(line); //將老的配置屬性讀入configstate結構體 rewriteConfigAppendLine(state,aux); continue; } sdstolower(argv[0]); /* We only want lowercase config directives. */ /* Now we populate the state according to the content of this line. * Append the line and populate the option -> line numbers map. */ rewriteConfigAppendLine(state,line); rewriteConfigAddLineNumberToOption(state,argv[0],linenum); sdsfreesplitres(argv,argc); } fclose(fp); //返回configstate,裡面記錄了一些老的設定檔中的配置行資訊 return state;}
又一次用到了configstate的結構體。小小config檔案也存在我們意想不到的的設計。
Redis源碼解析(十六)--- config設定檔