Welcome 微信登录

首页 / 数据库 / MySQL / 基于sysbenche-0.5的MySQL自动压测及分析出图

本文是作者工作中需要对atlas(360开源的mysql中间件,可实现读写分离、分表、多从库负载均衡)以及后期对proxysql(另一款高效并很有特色的数据库代理软件)进行测试时所设计和采用的一套脚本。由于对中间件测试,要测试对比的维度较多,所以尽量将涉及到的因素都纳入脚本中以实现自动化的压测和分析过程。总体思路如下:准备测试数据(这步在脚本之外人工完成) --> 运行脚本测试(压测线程数在脚本里定义,压测次数默认为3) --> 脚本将测试输出格式化后写入数据库 -->脚本加 analyse 参数 --> 将压测分析结果打印到标准输出脚本加 chart 参数 --> 对压测结果中的所有测试场景进行绘图脚本加 chart "测试场景"... --> 只对给定的"测试场景进行绘图"脚本依赖于sysbench-0.5gnuplot下面来看下脚本使用说明及用例截图帮助信息:

进行测试:

查看分析结果:

对结果进行画图:

效果图展示:
接下来我们来了解一下sysbench-0.5对MySQL进行测试的方法及原理sysbench-0.5,对于数据库的测试较0.4版本有较大不同,之前有内建的–test=oltp方法,现在改成了外部的lua脚本形式,这样更灵活,也方便用户构建自己的测试模型。这些相关的lua脚本位于”/usr/share/doc/sysbench/tests/db/“ 目录,其内脚本如下图所示
我们需要了解我们最有可能用到的三个脚本:common.lua(公共脚本)、oltp.lua(oltp测试主脚本)和parallel_prepare.lua(并行准备数据)。common.lua中定义了一些选项的默认值(故而,这些选项的值既可以通过命令行指定也可直接修改该脚本里对应值来更改).简单说一下oltp.lua脚本的逻辑:默认通过显式的使用begin和commit语句将如下模式的sql组合在一起形成一个事务(只读测试的话则没有写请求)关于oltp "读写"和"只读"测试的语句及其出现比例如下10条SELECT c FROM sbtest6 WHERE id=5047;1条SELECT c FROM sbtest16 WHERE id BETWEEN 5050 AND 5050+99;1条SELECT SUM(K) FROM sbtest7 WHERE id BETWEEN 5039 AND 5039+99;1条SELECT c FROM sbtest7 WHERE id BETWEEN 4987 AND 4987+99 ORDER BY c;1条SELECT DISTINCT c FROM sbtest7 WHERE id BETWEEN 13 AND 13+99 ORDER BY c;1条UPDATE sbtest1 SET k=k+1 WHERE id=1234;1条UPDATE sbtest2 SET c="78864443858-59732318638" where id=2345;1条DELETE FROM sbtest11 WHERE id=4958;1条INSERT 语句;然后将此事务循环执行10000次。也就是只读测试共14w请求,混合测试18w请求。若觉得数量不够,可以修改common.lua中的设置function set_vars() oltp_table_size = oltp_table_size or 10000 oltp_range_size = oltp_range_size or 100 oltp_tables_count = oltp_tables_count or 1 oltp_point_selects = oltp_point_selects or 20 (原来10) oltp_simple_ranges = oltp_simple_ranges or 2 (原来1) oltp_sum_ranges = oltp_sum_ranges or 2 (原来1) oltp_order_ranges = oltp_order_ranges or 2 (原来1) oltp_distinct_ranges = oltp_distinct_ranges or 2 (原来1) oltp_index_updates = oltp_index_updates or 1 oltp_non_index_updates = oltp_non_index_updates or 1这样总的测试请求量会变成28w以上是通过lua脚本里总结出来的,各位也可查看下这些lua脚本,来更好的理解测试的逻辑过程。一般来说,对MySQL做压测会基于两种需求:
  • 一种是通过压测来大致评估MySQL实例的最大能力,这种适合给定时长来测;
  • 另一种就是来对比某些改动前后的性能变化(如版本升级、参数调整等),这种适合给定请求数来测。
