🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 1.理论知识: ### 1.1 定义 > 1. 基准测试是一种测量和评估软件性能指标的活动,用于建立某个时刻的性能基准,以便当系统发生软硬件变化时重新进行基准测试以评估变化对性能的影响 > 2. 基准测试是针对系统设置的一种压力测试,但是和压力测试还是有区别的 > 基准测试:直接、简单,易于比较,用于评估服务器的处理能力 > 基准测试:可能不关心业务逻辑,所使用的查询和业务的真实性可以和业务环境没有关系 > 压力测试:对真实的业务数据进行测试,获得真实系统所能承受的压力 > 压力测试:需要针对不同的应用场景,所使用的数据和查询也是真实用到的 ### 1.2 测试步骤 选择sysbench为测试工具 1.安装sysbench 1)下载安装包 `wget https://github.com/akopytov/sysbench/archive/0.5.zip` 2)安装依赖工具 ~~~ apt -y install make automake libtool pkg-config libaio-dev vim-common # For MySQL support apt -y install libmysqlclient-dev # For PostgreSQL support apt -y install libpq-dev ~~~ 3)编译安装 ~~~ unzip 0.5.zip cd sysbench-0.5 ./autogen.sh # Add --with-pgsql to build with PostgreSQL support ./configure make make install ~~~ 2. 准备测试 1) 创建测试库 ~~~ create database sysbenchtest; ~~~ 2) 创建测试用户 ~~~ grant all privileges on sysbenchtest.* to sysbench@'localhost' identified by '4rfv$RFV'; flush privileges; ~~~ 3) 准备测试数据:构建5张表,每张表10万条数据 ~~~ sysbench --test=./sysbench/tests/db/oltp.lua --mysql-host=127.0.0.1 --mysql-db=sysbenchtest --mysql-user=sysbench \ --mysql-password='4rfv$RFV' --oltp-test-mode=complex --oltp-tables-count=5 --oltp-table-size=100000 --threads=10 --time=120 \ --rand-init=on --report-interval=10 --mysql-table-engine=innodb prepare ~~~ * 参数说明 ~~~ mysql-db=dbtest1a:测试使用的目标数据库,这个库名要事先创建 --oltp-tables-count=10:产生表的数量 --oltp-table-size=500000:每个表产生的记录行数 --oltp-dist-type=uniform:指定随机取样类型,可选值有 uniform(均匀分布), Gaussian(高斯分布), special(空间分布)。默认是special --oltp-read-only=off:表示不止产生只读SQL,也就是使用oltp.lua时会采用读写混合模式。默认 off,如果设置为on,则不会产生update,delete,insert的sql。 --oltp-test-mode=nontrx:执行模式,这里是非事务式的。可选值有simple,complex,nontrx。默认是complex simple:简单查询,SELECT c FROM sbtest WHERE id=N complex (advanced transactional):事务模式在开始和结束事务之前加上begin和commit, 一个事务里可以有多个语句,如点查询、范围查询、排序查询、更新、删除、插入等,并且为了不破坏测试表的数据,该模式下一条记录删除后会在同一个事务里添加一条相同的记录。 nontrx (non-transactional):与simple相似,但是可以进行update/insert等操作,所以如果做连续的对比压测,你可能需要重新cleanup,prepare。 --oltp-skip-trx=[on|off]:省略begin/commit语句。默认是off --rand-init=on:是否随机初始化数据,如果不随机化那么初始好的数据每行内容除了主键不同外其他完全相同 --num-threads=12: 并发线程数,可以理解为模拟的客户端并发连接数 --report-interval=10:表示每10s输出一次测试进度报告 --max-requests=0:压力测试产生请求的总数,如果以下面的max-time来记,这个值设为0 --max-time=120:压力测试的持续时间,这里是2分钟。 注意,针对不同的选项取值就会有不同的子选项。比如oltp-dist-type=special,就有比如oltp-dist-pct=1、oltp-dist-res=50两个子选项,代表有50%的查询落在1%的行(即热点数据)上,另外50%均匀的(sample uniformly)落在另外99%的记录行上。 再比如oltp-test-mode=nontrx时, 就可以有oltp-nontrx-mode,可选值有select(默认), update_key, update_nokey, insert, delete,代表非事务式模式下使用的测试sql类型。 以上代表的是一个只读的例子,可以把num-threads依次递增(16,36,72,128,256,512),或者调整my.cnf参数,比较效果。另外需要注意的是,大部分mysql中间件对事务的处理,默认都是把sql发到主库执行,所以只读测试需要加上oltp-skip-trx=on来跳过测试中的显式事务。 ps1: 只读测试也可以使用share/tests/db/select.lua进行,但只是简单的point select。 ps2: 我在用sysbench压的时候,在mysql后端会话里有时看到大量的query cache lock,如果使用的是uniform取样,最好把查询缓存关掉。当然如果是做两组性能对比压测,因为都受这个因素影响,关心也不大。 ~~~ 3. 运行基准测试 1) 运行测试:混合操作测试 ~~~ sysbench --test=./sysbench/tests/db/oltp.lua --mysql-table-engine=innodb --mysql-host=127.0.0.1 --mysql-db=sysbenchtest --mysql-user=sysbench \ --mysql-password='4rfv$RFV' --num-threads=8 --oltp-table-size=100000 --oltp_tables_count=5 --oltp-read-only=off --report-interval=10 \ --rand-type=uniform --max-time=300 --max-requests=0 --percentile=99 run >> ./log/sysbench.log ~~~ 2) 如果多次测试清理记录 ~~~ sysbench --test=./sysbench/tests/db/oltp.lua --mysql-table-engine=innodb --mysql-host=127.0.0.1 --mysql-db=sysbenchtest --num-threads=8 \ --oltp-table-size=100000 --oltp_tables_count=5 --oltp-read-only=off --report-interval=10 --rand-type=uniform --max-time=600 --max-requests=0 \ --mysql-user=test --mysql-password='4rfv$RFV' cleanup ~~~ * 参数说明 ~~~ --num-threads=8 表示发起 8个并发连接 --oltp-read-only=off 表示不要进行只读测试,也就是会采用读写混合模式测试 --report-interval=10 表示每10秒输出一次测试进度报告 --rand-type=uniform 表示随机类型为固定模式,其他几个可选随机模式:uniform(固定),gaussian(高斯),special(特定的),pareto(帕累托) --max-time=120 表示最大执行时长为 120秒,实际环境中建议30分钟 --max-requests=0 表示总请求数为 0,因为上面已经定义了总执行时长,所以总请求数可以设定为 0;也可以只设定总请求数,不设定最大执行时长 --percentile=99 表示设定采样比例,默认是 95%,即丢弃1%的长请求,在剩余的99%里取最大值 ~~~ 4. 分析调优效果:通过脚本控制测试,测试多次取平均值,运行脚本需要清空上一次的日志。 1) 参数分析 ~~~ queries performed: read: 938224 -- 读总数 write: 268064 -- 写总数 other: 134032 -- 其他操作总数(SELECT、INSERT、UPDATE、DELETE之外的操作,例如COMMIT等) total: 1340320 -- 全部总数 transactions: 67016 (1116.83 per sec.) -- 总事务数(每秒事务数) deadlocks: 0 (0.00 per sec.) -- 发生死锁总数 read/write requests: 1206288 (20103.01 per sec.) -- 读写总数(每秒读写次数) other operations: 134032 (2233.67 per sec.) -- 其他操作总数(每秒其他操作次数) General statistics: -- 一些统计结果 total time: 60.0053s -- 总耗时 total number of events: 67016 -- 共发生多少事务数 total time taken by event execution: 479.8171s -- 所有事务耗时相加(不考虑并行因素) response time: -- 响应时长统计,主要参考指标 min: 4.27ms -- 最小耗时 avg: 7.16ms -- 平均耗时 max: 13.80ms -- 最长耗时 approx. 99 percentile: 9.88ms -- 超过99%平均耗时 Threads fairness: events (avg/stddev): 8377.0000/44.33 execution time (avg/stddev): 59.9771/0.00 ~~~ 5. 测试分析脚本 analysis_mysql.py,控制多次测试,然后取平均值 ~~~ #!/usr/bin/env python import re import os import configparser if (not os.path.exists("sysbench.log")): os.mknod("sysbench.log") else : os.remove("sysbench.log") os.mknod("sysbench.log") if (not os.path.exists("analysis.log")): os.mknod("analysis.log") logFile = open('sysbench.log','r') analysisFile = open('analysis.log','a+') configFile = "sysbench.conf" config = configparser.ConfigParser() config.read(configFile) host = config.get('config','dbhost') database = config.get('config','database') user = config.get('config','user') password = config.get('config','password') time = config.get('config','times') print(host) print(database) print(user) print(password) tableCount = config.get('config','--oltp-tables-count') tableSize = config.get('config','--oltp-table-size') threadNum = config.get('config','--num-threads') maxTime = config.get('config','--max-time') oltp = config.get('config','oltp') # 结果分析 def closeFile(file): file.close() def analysis(): print("开始分析...") transacion = [] readeWrite = [] mintime = [] avgtime = [] maxtime = [] approxtime = [] # 读取文件数据 for line in logFile: transacion_persec = re.search(r'transactions:.*\((\d+\.*\d*).*\).*',line) readeWrite_persec = re.search(r'read/write requests:.*\((\d+\.*\d*).*\).*',line) mintime_response = re.search(r'min:\s*(\d+\.?\d*).*',line) avgtime_response = re.search(r'avg:\s*(\d+\.?\d*).*',line) maxtime_response = re.search(r'max:\s*(\d+\.?\d*).*',line) approxtime_response = re.search(r'approx.*?(\d+\.{1}\d*).*',line) if transacion_persec: transacion.append(transacion_persec.group(1)) elif readeWrite_persec: readeWrite.append(readeWrite_persec.group(1)) elif mintime_response: mintime.append(mintime_response.group(1)) elif avgtime_response: avgtime.append(avgtime_response.group(1)) elif maxtime_response: maxtime.append(maxtime_response.group(1)) elif approxtime_response: approxtime.append(approxtime_response.group(1)) sum = 0.0 # 统计事务 for data in transacion: sum += float(data) avgtransaction = ('%.2f'%(sum / len(transacion))) analysisFile.write("transacion每秒:" + str(avgtransaction) + "\n") sum = 0.0 # 统计读写 for data in readeWrite: sum += float(data) read_write = ('%.2f'%(sum / len(readeWrite))) analysisFile.write("read/write每秒:" + str(read_write) + "\n") sum = 0.0 # 统计最小时间 for data in mintime: sum += float(data) if len(mintime) !=0: min_time = ('%.2f'%(sum / len(mintime))) analysisFile.write("min time:" + str(min_time) + "\n") sum = 0.0 # 统计最大时间 for data in maxtime: sum += float(data) max_time = ('%.2f'%(sum / len(maxtime))) analysisFile.write("max time:" + str(max_time) + "\n") sum = 0.0 # 统计平均时间 for data in avgtime: sum += float(data) avg_time = ('%.2f'%(sum / len(avgtime))) analysisFile.write("avg time:" + str(avg_time) + "\n") analysisFile.write("="*20 + "\n") print("分析完成...") closeFile(analysisFile) # 数据准备 def prepare(): print("准备数据...表数:%s,记录数:%s,测试线程数:%s,持续时间:%s"%(tableCount,tableSize,threadNum,maxTime)) os.system("sysbench --test=%s --mysql-host=%s --mysql-db=%s --mysql-user=%s --mysql-password='%s' --oltp-test-mode=complex --oltp-tables-count=%s --oltp-table-size=%s --threads=10 --ti me=%s --rand-init=on --report-interval=10 --mysql-table-engine=innodb prepare"%(oltp,host,database,user,password,tableCount,tableSize,maxTime)) # 测试函数 def experiment(): print("测试数据...") os.system("sysbench --test=%s --mysql-table-engine=innodb --mysql-host=%s --mysql-db=%s --mysql-user=%s --mysql-password='%s' --num-threads=%s --oltp-test-mode=complex --oltp-table-siz e=%s --oltp_tables_count=%s --oltp-read-only=off --report-interval=10 --rand-type=uniform --max-time=%s --max-requests=0 --percentile=99 run >> ./sysbench.log"%(oltp,host,database,user,pass word,threadNum,tableSize,tableCount,maxTime)) # # # 清空数据 def clean(): print("清空测试数据库...") os.system("sysbench --test=%s --mysql-table-engine=innodb --mysql-host=%s --mysql-db=%s --num-threads=8 --oltp-table-size=%s --oltp_tables_count=%s --oltp-read-only=off --report-interv al=10 --rand-type=uniform --max-time=600 --max-requests=0 --mysql-user=%s --mysql-password='%s' cleanup"%(oltp,host,database,tableSize,tableCount,user,password)) if __name__ == '__main__': for i in range(0,int(time)): prepare() experiment() clean() analysis() ~~~ * 配置文件 ~~~ [config] dbhost = 127.0.0.1 database = sysbenchtest user = sysbench password = 4rfv$RFV # oltp测试脚本路径 oltp = /root/sysben/sysbench-0.5/sysbench/tests/db/oltp.lua # 测试次数 times = 5 --oltp-tables-count=5 --oltp-table-size=1000000 --num-threads=30 --max-time=600 ~~~ ## 2. 测试用例 > 测试用例: > 是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足特定需求 > 通俗的讲,测试用例是指导我们进行具体测试的描述。 ![](https://box.kancloud.cn/4503334f9ca1bcfec42307285f2306da_1556x774.png) ![](https://box.kancloud.cn/52ffb0c062b1358b36904a4b326e34cc_1560x814.png)