鍍金池/ 教程/ 大數(shù)據(jù)/ Redis 日志和斷言
Redis 數(shù)據(jù)淘汰機制
積分排行榜
小剖 Memcache
Redis 數(shù)據(jù)結(jié)構(gòu) intset
分布式鎖
從哪里開始讀起,怎么讀
Redis 數(shù)據(jù)結(jié)構(gòu) dict
不在浮沙筑高臺
Redis 集群(上)
Redis 監(jiān)視器
源碼閱讀工具
Redis 日志和斷言
內(nèi)存數(shù)據(jù)管理
Redis 數(shù)據(jù)結(jié)構(gòu)綜述
源碼日志
Web 服務(wù)器存儲 session
消息中間件
Redis 與 Lua 腳本
什么樣的源代碼適合閱讀
Redis 數(shù)據(jù)結(jié)構(gòu) sds
Memcached slab 分配策略
訂閱發(fā)布機制
Redis 是如何提供服務(wù)的
Redis 事務(wù)機制
Redis 集群(下)
主從復(fù)制
Redis 應(yīng)用
RDB 持久化策略
Redis 數(shù)據(jù)遷移
Redis 事件驅(qū)動詳解
初探 Redis
Redis 與 Memcache
AOF 持久化策略
Redis 數(shù)據(jù)結(jié)構(gòu) redisOb
作者簡介
Redis 數(shù)據(jù)結(jié)構(gòu) ziplist
Redis 數(shù)據(jù)結(jié)構(gòu) skiplist
Redis 哨兵機制

Redis 日志和斷言

Redis 日志

linux 的世界里,最好用的調(diào)試工具不是 gdb,而是日志和 printf。日志在一個軟件系統(tǒng)中是非常常見的,一個關(guān)鍵的作用即定位錯誤,當(dāng)系統(tǒng)出問題首先想到就是日志,查看日志能快速定位問題。Redis 中的日志模塊較為簡單。我們在 Redis 源碼中,到處都可以見到 redisLog()。

通常,日志會分為幾個級別。在 Redis 中 5 個日志級別,在 redis.h 文件中有定義:

/* Log levels */
#define REDIS_DEBUG 0 // 調(diào)試級別,這一級別產(chǎn)生最多的日志信息
#define REDIS_VERBOSE 1
#define REDIS_NOTICE 2
#define REDIS_WARNING 3
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
#define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE

服務(wù)器的配置結(jié)構(gòu)體中,struct redisServer.verbosity 是用來設(shè)定日志級別的,譬如將日志級別設(shè)定為REDIS_NOTICE 后,代碼中 REDIS_VERBOSE 和REDIS_DEBUG 級別的日志都不會被打印。

日志級別值越是低,日志級別越高,產(chǎn)生了日志也就越多,開發(fā)人員在產(chǎn)品上線之前會將日志級別調(diào)至最低,方便發(fā)現(xiàn)定位或發(fā)現(xiàn)潛在的問題。而上線之后,可以將志級別降低,減少調(diào)試日志。如果日志級別過高,則日志量大,可能會對線上的服務(wù)產(chǎn)生影響,因為寫日志就是寫文件操作,系統(tǒng)調(diào)用是要消耗時間的。

日志是想要記錄某一個時間點,在哪里發(fā)送了什么事情,以方便出現(xiàn)問題的時候,恢復(fù)現(xiàn)場,快速定位問題所在?!澳骋粫r間點”即添加時間戳;“在哪里”即程序執(zhí)行的位置,對應(yīng)的是源碼的文件,行號函數(shù)等;“發(fā)生了什么事情”即記錄一些關(guān)鍵數(shù)據(jù)。

// redis 日志函數(shù),會將給定的數(shù)據(jù)寫入日志文件,和常用的printf 函數(shù)用法差不多
void redisLog(int level, const char *fmt, ...) {
    va_list ap;
    char msg[REDIS_MAX_LOGMSG_LEN];
    // 如果日志級別小于預(yù)設(shè)的日志級別,直接返回
    if ((level&0xff) < server.verbosity) return;
    va_start(ap, fmt);
    vsnprintf(msg, sizeof(msg), fmt, ap);
    va_end(ap);
    // redisLogRaw() 函數(shù)將給定的信息,在增加時間戳和進(jìn)程id 后寫入日志文件
    redisLogRaw(level,msg);
}

Redis 斷言

為什么需要斷言???????> TODO 當(dāng)你認(rèn)為某些事情在正常情況下不可能出現(xiàn),應(yīng)盡可能結(jié)束任務(wù),而不是捕捉錯誤,嘗試挽救。同樣在西加加里,使用 try…catch() 會讓程序的邏輯變亂,甚至讓程序的行為變得不可預(yù)測,大膽的使用斷言吧。

Redis 中不僅僅實現(xiàn)了斷言,且在斷言失敗的時候會打印一些關(guān)鍵的信息。

在 Redis.h 中定義了兩個斷言相關(guān)的宏:

#define redisAssertWithInfo(_c,_o,_e) \
    ((_e)?(void)0 : \
    (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
#define redisAssert(_e) \
    ((_e)?(void)0 : \
    (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))

如果斷言為真,執(zhí)行一個空操作;斷言為假,會打印關(guān)鍵的信息。

_redisAssert() 函數(shù)會記錄斷言發(fā)生的錯誤信息,文件名和行號

void _redisAssert(char *estr, char *file, int line) {
    // 向日志文件中寫入BUG 頭部
    bugReportStart();
    // 將文件名,行號,錯誤信息寫入日志
    redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
    redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
    // 如果需要,可以記錄錯誤信息,文件名和行號,以便在進(jìn)程崩潰后調(diào)試(gdb core?)
#ifdef HAVE_BACKTRACE
    server.assert_failed = estr;
    server.assert_file = file;
    server.assert_line = line;
    redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)");
#endif
    // 強制segmentation fault。無效的內(nèi)存訪問,可以產(chǎn)生SIGSEGV,如此會
    // 產(chǎn)生coredump 文件以供進(jìn)程崩潰后調(diào)試使用
    *((char*)-1) = 'x';
}

這有個小有意思的語句:*((char*)-1) = ’x’;(char *)-1表示指向地址值為 -1 的指針,它所指向的內(nèi)存肯定是非法的,對非法內(nèi)存的操作會觸發(fā) SIGSEGV 信號,進(jìn)程結(jié)束后會產(chǎn)生 coredump 文件,方便調(diào)試使用。使用gdb、可執(zhí)行文件和 coredump 文件能快速定位問題所在,即使進(jìn)程已經(jīng)崩潰了。

_redisAssertWithInfo() 函數(shù)會打印 Redis 服務(wù)器當(dāng)前服務(wù)的客戶端和某個關(guān)鍵 Redis 對象的信息,具體請參看源碼,在這不展開了。