以作者的小经验来看,后者要更多一些,所以我的测试模式也是趋向于后者的。前提功课做好了,接下来一起看一下本例的测试过程准备数据:在被测的mysql上执行如下命令(以8线程并发创建16张50w数据的表)sysbench --test=/usr/share/doc/sysbench/tests/db/parallel_prepare.lua --mysql-table-engine=innodb --oltp-table-size=500000 --mysql-user=user --mysql-password="passwd" --mysql-port=3306 --mysql-host=192.168.1.33 --oltp-tables-count=16 --num-threads=8 run还有另外一种方式,用oltp.lua脚本以串行方式准备数据sysbench --test=/usr/share/doc/sysbench/tests/db/oltp.lua --mysql-table-engine=innodb --oltp-table-size=500000 --mysql-user=user --mysql-password="passwd" --mysql-port=3306 --mysql-host=192.168.1.33 --oltp-tables-count=16 prepare开始测试:sh mysql_oltp_test.sh test atlas-24-threads read-only 192.168.1.44 3306 user passwd清空测试数据:sysbench --test=/usr/share/doc/sysbench/tests/db/parallel_prepare.lua --mysql-user=user --mysql-password="passwd" --mysql-port=3306 --mysql-host=192.168.1.22 --oltp-tables-count=16 --num-threads=8 cleanup上面是整个思路过程,下面就来看看代码吧#!/bin/sh#通过sysbench测试mysql相关性能,并将关键数据存储于‘test.sysbenc_test’表中#定义记录测试结果的mysql连接相关参数,本例我在测试机上记录测试结果m_user="test"m_passwd="test"m_port="3307"m_host="127.0.0.1"#定义错误日志文件log=/tmp/mysql_oltp.log#定义测试线程threds_num="8 24 48 64 96 128 160 196 256"#测试函数sb_test() {#定义测试方式相关变量tables_count=16#测试表的数量if [ "$3" == "read-only" ];then read_only="on";else read_only="off";fi#根据脚本参数确定是否read-only#创建记录测试信息的表echo -e " --------------- 创建测测试结果表test.sysbench_test ---------------"mysql -u$m_user -p$m_passwd -P$m_port -h$m_host <<EOFCREATE TABLE IF NOT EXISTS test.sysbench_test (scenario varchar(30) NOT NULL DEFAULT "" COMMENT "测试场景",server_name varchar(15) NOT NULL COMMENT "被测DB name",test_type varchar(15) NOT NULL COMMENT "read-only,read-write,insert等",sb_threads int(11) NOT NULL DEFAULT "0" COMMENT "sysbench 测试线程",server_load decimal(12,2) NOT NULL DEFAULT "0.00" COMMENT "以当前线程测试完后立刻记录一分钟负载值",request_total int(11) NOT NULL DEFAULT "0",request_read int(11) NOT NULL DEFAULT "0",request_write int(11) NOT NULL DEFAULT "0",request_per_second decimal(12,2) NOT NULL DEFAULT "0.00",total_time decimal(12,2) NOT NULL DEFAULT "0.00" COMMENT "单位秒",95_pct_time decimal(12,2) NOT NULL DEFAULT "0.00" COMMENT "单位毫秒") ENGINE=InnoDB DEFAULT CHARSET=utf8;EOFif [ $? -ne 0 ];then exit -1;fi#开始测试,每种条件测3次,分析时取平均值echo -e " --------------- 场景:$2 模式:$3 ---------------"for i in {1..3};dofor sb_threds in $threds_num;do#按照指定的sysbench线程测试printf "%-10s %s " $sb_threds线程 第$i次运行...#result 作为每次最小测试单元的结果,根据sysbench测试结果各参数的出现顺序,以request_read、request_write、request_total、request_per_second、total_time、95_pct_time为顺序插入表中。下条命令中,egerp之后的操作是为了对sysbench的输出做筛选和格式化,以便插入数据库sysbench --test=/usr/share/doc/sysbench/tests/db/oltp.lua --mysql-user=$6 --mysql-password=$7 --mysql-port=$5 --mysql-host=$4 --num-threads=$sb_threds run --oltp-skip-trx=on --oltp-read-only=$read_only > $logif [ $? -ne 0 ];thenecho -e " Sysbench error! For more information see $log"exit -1firesult=$(cat $log | egrep"read:|write:|read/write.*:|total:|total time:|approx..*95.*:" |sed -r -e "s/[0-9]+ (//g" -e "s/ per sec.)//g" -e "s/m?s$//g" | awk"{printf("%s ",$NF)}"|sed "s/ /,/g" | sed "s/,$//g")#测试完成后立刻记录系统一分钟负载值,可近似认为测试过程中proxy的负载抽样load=$(ssh -p22 $4 "uptime|awk -F: "{print $NF}"|awk -F, "{print $1}"" 2>/dev/null)#本次测试结果写入数据库mysql -u$m_user -p$m_passwd -P$m_port -h$m_host <<EOF 2> $logINSERT INTO test.sysbench_test (scenario,server_name,test_type,sb_threads,server_load,request_read,request_write,request_total,request_per_second,total_time,95_pct_time) VALUES ("$2","$4","$3","$sb_threds","$load",$result);EOFif [ $? -ne 0 ];thenecho -e " ----------$sb_threds线程测试,第$i次插入数据库时失败----------"echo "INSERT VALUES ("$2","$4","$3",$sb_threds,$load,$result)"exit -2fisleep 60#让库歇一会,也让一分钟负载能够恢复到测试前的值donedone}#结果分析函数sb_analyse() { mysql -u$m_user -p$m_passwd -h$m_host -P$m_port <<EOF 2> $logSELECTscenario, server_name,test_type,sb_threads,convert(avg(server_load),decimal(12,2)) as server_load,convert(avg(request_total),decimal(12,0)) as request_total,convert(avg(request_read),decimal(12,0)) as request_read,convert(avg(request_write),decimal(12,0)) as request_write,convert(avg(request_per_second),decimal(12,2)) as request_per_second,convert(avg(total_time),decimal(12,2)) as total_time,convert(avg(95_pct_time),decimal(12,2)) as 95_pct_timeFROM test.sysbench_test group by scenario,server_name,test_type,sb_threadsEOF}#画图函数sb_chart() {sb_analyse > /tmp/mysql_oltp.datfor chart_type in "request_per_second" "total_time" "95_pct_time";do#这里写死了关注的三个指标,也就是会画三张图col_num=0#该行及下面这个for循环用于取得三个指标在数据中的列号for col_name in `cat /tmp/aualyse.txt |awk "NR<2 {print}"`;dolet col_num++if [ $col_name == $chart_type ];then break;fidoneif [ $chart_type == "request_per_second" ];then#根据图表特点为不同的chart_type设置不同的key positionkey_pos="bottom right"unit=""elif [ $chart_type == "total_time" ];thenkey_pos="top right"unit="(s)"elif [ $chart_type == "95_pct_time" ];thenkey_pos="top left"unit="(ms)"fiplot_cmd="set term png size 800,600;set output "/tmp/$chart_type.png";set title "$chart_type $unit";set grid;set key $key_pos;plot "if [ $# -eq 0 ];then#对分析结果中所有场景进行画图for scenario in `mysql -u$m_user -p$m_passwd -h$m_host -P$m_port -s -e "select distinct(scenario) from test.sysbench_test" 2>/dev/null`;dosb_analyse | awk -v scenario=$scenario "$1 == scenario {print}" > /tmp/"$scenario.dat"plot_cmd=${plot_cmd}""/tmp/"$scenario.dat"" using $col_num:xtic(4) title "$scenario" with linespoints lw 2,"doneplot_cmd=$(echo $plot_cmd | sed "s/,$//g")echo $plot_cmd | gnuplotelse#只绘制指定的场景for scenario in $*;dosb_analyse | awk -v scenario=$scenario "$1 == scenario {print}" > /tmp/"$scenario.dat"plot_cmd=${plot_cmd}""/tmp/"$scenario.dat"" using $col_num:xtic(4) title "$scenario" with linespoints lw 2,"doneplot_cmd=$(echo $plot_cmd | sed "s/,$//g")echo "$plot_cmd" | gnuplotfidone}#脚本使用说明/参数判断if [ $# -eq 1 ] && [ $1 == "-h" -o $1 == "--help" ];thenecho -e " Usage: $0 {test test_scenario test_type mysql_host mysql_port mysql_user mysql_password} | {analyse} | {chart [scenario]...} "echo ----------echo -e "测试: 请在脚本后跟上 test test_scenario test_type mysql_host mysql_port mysql_user mysql_password 7个参数 !"echo -e "test_type: read-only 或 read-write, 表示测试模式"echo -e "其余4参数表示待测试MySQL连接相关信息,密码若包含特殊字符,将其置于单引号内"echo -e "----------"echo -e "分析: 请在脚本后跟上 analyse"echo -e "----------"echo -e "画图: 请在脚本后面跟上"echo -e "会在/tmp/下生成request_per_second.png total_time.png 95_pct_time.png 三张图"echo -e "chart (对分析结果中的所有测试场景画图)"echo -e "chart scenario ... (对指定的测试场景画图,场景名可查看analyse) "exit -1elif [ "$1" == "test" -a$# -eq 7 ];thensb_test $1 $2 $3 $4 $5 $6 $7elif [ "$1" == "analyse" -a $# -eq 1 ];thensb_analyseelif [ "$1" == "chart" ];then#chart函数可不接参数,也可接任意个"测试场景"作为参数arg=($*)arg_len=${#arg[@]}sb_chart ${arg[@]:1:$arg_len-1}elseecho -e " Usage: $0 {test test_scenario test_type mysql_host mysql_port mysql_user mysql_password} | {analyse} | {chart [scenario]...} "fi### by ljk 2016/10/14本文永久更新链接地址