? ? ? ?每當提到延時統計的時候,一定想到的一個名詞就是”性能測試“,沒錯,在Redis的redis_benchmark文件中,的確用到了延遲文件中的相關信息。在Redis中的官方解釋此文件:
~~~
/* The latency monitor allows to easily observe the sources of latency
* in a Redis instance using the LATENCY command. Different latency
* sources are monitored, like disk I/O, execution of commands, fork
* system call, and so forth.
*
* 延時監聽器可以對Redis中很多簡單的資源進行監聽,比如I/O磁盤操作,執行一些指令,
* fork創建子線程操作等的監聽。
* ----------------------------------------------------------------------------
~~~
在Redis中的延時操作中,整個過程原理非常簡單,他是針對每種事件維護了一個統計列表,每個列表中包括了了采集的一系列樣本,每個樣本包括,此樣本的創建時間和此樣本的延時時間。event==》對SampleSeriesList 是一個字典的映射關系。下面看看,里面關鍵的采集點,名叫latencySample采集點的結構定義:
~~~
/* Representation of a latency sample: the sampling time and the latency
* observed in milliseconds. */
/* 延時樣品例子 */
struct latencySample {
//延時Sample創建的時間
int32_t time; /* We don't use time_t to force 4 bytes usage everywhere. */
//延時的具體時間, 單位為毫秒
uint32_t latency; /* Latency in milliseconds. */
};
~~~
字典中維護的可不是一個Sample結點,而是一個結點列表結構體:
~~~
/* The latency time series for a given event. */
/* 針對某個事件采集的一系列延時sample */
struct latencyTimeSeries {
//下一個延時Sample的下標
int idx; /* Index of the next sample to store. */
//最大的延時
uint32_t max; /* Max latency observed for this event. */
//最近的延時記錄
struct latencySample samples[LATENCY_TS_LEN]; /* Latest history. */
};
~~~
在Redis代碼的設計中,因為延時是用來測試和結果分析的,所以,作者還設計了用于后面分析報告中會用到的數據統計結構體;
~~~
/* Latency statistics structure. */
/* 延時sample的數據統計結果結構體 */
struct latencyStats {
//絕對最高的延時時間
uint32_t all_time_high; /* Absolute max observed since latest reset. */
//平均Sample延時時間
uint32_t avg; /* Average of current samples. */
//Sample的最小延時時間
uint32_t min; /* Min of current samples. */
//Sample的最大延時時間
uint32_t max; /* Max of current samples. */
//平均相對誤差,與平均延時相比
uint32_t mad; /* Mean absolute deviation. */
//samples的總數
uint32_t samples; /* Number of non-zero samples. */
//最早的延時記錄點的創建時間
time_t period; /* Number of seconds since first event and now. */
};
~~~
?意思都非常的直接,那么一個簡單的Sample如何進行事件的檢測呢?
~~~
/* Start monitoring an event. We just set the current time. */
/* 對某個事件設置監聽,就是設置一下當前的時間 */
#define latencyStartMonitor(var) if (server.latency_monitor_threshold) { \
var = mstime(); \
} else { \
var = 0; \
}
/* End monitoring an event, compute the difference with the current time
* to check the amount of time elapsed. */
/* 結束監聽,算出過了多少時間 */
#define latencyEndMonitor(var) if (server.latency_monitor_threshold) { \
var = mstime() - var; \
}
~~~
很簡單,記錄開始時間,記錄結束時間,中間的差值就是延時時間了,如果超出給定的時間范圍,就加入到延時列表中:
~~~
/* Add the sample only if the elapsed time is >= to the configured threshold. */
/* 如果延時時間超出server.latency_monitor_threshold,則將Sample加入延時列表中 */
#define latencyAddSampleIfNeeded(event,var) \
if (server.latency_monitor_threshold && \
(var) >= server.latency_monitor_threshold) \
latencyAddSample((event),(var));
~~~
我們重點關注一下,latencyAddSample,就是把采樣結點加入到記錄中,步驟如下:
1.根據傳入的event事件,在server.latency_events找到key為event事件 的val,即一個latencyTimeSeries
2.在這個latencyTimeSeries的struct latencySample samples[LATENCY_TS_LEN]中添加一個新的Sample
實現代碼如下:
~~~
/* Add the specified sample to the specified time series "event".
* This function is usually called via latencyAddSampleIfNeeded(), that
* is a macro that only adds the sample if the latency is higher than
* server.latency_monitor_threshold. */
/* 添加Sample到指定的Event對象的Sample列表中 */
void latencyAddSample(char *event, mstime_t latency) {
//找出Event對應的延時Sample記錄結構體
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
time_t now = time(NULL);
int prev;
/* Create the time series if it does not exist. */
if (ts == NULL) {
ts = zmalloc(sizeof(*ts));
ts->idx = 0;
ts->max = 0;
memset(ts->samples,0,sizeof(ts->samples));
//如果ts為空,重新添加,一個Event,對應一個latencyTimeSeries
dictAdd(server.latency_events,zstrdup(event),ts);
}
/* If the previous sample is in the same second, we update our old sample
* if this latency is > of the old one, or just return. */
prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
if (ts->samples[prev].time == now) {
if (latency > ts->samples[prev].latency)
ts->samples[prev].latency = latency;
return;
}
//為Sample賦值
ts->samples[ts->idx].time = time(NULL);
ts->samples[ts->idx].latency = latency;
if (latency > ts->max) ts->max = latency;
ts->idx++;
if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
}
~~~
結點都出來之后,當然會進行結構的分析統計了,這時就用到了latencyStats結構體;
~~~
/* Analyze the samples avaialble for a given event and return a structure
* populate with different metrics, average, MAD, min, max, and so forth.
* Check latency.h definition of struct latenctStat for more info.
* If the specified event has no elements the structure is populate with
* zero values. */
/* 分析某個時間Event的延時結果,結果信息存入latencyStats結構體中 */
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
int j;
uint64_t sum;
//初始化延時統計結果結構體的變量
ls->all_time_high = ts ? ts->max : 0;
ls->avg = 0;
ls->min = 0;
ls->max = 0;
ls->mad = 0;
ls->samples = 0;
ls->period = 0;
if (!ts) return;
/* First pass, populate everything but the MAD. */
sum = 0;
for (j = 0; j < LATENCY_TS_LEN; j++) {
if (ts->samples[j].time == 0) continue;
ls->samples++;
if (ls->samples == 1) {
ls->min = ls->max = ts->samples[j].latency;
} else {
//找出延時最大和最小的延時時間
if (ls->min > ts->samples[j].latency)
ls->min = ts->samples[j].latency;
if (ls->max < ts->samples[j].latency)
ls->max = ts->samples[j].latency;
}
sum += ts->samples[j].latency;
/* Track the oldest event time in ls->period. */
if (ls->period == 0 || ts->samples[j].time < ls->period)
//最早的延時記錄點的創建時間
ls->period = ts->samples[j].time;
}
/* So far avg is actually the sum of the latencies, and period is
* the oldest event time. We need to make the first an average and
* the second a range of seconds. */
if (ls->samples) {
ls->avg = sum / ls->samples;
ls->period = time(NULL) - ls->period;
if (ls->period == 0) ls->period = 1;
}
/* Second pass, compute MAD. */
//計算平均相對誤差,與平均延時相比
sum = 0;
for (j = 0; j < LATENCY_TS_LEN; j++) {
int64_t delta;
if (ts->samples[j].time == 0) continue;
delta = (int64_t)ls->avg - ts->samples[j].latency;
if (delta < 0) delta = -delta;
sum += delta;
}
if (ls->samples) ls->mad = sum / ls->samples;
}
~~~
當然還可以利用這些采集的點,畫一個微線圖,更加形象的展示出來:
~~~
#define LATENCY_GRAPH_COLS 80
/* 利用延時的Sample點,畫出對應的微線圖 */
sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
int j;
struct sequence *seq = createSparklineSequence();
sds graph = sdsempty();
uint32_t min = 0, max = 0;
for (j = 0; j < LATENCY_TS_LEN; j++) {
int i = (ts->idx + j) % LATENCY_TS_LEN;
int elapsed;
char *label;
char buf[64];
if (ts->samples[i].time == 0) continue;
/* Update min and max. */
if (seq->length == 0) {
min = max = ts->samples[i].latency;
} else {
if (ts->samples[i].latency > max) max = ts->samples[i].latency;
if (ts->samples[i].latency < min) min = ts->samples[i].latency;
}
/* Use as label the number of seconds / minutes / hours / days
* ago the event happened. */
elapsed = time(NULL) - ts->samples[i].time;
if (elapsed < 60)
snprintf(buf,sizeof(buf),"%ds",elapsed);
else if (elapsed < 3600)
snprintf(buf,sizeof(buf),"%dm",elapsed/60);
else if (elapsed < 3600*24)
snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
else
snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
label = zstrdup(buf);
sparklineSequenceAddSample(seq,ts->samples[i].latency,label);
}
graph = sdscatprintf(graph,
"%s - high %lu ms, low %lu ms (all time high %lu ms)\n", event,
(unsigned long) max, (unsigned long) min, (unsigned long) ts->max);
for (j = 0; j < LATENCY_GRAPH_COLS; j++)
graph = sdscatlen(graph,"-",1);
graph = sdscatlen(graph,"\n",1);
//調用sparkline函數畫微線圖
graph = sparklineRender(graph,seq,LATENCY_GRAPH_COLS,4,SPARKLINE_FILL);
freeSparklineSequence(seq);
//返回微線圖字符串
return graph;
}
~~~
在Redis還封裝了一些命令供外部調用,這里就不分析了,就是對上述方法的復合調用:
~~~
/* ---------------------------- Latency API --------------------------------- */
void latencyMonitorInit(void) /* 延時監聽初始化操作,創建Event字典對象 */
void latencyAddSample(char *event, mstime_t latency) /* 添加Sample到指定的Event對象的Sample列表中 */
int latencyResetEvent(char *event_to_reset) /* 重置Event事件的延遲,刪除字典中的event的記錄 */
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) /* 分析某個時間Event的延時結果,結果信息存入latencyStats結構體中 */
sds createLatencyReport(void) /* 根據延時Sample的結果,創建閱讀性比較好的分析報告 */
void latencyCommandReplyWithSamples(redisClient *c, struct latencyTimeSeries *ts)
void latencyCommandReplyWithLatestEvents(redisClient *c)
sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts)
void latencyCommand(redisClient *c)
~~~
Redis的延時類文件的分析也結束了,分析了這么長時間Redis的Redis代碼,感覺每一塊的代碼都會有他的亮點存在,分析了30多期下來,還是學到了很多網上所學不到的知識,網上更多的是Redis主流思想的學習,像一些比較細小點,也只有自己品味,自己才能夠真正的體會。
- 前言
- (一)--Redis結構解析
- (二)--結構體分析(1)
- (三)---dict哈希結構
- (四)-- sds字符串
- (五)--- sparkline微線圖
- (六)--- ziplist壓縮列表
- (七)--- zipmap壓縮圖
- (八)--- t_hash哈希轉換
- (九)--- t_list,t_string的分析
- (十)--- testhelp.h小型測試框架和redis-check-aof.c日志檢測
- (十一)--- memtest內存檢測
- (十二)--- redis-check-dump本地數據庫檢測
- (十三)--- redis-benchmark性能測試
- (十四)--- rdb.c本地數據庫操作
- (十五)--- aof-append only file解析
- (十六)--- config配置文件
- (十七)--- multi事務操作
- (十八)--- db.c內存數據庫操作
- (十九)--- replication主從數據復制的實現
- (二十)--- ae事件驅動
- (二十一)--- anet網絡通信的封裝
- (二十二)--- networking網絡協議傳輸
- (二十三)--- CRC循環冗余算法和RAND隨機數算法
- (二十四)--- tool工具類(2)
- (二十五)--- zmalloc內存分配實現
- (二十六)--- slowLog和hyperloglog
- (二十七)--- rio系統I/O的封裝
- (二十八)--- object創建和釋放redisObject對象
- (二十九)--- bio后臺I/O服務的實現
- (三十)--- pubsub發布訂閱模式
- (三十一)--- latency延遲分析處理
- (三十二)--- redis-cli.c客戶端命令行接口的實現(1)
- (三十三)--- redis-cli.c客戶端命令行接口的實現(2)
- (三十四)--- redis.h服務端的實現分析(1)
- (三十五)--- redis.c服務端的實現分析(2)
- (三十六)--- Redis中的11大優秀設計