Redis-benchmark源码修改笔记——吞吐率(RPS)、平均响应时间、99%响应时间深入探究

这篇文章总结的是Redis的benchmark源码修改中增加一些性能测试指标所应思考的问题,以及解决问题的方法,权当本渣渣一篇笔记,如果您发现有疏漏错误之处,烦请留言指出!

如不清楚吞吐率(RPS)、平均响应时间、99%响应时间的概念请参考:性能测试的几个指标(并发数、吞吐率、响应时间、平均响应时间、99%响应时间)

1、原生Redis的两种主要性能测试指标

在原生的Redis benchmark性能测试中,只提供了两种主要的测试指标:

一是RPS(requests per second),即每秒完成的请求数量,反映的是服务器处理请求的吞吐率,这是redis最主要的性能测试指标。

二是,当你不使用quiet模式输出(不使用-q参数)时,redis会展示出响应时间低于1ms、2ms、3ms...等的请求数占总请求数的百分比。如下图所示。

下面我们分别说明以上两种测试指标的实现方式:

首先我们需要知道benchmark.c文件的一个重要的数据结构static struct config。

本文所讲的性能测试指标的最红显示和实现都是在redis-benchmark.c文件中的showLatencyReport函数中实现,所有修改也都在此函数中进行。

static struct config {
    aeEventLoop *el;
    const char *hostip;
    int hostport;
    const char *hostsocket;
    int numclients;
    int liveclients;
    int requests;//总请求数
    int requests_issued;
    int requests_finished;//已完成的请求数
    int keysize;
    int datasize;
    int randomkeys;//1表示key值随机化
    int randomkeys_keyspacelen;//key值取0~该值中的随机数
    int keepalive;
    int pipeline;
    int showerrors;
    long long start;//性能测试前记录开始时间
    long long totlatency;//记录性能测试所用的总延时
    long long *latency;//算是一个数组指针,在堆空间开辟,存放每一笔请求的延时,一共有config.requests个
    const char *title;
    list *clients;
    int quiet;
    int csv;
    int loop;
    int idlemode;
    int dbnum;
    sds dbnumstr;
    char *tests;
    char *auth;
} config;

主要关注requests、 start、totlatency、*latency!

(1)RPS的实现:总请求数 / 性能测试的总延时, 即 

reqpersec = (float)config.requests_finished/((float)config.totlatency/1000);

(2)响应时间低于1ms、2ms、3ms...等的请求数占总请求数的百分比实现:先对latency数组中的所有请求的响应时间进行从小到大排序,然后遍历,当遍历到响应时间低于1ms时计算响应百分比即可,以此类推。

原生Redis实现的代码如下(以上两种指标的实现)

//定义比较函数
static int compareLatency(const void *a, const void *b) {
    return (*(long long*)a)-(*(long long*)b);
}

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++) {//打印出低于1ms、2ms...等的请求数与总请求数的百分比
            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);//打印rps
    } else if (config.csv) {
        printf("\"%s\",\"%.2f\"\n", config.title, reqpersec);
    } else {
    	printf("%s: %.2f requests per second\n", config.title, reqpersec);
}

2、添加平均响应时间、99%响应时间

平均响应时间的实现

平均响应时间=总响应时间÷总请求数, 公式很简单,但是我们深入思考会发现一些问题:刚开始时我把总响应时间误以为就是上面的totlatency,后来发现不对,这里说清楚区别:

totlatency是在benchmark测试前后做的一个时间记录差值,即相当于服务器处理完requests条请求所花费的时间,这里面也包含了一部分客户端的函数执行的时间开销(我测试时使用单个客户端连接(即只有一个并发连接)、不使用管道pipelining的时候,测试出来的totlatency是大于【把latency数组中所有请求的响应时间相加所得sum】)。

所以把latency数组中所有请求的响应时间相加所得sum(我们姑且称之为响应时间之和),跟延迟差值totlatency是不一样的!

响应时间之和是所有请求的响应时间加到一块,而totlatency只是测试开始前后的一个时间差!但是你千万别以为响应时间之和就应该小于或者等于totlatency(因为你在测试过程中也有客户端的一些函数时间执行开销),我刚开始就是这样认为的!这种情况仅仅是当只有一个并发连接,并且不使用pipeline的时候,它确实是这样的!

当不止一个并发连接的时候,客户端怎么操作的呢? 假如说有10个并发连接客户端,那么这十个客户端就会逐个发送请求,哪个客户端收到请求回复了就再接着发请求,一直到请求数全部得到回复。这里模拟的是10个客户端并发连接请求的情况。所以这样你会发现:最后算的响应时间之和是远远大于totlatency的!

当使用pipelining的时候呢?假如-P的参数是5,即一条管道可以一次放5条请求。使用管道其实就是每次客户端把5个请求打包到一起然后一块发给服务器进行处理,服务器处理完之后把五个请求的结果再打包到一起返回给客户端。所以五条请求的响应时间是一样的!那这个时候五条请求的响应时间之和怎么算呢?是五个请求的响应时间全加起来,还是只算一个响应时间呢? 自习想一样,应该是把5个请求看做一个整体,他们的响应时间之和就是其中一个的响应时间。为什么这样算呢?很明显人家五个一起花了5ms,你不能说人家五个一共花了25ms吧! 比如说5个人团购一张券吃饭花了5块,那么每个人实质上是只用了1块,五个人一共用5块,而不是25块。所以当我们使用管道的时候,求平均响应时间和99%响应时间的时候需要整体除以管道大小pipeline。

99%响应时间的实现

先排序,然后找到99%所在的位置,把对应位置的响应时间记录即可。

修改后的实现代码

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);//对每条请求的响应时间进行升序排序
		long long sumLatency = 0;//响应时间之和
	    float avg, percentile99 = (float)config.latency[config.requests - 1]/1000;//平均响应时间、99%响应时间(初始化为最后一个请求的延迟),单位ms
		int flag = 0;//临时辅助标志位
        for (i = 0; i < config.requests; i++) {
			sumLatency += config.latency[i];//requests条命令的响应时间总和
			if(flag == 0 && (float)(i + 1)/config.requests >= (float)0.99) { 
				percentile99 = (float)config.latency[i]/1000;//99%响应时间
				flag = 1;
			}
            if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {//打印出低于1ms、2ms...等的请求数与总请求数的百分比
                curlat = config.latency[i]/1000;
                perc = ((float)(i+1)*100)/config.requests;
                printf("%.2f%% <= %d milliseconds\n", perc, curlat);
            }
        }
		avg = (float)sumLatency/((float)config.requests*1000);//requests条请求的平均响应时间,单位是ms
		if(config.pipeline > 1) {//考虑使用管道的特殊情况
			avg /= config.pipeline;
			percentile99 /= config.pipeline;
		}
		//printf("总延迟: %lldms  响应时间之和: %lldms\n", config.totlatency, sumLatency/1000);
        printf("%.2f requests per second | average response time: %.2fms | 99%%response time: %.2fms \n\n", reqpersec, avg, percentile99);
    } else if (config.csv) {
        printf("\"%s\",\"%.2f\"\n", config.title, reqpersec);
    } else {
    	printf("%s: %.2f requests per second\n", config.title, reqpersec);
    }
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值