请选择 进入手机版 | 继续访问电脑版

Redis中国用户组(CRUG)论坛

 找回密码
 立即注册

扫一扫,访问微社区

搜索
热搜: 活动 交友 discuz
查看: 4036|回复: 0

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

[复制链接]
  • TA的每日心情
    开心
    2019-4-19 23:07
  • 签到天数: 95 天

    [LV.6]常住居民II

    406

    主题

    515

    帖子

    3981

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    3981

    最佳新人活跃会员宣传达人突出贡献优秀版主荣誉管理论坛元老

    发表于 2016-4-5 10:08:29 | 显示全部楼层 |阅读模式

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

         redis做的性能测试时对立面的基本操作做的检测,比如Client客户端执行set,get,lpush等数据操作的性能,可以从他的测试程序可以看出:

    1. if (test_is_selected("get")) {  
    2.            len = redisFormatCommand(&cmd,"GET key:__rand_int__");  
    3.            benchmark("GET",cmd,len);  
    4.            free(cmd);  
    5.        }  
    6.   
    7.        if (test_is_selected("incr")) {  
    8.            len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");  
    9.            benchmark("INCR",cmd,len);  
    10.            free(cmd);  
    11.        }  
    12.   
    13.        if (test_is_selected("lpush")) {  
    14.            len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);  
    15.            benchmark("LPUSH",cmd,len);  
    16.            free(cmd);  
    17.        }  
    18.   
    19.        if (test_is_selected("lpop")) {  
    20.            len = redisFormatCommand(&cmd,"LPOP mylist");  
    21.            benchmark("LPOP",cmd,len);  
    22.            free(cmd);  
    23.        }  
    复制代码

    那么通过什么指标反映测试性能的好坏之分呢,在这里我们使用的就是延时性来判断,最简单的想法,就是在测试到额最开始,记录一个时间,中间执行测试操作,在操作结束在记录一个时间,中间的时间差就是执行的时间,时间越短说明性能越好。这也正是redis性能测试的做法。
    1. /* 对指定的CMD命令做性能测试 */  
    2. static void benchmark(char *title, char *cmd, int len) {  
    3.     client c;  
    4.   
    5.     config.title = title;  
    6.     config.requests_issued = 0;  
    7.     config.requests_finished = 0;  
    8.   
    9.     c = createClient(cmd,len,NULL);  
    10.     createMissingClients(c);  
    11.   
    12.     config.start = mstime();  
    13.     aeMain(config.el);  
    14.     //最后通过计算总延时,显示延时报告,体现性能测试的结果  
    15.     config.totlatency = mstime()-config.start;  
    16.   
    17.     showLatencyReport();  
    18.     freeAllClients();  
    19. }  
    复制代码

    因为这样的操作要求时间精度比较高,用秒做单位肯定不行了,所以这里用的是ms毫秒,在这里添加个知识点,在这里用到了时间相关的结构体,在Linux里也存在:
    1. /* 介绍一下struct timeval结构体
    2. struct timeval结构体在time.h中的定义为:
    3. struct timeval
    4. {
    5.   __time_t tv_sec;        // Seconds.  
    6.   __suseconds_t tv_usec;    // Microseconds. (微秒)
    7. }; */  
    复制代码

    那么下面是最关键的问题了,如何测,测试肯定要通过一个模拟客户端,按照配置文件中的设置去测试,所以必须存在一个配置信息,然后我们通过我们想要的配置再去逐步测试,亮出config,配置信息:
    1. /* config配置信息结构体,static静态变量,使得全局只有一个 */  
    2. static struct config {  
    3.     //消息事件  
    4.     aeEventLoop *el;  
    5.     const char *hostip;  
    6.     int hostport;  
    7.     //据此判断是否是本地测试  
    8.     const char *hostsocket;  
    9.     //Client总数量  
    10.     int numclients;  
    11.     int liveclients;  
    12.     //请求的总数  
    13.     int requests;  
    14.     int requests_issued;  
    15.     //请求完成的总数  
    16.     int requests_finished;  
    17.     int keysize;  
    18.     int datasize;  
    19.     int randomkeys;  
    20.     int randomkeys_keyspacelen;  
    21.     int keepalive;  
    22.     int pipeline;  
    23.     long long start;  
    24.     long long totlatency;  
    25.     long long *latency;  
    26.     const char *title;  
    27.     //Client列表,这个在下面会经常提起  
    28.     list *clients;  
    29.     int quiet;  
    30.     int csv;  
    31.     //判断是否loop循环处理  
    32.     int loop;  
    33.     int idlemode;  
    34.     int dbnum;  
    35.     sds dbnumstr;  
    36.     char *tests;  
    37.     char *auth;  
    38. } config;  
    39.   
    40. typedef struct _client {  
    41.     //redis上下文  
    42.     redisContext *context;  
    43.     //此缓冲区将用于后面的读写handler  
    44.     sds obuf;  
    45.     //rand指针数组  
    46.     char **randptr;         /* Pointers to :rand: strings inside the command buf */  
    47.     //randptr中指针个数  
    48.     size_t randlen;         /* Number of pointers in client->randptr */  
    49.     //randptr中没有被使用的指针个数  
    50.     size_t randfree;        /* Number of unused pointers in client->randptr */  
    51.     unsigned int written;   /* Bytes of 'obuf' already written */  
    52.     //请求的发起时间  
    53.     long long start;        /* Start time of a request */  
    54.     //请求的延时  
    55.     long long latency;      /* Request latency */  
    56.     //请求的等待个数  
    57.     int pending;            /* Number of pending requests (replies to consume) */  
    58.     int selectlen;  /* If non-zero, a SELECT of 'selectlen' bytes is currently
    59.                        used as a prefix of the pipline of commands. This gets
    60.                        discarded the first time it's sent. */  
    61. } *client;  
    复制代码

    上面还附带了client的一些信息,这里的Client和之前的RedisClient还不是一个东西,也就是说,这里的Client会根据config结构体中的配置做相应的操作。client根据获得到命令无非2种操作,read和Write,所以在后面的事件处理中也是针对这2种事件的处理,这里给出read的方法:
    1. /* 读事件的处理方法 */  
    2. static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {  
    3.     client c = privdata;  
    4.     void *reply = NULL;  
    5.     REDIS_NOTUSED(el);  
    6.     REDIS_NOTUSED(fd);  
    7.     REDIS_NOTUSED(mask);  
    8.   
    9.     /* Calculate latency only for the first read event. This means that the
    10.      * server already sent the reply and we need to parse it. Parsing overhead
    11.      * is not part of the latency, so calculate it only once, here. */  
    12.     //计算延时,然后比较延时,取得第一个read 的event事件  
    13.     if (c->latency < 0) c->latency = ustime()-(c->start);  
    14.   
    15.     if (redisBufferRead(c->context) != REDIS_OK) {  
    16.         //首先判断能否读  
    17.         fprintf(stderr,"Error: %s\n",c->context->errstr);  
    18.         exit(1);  
    19.     } else {  
    20.         while(c->pending) {  
    21.             if (redisGetReply(c->context,&reply) != REDIS_OK) {  
    22.                 fprintf(stderr,"Error: %s\n",c->context->errstr);  
    23.                 exit(1);  
    24.             }  
    25.             if (reply != NULL) {  
    26.                 //获取reply回复,如果这里出错,也会直接退出  
    27.                 if (reply == (void*)REDIS_REPLY_ERROR) {  
    28.                     fprintf(stderr,"Unexpected error reply, exiting...\n");  
    29.                     exit(1);  
    30.                 }  
    31.   
    32.                 freeReplyObject(reply);  
    33.                      
    34.                 if (c->selectlen) {  
    35.                     size_t j;  
    36.   
    37.                     /* This is the OK from SELECT. Just discard the SELECT
    38.                      * from the buffer. */  
    39.                     //执行到这里,请求已经执行成功,等待的请求数减1  
    40.                     c->pending--;  
    41.                     sdsrange(c->obuf,c->selectlen,-1);  
    42.                     /* We also need to fix the pointers to the strings
    43.                      * we need to randomize. */  
    44.                     for (j = 0; j < c->randlen; j++)  
    45.                         c->randptr[j] -= c->selectlen;  
    46.                     c->selectlen = 0;  
    47.                     continue;  
    48.                 }  
    49.   
    50.                 if (config.requests_finished < config.requests)  
    51.                     config.latency[config.requests_finished++] = c->latency;  
    52.                 c->pending--;  
    53.                 if (c->pending == 0) {  
    54.                     //调用客户端done完成后的方法  
    55.                     clientDone(c);  
    56.                     break;  
    57.                 }  
    58.             } else {  
    59.                 break;  
    60.             }  
    61.         }  
    62.     }  
    63. }  
    复制代码

    这个方法其实是一个回调方法,会作为参数传入另一个函数中,redis的函数式编程的思想又再次体现了,在这些操作都执行好了之后,会有个延时报告,针对各种操作的延时统计,这时我们就能知道,性能之间的比较了:
    1. /* 输出请求延时 */  
    2. static void showLatencyReport(void) {  
    3.     int i, curlat = 0;  
    4.     float perc, reqpersec;  
    5.   
    6.     reqpersec = (float)config.requests_finished/((float)config.totlatency/1000);  
    7.     if (!config.quiet && !config.csv) {  
    8.         printf("====== %s ======\n", config.title);  
    9.         printf("  %d requests completed in %.2f seconds\n", config.requests_finished,  
    10.             (float)config.totlatency/1000);  
    11.         printf("  %d parallel clients\n", config.numclients);  
    12.         printf("  %d bytes payload\n", config.datasize);  
    13.         printf("  keep alive: %d\n", config.keepalive);  
    14.         printf("\n");  
    15.   
    16.         //将请求按延时排序  
    17.         qsort(config.latency,config.requests,sizeof(long long),compareLatency);  
    18.         for (i = 0; i < config.requests; i++) {  
    19.             if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {  
    20.                 curlat = config.latency[i]/1000;  
    21.                 perc = ((float)(i+1)*100)/config.requests;  
    22.                 printf("%.2f%% <= %d milliseconds\n", perc, curlat);  
    23.             }  
    24.         }  
    25.         printf("%.2f requests per second\n\n", reqpersec);  
    26.     } else if (config.csv) {  
    27.         printf("\"%s\",\"%.2f\"\n", config.title, reqpersec);  
    28.     } else {  
    29.         printf("%s: %.2f requests per second\n", config.title, reqpersec);  
    30.     }  
    31. }  
    复制代码

    当然你能更改配置文件,做你想做的性能测试,不过这里我想吐槽一点,这么多个if判断语句不是很好吧,换成switch也比这简洁啊:
    1. /* Returns number of consumed options. */  
    2. /* 根据读入的参数,设置config配置文件 */  
    3. int parseOptions(int argc, const char **argv) {  
    4.     int i;  
    5.     int lastarg;  
    6.     int exit_status = 1;  
    7.   
    8.     for (i = 1; i < argc; i++) {  
    9.         lastarg = (i == (argc-1));  
    10.          
    11.         //通过多重if判断,但个人感觉不够优美,稳定性略差  
    12.         if (!strcmp(argv[i],"-c")) {  
    13.             if (lastarg) goto invalid;  
    14.             config.numclients = atoi(argv[++i]);  
    15.         } else if (!strcmp(argv[i],"-n")) {  
    16.             if (lastarg) goto invalid;  
    17.             config.requests = atoi(argv[++i]);  
    18.         } else if (!strcmp(argv[i],"-k")) {  
    19.             if (lastarg) goto invalid;  
    20.             config.keepalive = atoi(argv[++i]);  
    21.         } else if (!strcmp(argv[i],"-h")) {  
    22.             if (lastarg) goto invalid;  
    23.             config.hostip = strdup(argv[++i]);  
    24.         } else if (!strcmp(argv[i],"-p")) {  
    25.             if (lastarg) goto invalid;  
    26.             config.hostport = atoi(argv[++i]);  
    27.         } else if (!strcmp(argv[i],"-s")) {  
    28.             if (lastarg) goto invalid;  
    29.             config.hostsocket = strdup(argv[++i]);  
    30.         } else if (!strcmp(argv[i],"-a") ) {  
    31.             if (lastarg) goto invalid;  
    32.             config.auth = strdup(argv[++i]);  
    33.         } else if (!strcmp(argv[i],"-d")) {  
    34.             if (lastarg) goto invalid;  
    35.             config.datasize = atoi(argv[++i]);  
    36.             if (config.datasize < 1) config.datasize=1;  
    37.             if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024;  
    38.         } else if (!strcmp(argv[i],"-P")) {  
    39.             if (lastarg) goto invalid;  
    40.             config.pipeline = atoi(argv[++i]);  
    41. //......省略  
    复制代码

    redis的性能测试还能支持本地测试和指定Ip,端口测试,挺方便的。下面列出全部的API:
    1. /* Prototypes */  
    2. /* 方法原型 */  
    3. static void createMissingClients(client c); /* 创建没有Command命令要求的clint */  
    4. static long long ustime(void) /* 返回当期时间的单位为微秒的格式 */  
    5. static long long mstime(void) /* 返回当期时间的单位为毫秒的格式 */  
    6. static void freeClient(client c) /* 释放Client */  
    7. static void freeAllClients(void) /* 释放所有的Client */  
    8. static void resetClient(client c) /* 重置Client */  
    9. static void randomizeClientKey(client c) /* 随机填充client里的randptr中的key值 */  
    10. static void clientDone(client c) /* Client完成后的调用方法 */  
    11. static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) /* 读事件的处理方法 */  
    12. static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) /* 写事件方法处理 */  
    13. static client createClient(char *cmd, size_t len, client from) /* 创建一个基准的Client */  
    14. static int compareLatency(const void *a, const void *b) /* 比较延时 */  
    15. static void showLatencyReport(void) /* 输出请求延时 */  
    16. static void benchmark(char *title, char *cmd, int len) /* 对指定的CMD命令做性能测试 */  
    17. int parseOptions(int argc, const char **argv) /* 根据读入的参数,设置config配置文件 */  
    18. int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) /* 显示Request执行的速度,简称RPS */  
    19. int test_is_selected(char *name) /* 检测config中的命令是否被选中 */  
    复制代码

    转自:http://blog.csdn.net/androidlushangderen/article/details/40211907
    上一篇:Redis源码分析(十二)--- redis-check-dump本地数据库检测
    下一篇:Redis源码分析(十四)--- rdb.c本地数据库操作

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Archiver|手机版|小黑屋|Redis中国用户组 ( 京ICP备15003959号

    GMT+8, 2019-11-20 08:19 , Processed in 0.087224 second(s), 29 queries .

    Powered by Discuz! X3.2

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表