设为首页 收藏本站
查看: 2834|回复: 0

[经验分享] Redis源码分析(十三)--- redis-benchmark性能测试

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-11-5 09:17:01 | 显示全部楼层 |阅读模式
  今天讲的这个是用来给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中的命令是否被选中 */  


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-27092-1-1.html 上篇帖子: Redis源码分析(十二)--- redis-check-dump本地数据库检测 下篇帖子: Redis源码分析(十四)--- rdb.c本地数据库操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

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