您好,欢迎访问一九零五行业门户网

Redis源码分析(十三)---redis-benchmark性能测试

今天讲的这个是用来给redis数据库做性能测试的,说到性能测试,感觉这必然是高大上的操作了,redis性能测试,测的到底是哪方面的性能,如何测试,通过什么指标反映此次测试的性能好坏呢,下面我通过源码给大家做一一解答。 redis做的性能测试时对立面的基本
今天讲的这个是用来给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 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 randlen; j++) c->randptr[j] -= c->selectlen; c->selectlen = 0; continue; } if (config.requests_finished 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 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中的命令是否被选中 */
其它类似信息

推荐信息