Redis源代码分析(十三)--- redis-benchmark性能測试
今天讲的这个是用来给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中的命令是否被选中 */
Redis源代码分析(十三)--- redis-benchmark性能測试的更多相关文章
- Redis源代码分析(一)--Redis结构解析
从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...
- Window平台搭建Redis分布式缓存集群 (一)server搭建及性能測试
百度定义:Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对很多其它.包含string(字符串).list(链表).set(集合).zset(sort ...
- Redis源代码分析(十)--- testhelp.h小测试框架和redis-check-aof.c
日志检测
周期分析struct结构体redis代码.最后,越多越发现很多的代码其实大同小异.于struct有袋1,2不分析文件,关于set集合的一些东西,就放在下次分析好了,在选择下个分析的对象时,我考虑了一下 ...
- Redis 性能測试
Redis 性能測试 Redis 性能測试是通过同一时候运行多个命令实现的. 语法 redis 性能測试的基本命令例如以下: redis-benchmark [option] [option valu ...
- 【Redis源代码剖析】 - Redis内置数据结构之压缩字典zipmap
原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51111230 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在 ...
- PAAS平台的web应用性能測试与分析
引言 为什么我会写这一篇博客,由于近期非常多京东云擎jae的用户反应一个问题就是他们部署在jae上面的应用訪问非常慢,有极少数应用甚至常常出现504超时现象.当然大家首先想到的是jae性能太差,这也是 ...
- Android性能測试 一些适用于Android Studio的代码审查和性能測试工具
导言: Android应用在CPU占用,内存消耗方面的性能指标是影响产品质量的重要因素,因为QQ管家,360手机助手等应用都提供直观的内存消耗,流量监控功能,致使用户比以往更加关注软件的性能,并以此进 ...
- mysql主键设置成auto_increment时,进行并发性能測试出现主键反复Duplicate entry 'xxx' for key 'PRIMARY'
mysql主键设置成auto_increment时,进行并发性能測试出现主键反复Duplicate entry 'xxx' for key 'PRIMARY' 解决方法: 在my.cnf的[mysql ...
- 系统吞吐量、TPS(QPS)、用户并发量、性能測试概念和公式
PS:以下是性能測试的主要概念和计算公式,记录下: 一.系统吞度量要素: 一个系统的吞度量(承压能力)与request对CPU的消耗.外部接口.IO等等紧密关联.单个reqeust 对CPU消耗越高, ...
随机推荐
- 【马克-to-win】—— 学习笔记
声明 以下学习内容转载自:http://www.mark-to-win.com/ 社区,由马克java社区创始人---"马克-to-win"一人全部独立写作,创作和制作. 非常感谢 ...
- delete zone and cfgsave on brocade by CMD
brocade:user> cfgshowDefined configuration: cfg: cfg001 AMS_ESX_HBA1; AMS_ESX_HBA2; HUS_ESX_HBA1; ...
- yum 和 apt-get
yum 和apt-get 一般来说著名的linux系统基本上分两大类: 1.RedHat系列:Redhat.Centos.Fedora等 2.Debian系列:Debian.Ubuntu等 RedHa ...
- Android键盘面板冲突 布局闪动处理方案
转:来自Android键盘面板冲突 布局闪动处理方案 起源,之前在微信工作的时候,为了给用户带来更好的基础体验,做了很多尝试,踩了很多输入法的坑,特别是动态调整键盘高度,二级页面是透明背景,魅族早期的 ...
- SQLSever 触发器
/*DML触发器分为: 1. after触发器(之后触发) a. insert触发器 b. update触发器 c. delete触发器*/ /*UPDATE 触发器创建触发的语法*/ CREATE ...
- Oracle 数据库维护相关
版本升级 在维护数据库升级的过程中,会产生n个脚本.谈谈我所处的项目背景,项目数据库最早版本假定为1,最后在多次维护后,版本号,可能变更为16.那么针对项目上不同的数据库版本,如何来进行升级呢? 我使 ...
- 基于CSOCKET的Client简单实例(转)
原文转自 http://blog.csdn.net/badagougou/article/details/78410382 第一步:创建一个基类为CSOCKET类的新类,Cclient,并在主对话框类 ...
- python3使用urllib获取set-cookies
#!/usr/bin/env python # encoding: utf-8 import urllib.request from collections import defaultdict re ...
- Spring与Struts2的整合
一.复制jar文件. 把struts2-spring-plugin-..*.jar和spring.jar复制到Web工程的WEB-INF/lib目录下,并且还需要复制commons-logging.j ...
- Appium+python自动化2-环境搭建(下)【转载】
前言 上一篇android测试开发环境已经准备好, 接下来就是appium的环境安装了.环境安装过程中切勿浮躁,按照步骤一个个来. 环境装好后,可以用真机连电脑,也可以用android-sdk里 ...