??????我們很多人都用過 Redis,自己肯定也搭建過 Redis 的環(huán)境,然后使用 set 命令設(shè)置一個值,設(shè)置完之后 Redis 會給你返回 "OK"。就這樣我們 Redis 環(huán)境就搭建成功了,成功之后肯定有人會有這樣的疑問,一個 ’set name marvel’ 命令到達 Redis 服務(wù)器的時候,服務(wù)器是如何返回’OK’ 的?里面命令處理的流程如何,具體細節(jié)怎么樣?下面我們就來解決這些問題吧!
??????獲取我們換一種思路,Java 程序員都知道現(xiàn)在流行 SpringBoot 應(yīng)用,那么我們時如何開發(fā)一個 SpringBoot 應(yīng)用對外提供服務(wù)的呢?下面我就來分析一下吧。
- 我們要啟動我們的 Application 類
- 通過啟動日志我們會發(fā)現(xiàn),SpringBoot 會去加載我們的類信息
- 加載我們的配置信息
- Tomcat 監(jiān)聽我們的 8080 端口,接收請求
- 給用戶返回響應(yīng)
??????簡單分析完 SpringBoot 應(yīng)用,我們再來看看 Redis 是怎樣提供服務(wù)的吧,首先肯定一點,Redis 啟動肯定也有對呀的 Application 類,也就是 main 函數(shù)了啦,我們看看我們的 main 函數(shù)吧。
/**
* todo: 服務(wù)端 main 方法
*
* @param argc
* @param argv
* @return
*/
int main(int argc, char **argv) {
// ...
/*
We need to initialize our libraries, and the server configuration.
我們需要初始化我們的庫和服務(wù)器配置
*/
//...
// 設(shè)定默認的參數(shù)值
initServerConfig();
// 加載配置文件,也就是加載 redis.conf 文件
loadServerConfig(configfile, options);
/*
* 我們現(xiàn)在需要初始化sentinel來解析配置文件
* 在哨兵模式中將具有填充哨兵的效果具有要監(jiān)控的主節(jié)點的數(shù)據(jù)結(jié)構(gòu)。
* 如果我們是處于哨兵模式,那么就需要加載哨兵配置文件
*/
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
// ...
// todo: 對 server 進行初始化
initServer();
// ...
/* 警告用戶可疑的maxmemory設(shè)置(用戶可以設(shè)置內(nèi)存大小) */
if (server.maxmemory > 0 && server.maxmemory < 1024 * 1024) {
serverLog(LL_WARNING,
"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?",
server.maxmemory);
}
/*
* 在進入事件循環(huán)之前,為服務(wù)器設(shè)置每次事件循環(huán)之前都要執(zhí)行的一個函數(shù) beforeSleep()
* 該函數(shù)一開始就會執(zhí)行集群的 clusterBeforeSleep() 函數(shù)
*/
aeSetBeforeSleepProc(server.el, beforeSleep);
aeSetAfterSleepProc(server.el, afterSleep);
// 進入事件循環(huán),一開始就會執(zhí)行之前設(shè)置的 beforeSleep() 函數(shù),之后就等待事件發(fā)生,處理就緒的事件。
aeMain(server.el);
// aeMain 里面會不斷循環(huán)的等待事件的到來,直到遇到 stop 事件才會跳出循環(huán)
// 然后執(zhí)行刪除事件
aeDeleteEventLoop(server.el);
return 0;
}
上面我只列出來一些我們熟悉的操作。下面我們就來看看他們分別做了什么?
initServerConfig
/**
* 用于設(shè)置默認值
* 下面我只介紹一些我們常用參數(shù)的默認值
*/
void initServerConfig(void) {
...
// 默認端口號是 6379
server.port = CONFIG_DEFAULT_SERVER_PORT;
// 默認 tcp 監(jiān)聽的 backlog 為 511 個
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
...
// 默認 AOF 重寫緩存區(qū)為 64M
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
...
// 默認壓縮鏈表中節(jié)點個數(shù)為 512
server.hash_max_ziplist_entries = OBJ_HASH_MAX_ZIPLIST_ENTRIES;
// 默認壓縮列表中存儲的 value 大小為 64
...
// 重置 RDB save 的保存參數(shù)
resetServerSaveParams();
// 設(shè)置 rdb save 保存參數(shù)
appendServerSaveParams(60 * 60, 1); /* save after 1 hour and 1 change */
appendServerSaveParams(300, 100); /* save after 5 minutes and 100 changes */
appendServerSaveParams(60, 10000); /* save after 1 minute and 10000 changes */
...
/*
* 命令表-我們可以在這里初始化它,因為它是初始配置的一部分,
* todo: 命令名稱可以通過 redis.conf 使用 rename-command 指令修改
*/
server.commands = dictCreate(&commandTableDictType, NULL);
server.orig_commands = dictCreate(&commandTableDictType, NULL);
// 填充命令表格,根據(jù)協(xié)議填充
populateCommandTable();
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
server.zpopminCommand = lookupCommandByCString("zpopmin");
server.zpopmaxCommand = lookupCommandByCString("zpopmax");
server.sremCommand = lookupCommandByCString("srem");
server.execCommand = lookupCommandByCString("exec");
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
server.xclaimCommand = lookupCommandByCString("xclaim");
// todo: slowlog 慢日志
/* Slow log 和日志相關(guān)的配置 */
// slow log 時間限制(紀錄)默認值是 10000
server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
// showlog 最大長度,默認值是 128
server.slowlog_max_len = CONFIG_DEFAULT_SLOWLOG_MAX_LEN;
/* Latency monitor 延遲監(jiān)控 */
server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;
/* Debugging */
server.assert_failed = "<no assertion failed>";
server.assert_file = "<no file>";
server.assert_line = 0;
server.bug_report_start = 0;
server.watchdog_period = 0;
}
給上面參數(shù)設(shè)置完默認值之后,肯定就是加載 redis.conf 文件,獲取我們設(shè)置的值。
loadServerConfig
void loadServerConfig(char *filename, char *options) {
...
// 這里才是真正的加載 redis.conf 文件
loadServerConfigFromString(config);
sdsfree(config);
}
我們看到真正加載 redis.conf 實際是 loadServerConfigFromString 這個方法。
/**
* 加載 redis.conf 配置文件
*
* @param config redis.conf 配置文件
*/
void loadServerConfigFromString(char *config) {
... // 忽略局部變量定義的方法
// 根據(jù) \n 換行符切割字符串,存到 lines 數(shù)組里面 totlines: 表示多少行
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
// 循環(huán)遍歷每一行字符串
for (i = 0; i < totlines; i++) {
// sds 指針數(shù)組
sds *argv;
// argv 存儲元素的個數(shù)
int argc;
linenum = i+1;
// 移除 sds 字符串中的 \t\r\n 字符,方便后面解析參數(shù)
lines[i] = sdstrim(lines[i]," \t\r\n");
/*
* todo:跳過配置文件中以 # 或者以空格開頭的配置
* 這里就會有個坑,有的人配置文件的時候不小心多了個空格,
* 這樣就會導(dǎo)致自己配置的內(nèi)容不會生效
*
* 為什么起始字符不能有空格呢?
* 因為后面解析數(shù)據(jù)的時候是根據(jù)空格解析的
*
* 注意:lines 只是一個指針數(shù)組,為什么可以 lines[i][0]
* 因為 lines 里面存的都是sds 結(jié)構(gòu),然后 sds 就是一個字符數(shù)組
* lines[i][0]: 表示第i個 sds 中下標為0的字符
*/
if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
}
// 將 redis.conf 中我們自己配置的信息將默認值覆蓋(忽略代碼,很簡單)
...
// 釋放 lines,totlines 的內(nèi)存
sdsfreesplitres(lines,totlines);
return;
// 打印出錯誤信息
loaderr:
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
fprintf(stderr, ">>> '%s'\n", lines[i]);
fprintf(stderr, "%s\n", err);
exit(1);
}