博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis源代码分析(十三)--- redis-benchmark性能測试
阅读量:6862 次
发布时间:2019-06-26

本文共 9847 字,大约阅读时间需要 32 分钟。

       今天讲的这个是用来给redis数据库做性能測试的,说到性能測试,感觉这必定是高大上的操作了。redis性能測试。測的究竟是哪方面的性能,怎样測试,通过什么指标反映此次測试的性能好坏呢。以下我通过源代码给大家做一一解答。

     redis做的性能測试时对立面的基本操作做的检測,比方Clientclient运行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. (微秒)}; */
那么以下是最关键的问题了。怎样測,測试肯定要通过一个模拟client,依照配置文件里的设置去測试,所以必须存在一个配置信息。然后我们通过我们想要的配置再去逐步測试,亮出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。port測试。挺方便的。

以下列出所有的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中的命令是否被选中 */

转载地址:http://epqyl.baihongyu.com/

你可能感兴趣的文章