? ? ?今天講的這個是用來給redis數據庫做性能測試的,說到性能測試,感覺這必然是高大上的操作了,redis性能測試,測的到底是哪方面的性能,如何測試,通過什么指標反映此次測試的性能好壞呢,下面我通過源碼給大家做一一解答。
? ?redis做的性能測試時對立面的基本操作做的檢測,比如Client客戶端執行set,get,lpush等數據操作的性能,可以從他的測試程序可以看出:
~~~
if (test_is_selected("get")) {
len = redisFormatCommand(&cmd,"GET key:__rand_int__");
benchmark("GET",cmd,len);
free(cmd);
}
if (test_is_selected("incr")) {
len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");
benchmark("INCR",cmd,len);
free(cmd);
}
if (test_is_selected("lpush")) {
len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
benchmark("LPUSH",cmd,len);
free(cmd);
}
if (test_is_selected("lpop")) {
len = redisFormatCommand(&cmd,"LPOP mylist");
benchmark("LPOP",cmd,len);
free(cmd);
}
~~~
那么通過什么指標反映測試性能的好壞之分呢,在這里我們使用的就是延時性來判斷,最簡單的想法,就是在測試到額最開始,記錄一個時間,中間執行測試操作,在操作結束在記錄一個時間,中間的時間差就是執行的時間,時間越短說明性能越好。這也正是redis性能測試的做法。
~~~
/* 對指定的CMD命令做性能測試 */
static void benchmark(char *title, char *cmd, int len) {
client c;
config.title = title;
config.requests_issued = 0;
config.requests_finished = 0;
c = createClient(cmd,len,NULL);
createMissingClients(c);
config.start = mstime();
aeMain(config.el);
//最后通過計算總延時,顯示延時報告,體現性能測試的結果
config.totlatency = mstime()-config.start;
showLatencyReport();
freeAllClients();
}
~~~
因為這樣的操作要求時間精度比較高,用秒做單位肯定不行了,所以這里用的是ms毫秒,在這里添加個知識點,在這里用到了時間相關的結構體,在Linux里也存在:
~~~
/* 介紹一下struct timeval結構體
struct timeval結構體在time.h中的定義為:
struct timeval
{
__time_t tv_sec; // Seconds.
__suseconds_t tv_usec; // Microseconds. (微秒)
}; */
~~~
那么下面是最關鍵的問題了,如何測,測試肯定要通過一個模擬客戶端,按照配置文件中的設置去測試,所以必須存在一個配置信息,然后我們通過我們想要的配置再去逐步測試,亮出config,配置信息:
~~~
/* config配置信息結構體,static靜態變量,使得全局只有一個 */
static struct config {
//消息事件
aeEventLoop *el;
const char *hostip;
int hostport;
//據此判斷是否是本地測試
const char *hostsocket;
//Client總數量
int numclients;
int liveclients;
//請求的總數
int requests;
int requests_issued;
//請求完成的總數
int requests_finished;
int keysize;
int datasize;
int randomkeys;
int randomkeys_keyspacelen;
int keepalive;
int pipeline;
long long start;
long long totlatency;
long long *latency;
const char *title;
//Client列表,這個在下面會經常提起
list *clients;
int quiet;
int csv;
//判斷是否loop循環處理
int loop;
int idlemode;
int dbnum;
sds dbnumstr;
char *tests;
char *auth;
} config;
typedef struct _client {
//redis上下文
redisContext *context;
//此緩沖區將用于后面的讀寫handler
sds obuf;
//rand指針數組
char **randptr; /* Pointers to :rand: strings inside the command buf */
//randptr中指針個數
size_t randlen; /* Number of pointers in client->randptr */
//randptr中沒有被使用的指針個數
size_t randfree; /* Number of unused pointers in client->randptr */
unsigned int written; /* Bytes of 'obuf' already written */
//請求的發起時間
long long start; /* Start time of a request */
//請求的延時
long long latency; /* Request latency */
//請求的等待個數
int pending; /* Number of pending requests (replies to consume) */
int selectlen; /* If non-zero, a SELECT of 'selectlen' bytes is currently
used as a prefix of the pipline of commands. This gets
discarded the first time it's sent. */
} *client;
~~~
上面還附帶了client的一些信息,這里的Client和之前的RedisClient還不是一個東西,也就是說,這里的Client會根據config結構體中的配置做相應的操作。client根據獲得到命令無非2種操作,read和Write,所以在后面的事件處理中也是針對這2種事件的處理,這里給出read的方法:
~~~
/* 讀事件的處理方法 */
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
client c = privdata;
void *reply = NULL;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
/* Calculate latency only for the first read event. This means that the
* server already sent the reply and we need to parse it. Parsing overhead
* is not part of the latency, so calculate it only once, here. */
//計算延時,然后比較延時,取得第一個read 的event事件
if (c->latency < 0) c->latency = ustime()-(c->start);
if (redisBufferRead(c->context) != REDIS_OK) {
//首先判斷能否讀
fprintf(stderr,"Error: %s\n",c->context->errstr);
exit(1);
} else {
while(c->pending) {
if (redisGetReply(c->context,&reply) != REDIS_OK) {
fprintf(stderr,"Error: %s\n",c->context->errstr);
exit(1);
}
if (reply != NULL) {
//獲取reply回復,如果這里出錯,也會直接退出
if (reply == (void*)REDIS_REPLY_ERROR) {
fprintf(stderr,"Unexpected error reply, exiting...\n");
exit(1);
}
freeReplyObject(reply);
if (c->selectlen) {
size_t j;
/* This is the OK from SELECT. Just discard the SELECT
* from the buffer. */
//執行到這里,請求已經執行成功,等待的請求數減1
c->pending--;
sdsrange(c->obuf,c->selectlen,-1);
/* We also need to fix the pointers to the strings
* we need to randomize. */
for (j = 0; j < c->randlen; j++)
c->randptr[j] -= c->selectlen;
c->selectlen = 0;
continue;
}
if (config.requests_finished < config.requests)
config.latency[config.requests_finished++] = c->latency;
c->pending--;
if (c->pending == 0) {
//調用客戶端done完成后的方法
clientDone(c);
break;
}
} else {
break;
}
}
}
}
~~~
這個方法其實是一個回調方法,會作為參數傳入另一個函數中,redis的函數式編程的思想又再次體現了,在這些操作都執行好了之后,會有個延時報告,針對各種操作的延時統計,這時我們就能知道,性能之間的比較了:
~~~
/* 輸出請求延時 */
static void showLatencyReport(void) {
int i, curlat = 0;
float perc, reqpersec;
reqpersec = (float)config.requests_finished/((float)config.totlatency/1000);
if (!config.quiet && !config.csv) {
printf("====== %s ======\n", config.title);
printf(" %d requests completed in %.2f seconds\n", config.requests_finished,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
printf(" %d bytes payload\n", config.datasize);
printf(" keep alive: %d\n", config.keepalive);
printf("\n");
//將請求按延時排序
qsort(config.latency,config.requests,sizeof(long long),compareLatency);
for (i = 0; i < config.requests; i++) {
if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {
curlat = config.latency[i]/1000;
perc = ((float)(i+1)*100)/config.requests;
printf("%.2f%% <= %d milliseconds\n", perc, curlat);
}
}
printf("%.2f requests per second\n\n", reqpersec);
} else if (config.csv) {
printf("\"%s\",\"%.2f\"\n", config.title, reqpersec);
} else {
printf("%s: %.2f requests per second\n", config.title, reqpersec);
}
}
~~~
當然你能更改配置文件,做你想做的性能測試,不過這里我想吐槽一點,這么多個if判斷語句不是很好吧,換成switch也比這簡潔啊:
~~~
/* Returns number of consumed options. */
/* 根據讀入的參數,設置config配置文件 */
int parseOptions(int argc, const char **argv) {
int i;
int lastarg;
int exit_status = 1;
for (i = 1; i < argc; i++) {
lastarg = (i == (argc-1));
//通過多重if判斷,但個人感覺不夠優美,穩定性略差
if (!strcmp(argv[i],"-c")) {
if (lastarg) goto invalid;
config.numclients = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-n")) {
if (lastarg) goto invalid;
config.requests = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-k")) {
if (lastarg) goto invalid;
config.keepalive = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-h")) {
if (lastarg) goto invalid;
config.hostip = strdup(argv[++i]);
} else if (!strcmp(argv[i],"-p")) {
if (lastarg) goto invalid;
config.hostport = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-s")) {
if (lastarg) goto invalid;
config.hostsocket = strdup(argv[++i]);
} else if (!strcmp(argv[i],"-a") ) {
if (lastarg) goto invalid;
config.auth = strdup(argv[++i]);
} else if (!strcmp(argv[i],"-d")) {
if (lastarg) goto invalid;
config.datasize = atoi(argv[++i]);
if (config.datasize < 1) config.datasize=1;
if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024;
} else if (!strcmp(argv[i],"-P")) {
if (lastarg) goto invalid;
config.pipeline = atoi(argv[++i]);
//......省略
~~~
redis的性能測試還能支持本地測試和指定Ip,端口測試,挺方便的。下面列出全部的API:
~~~
/* Prototypes */
/* 方法原型 */
static void createMissingClients(client c); /* 創建沒有Command命令要求的clint */
static long long ustime(void) /* 返回當期時間的單位為微秒的格式 */
static long long mstime(void) /* 返回當期時間的單位為毫秒的格式 */
static void freeClient(client c) /* 釋放Client */
static void freeAllClients(void) /* 釋放所有的Client */
static void resetClient(client c) /* 重置Client */
static void randomizeClientKey(client c) /* 隨機填充client里的randptr中的key值 */
static void clientDone(client c) /* Client完成后的調用方法 */
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) /* 讀事件的處理方法 */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) /* 寫事件方法處理 */
static client createClient(char *cmd, size_t len, client from) /* 創建一個基準的Client */
static int compareLatency(const void *a, const void *b) /* 比較延時 */
static void showLatencyReport(void) /* 輸出請求延時 */
static void benchmark(char *title, char *cmd, int len) /* 對指定的CMD命令做性能測試 */
int parseOptions(int argc, const char **argv) /* 根據讀入的參數,設置config配置文件 */
int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) /* 顯示Request執行的速度,簡稱RPS */
int test_is_selected(char *name) /* 檢測config中的命令是否被選中 */
~~~
- 前言
- (一)--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大優秀設計