慢查詢日誌是 Redis 提供的一個用於觀察系統性能的功能, 這個功能的實現非常簡單, 這裏我們也簡單地講解一下。
本章先介紹和慢查詢功能相關的數據結構和變量, 然後介紹 Redis 是如何記錄命令的執行時間, 以及如何爲執行超過限制事件的命令記錄慢查詢日誌的。
相關數據結構
每條慢查詢日誌都以一個 slowlog.h/slowlogEntry 結構定義:
typedef struct slowlogEntry {
// 命令參數
robj **argv;
// 命令參數數量
int argc;
// 唯一標識符
long long id; /* Unique entry identifier. */
// 執行命令消耗的時間,以納秒(1 / 1,000,000,000 秒)爲單位
long long duration; /* Time spent by the query, in nanoseconds. */
// 命令執行時的時間
time_t time; /* Unix time at which the query was executed. */
} slowlogEntry;
記錄服務器狀態的 redis.h/redisServer 結構裏保存了幾個和慢查詢有關的屬性:
struct redisServer {
// ... other fields
// 保存慢查詢日誌的鏈表
list *slowlog; /* SLOWLOG list of commands */
// 慢查詢日誌的當前 id 值
long long slowlog_entry_id; /* SLOWLOG current entry ID */
// 慢查詢時間限制
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
// 慢查詢日誌的最大條目數量
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
// ... other fields
};
slowlog 屬性是一個鏈表, 鏈表裏的每個節點保存了一個慢查詢日誌結構, 所有日誌按添加時間從新到舊排序,新的日誌在鏈表的左端,舊的日誌在鏈表的右端。
slowlog_entry_id 在創建每條新的慢查詢日誌時增一,用於產生慢查詢日誌的 ID (這個 ID 在執行 SLOWLOG RESET 之後會被重置)。
slowlog_log_slower_than 是用戶指定的命令執行時間上限,執行時間大於等於這個值的命令會被慢查詢日誌記錄。
slowlog_max_len 慢查詢日誌的最大數量,當日志數量等於這個值時,添加一條新日誌會造成最舊的一條日誌被刪除。
下圖展示了一個 slowlog 屬性的實例:
慢查詢日誌的記錄
在每次執行命令之前, Redis 都會用一個參數記錄命令執行前的時間, 在命令執行完之後, 再計算一次當前時間, 然後將兩個時間值相減, 得出執行命令所耗費的時間值 duration , 並將 duration 傳給 slowlogPushEntryIfNeed 函數。
如果 duration 超過服務器設置的執行時間上限 server.slowlog_log_slower_than 的話, slowlogPushEntryIfNeed 就會創建一條新的慢查詢日誌, 並將它加入到慢查詢日誌鏈表裏。
可以用一段僞代碼來表示這個過程:
def execute_redis_command_with_slowlog():
# 命令執行前的時間
start = ustime()
# 執行命令
execute_command(argv, argc)
# 計算命令執行所耗費的時間
duration = ustime() - start
if slowlog_is_enabled:
slowlogPushEntryIfNeed(argv, argc, duration)
def slowlogPushEntryIfNeed(argv, argc, duration)
# 如果執行命令耗費的時間超過服務器設置命令執行時間上限
# 那麼創建一條新的 slowlog
if duration > server.slowlog_log_slower_than:
# 創建新 slowlog
log = new slowlogEntry()
# 設置各個域
log.argv = argv
log.argc = argc
log.duration = duration
log.id = server.slowlog_entry_id
log.time = now()
# 將新 slowlog 追加到日誌鏈表末尾
server.slowlog.append(log)
# 更新服務器 slowlog
server.slowlog_entry_id += 1
慢查詢日誌的操作
針對慢查詢日誌有三種操作,分別是查看、清空和獲取日誌數量:
- 查看日誌:在日誌鏈表中遍歷指定數量的日誌節點,複雜度爲 O(N) 。
- 清空日誌:釋放日誌鏈表中的所有日誌節點,複雜度爲 O(N) 。
- 獲取日誌數量:獲取日誌的數量等同於獲取 server.slowlog 鏈表的數量,複雜度爲 O(1) 。
小結
Redis 用一個鏈表以 FIFO 的順序保存着所有慢查詢日誌。
每條慢查詢日誌以一個慢查詢節點表示,節點中記錄着執行超時的命令、命令的參數、命令執行時的時間,以及執行命令所消耗的時間等信息。