基于sysbench-0.5的MySQL自动化压测及分析出图

  本文是作者工作中需要对atlas(360开源的mysql中间件,可实现读写分离、分表、多从库负载均衡)以及后期对proxysql进行测试时所设计和采用的一套脚本。由于对中间件测试,要测试对比的维度较多,所以尽量将涉及到的因素都纳入脚本中以实现自动化的压测和分析过程。总体思路如下:

准备测试数据(这步在脚本之外)----运行脚本测试(线程数脚本内指定,每个条件测试三次)----脚本对每次测试输出过滤并格式化后写入数据库----脚本加“分析参数”将结果直观展示出来----亦或直接加“画图参数”画出图形(画图基于gnuplot)

下面为脚本使用说明截图

帮助信息:

wKioL1gA9_vBi8sVAAUi_ih9Afw578.png

进行测试:

wKiom1bYAeOA1GxwAAKll2FSJjs327.png

查看测试结果:

wKioL1gA_FWQYlLhAAxXSIWw6KQ525.png

对结果进行画图:

wKiom1gA-i_DtcQhAAB_1VY5wcQ438.png

效果图展示:

wKioL1gA-33y64FqAAAfUIYcEh0352.png

  接下来说说sysbench-0.5,对于数据库的测试较0.4版本有较大不同,之前有内建的--test=oltp方法,现在改成了外部的lua脚本形式,这样更灵活,也方便用户构建自己的测试模型。

  这些相关的lua脚本位于”/usr/share/doc/sysbench/tests/db/“ 目录,其内脚本如下图所示

wKiom1bW8M-yS4DpAAAoCWAEXJI478.png

  我们需要了解我们最有可能用到的三个脚本:common.lua(公共脚本)、oltp.lua(oltp测试主脚本)和parallel_prepare.lua(并行准备数据)。common.lua中定义了一些选项的默认值(故而,这些选项的值既可以通过命令行指定也可直接修改该脚本里对应值来更改).

  简单说一下oltp.lua脚本的逻辑:

默认通过显式的使用begin和commit语句将如下模式的sql组合在一起形成一个事务(只读测试的话则没有写请求)

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上分别执行

1
2
3
4
5
#以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脚本以串行方式准备数据

1
2
3
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

开始测试

1
sh  /root/shells/mysql_oltp_test .sh  test  read -only 192.168.1.44 3306 user  passwd

下面为脚本内容,注释挺详细,我想就无需多说了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/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  "\n---------------\n创建测测试结果表test.sysbench_test\n---------------"
     mysql -u$m_user -p$m_passwd -P$m_port -h$m_host <<EOF
         CREATE 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;
EOF
     if  [ $? - ne  0 ]; then  exit  -1; fi
 
     #开始测试,每种条件测3次,分析时取平均值
     echo  -e  "\n---------------\n场景:$2 模式:$3\n---------------"
     for  in  {1..3}; do
         
         for  sb_threds  in  $threds_num; do     #按照指定的sysbench线程测试
             printf  "  %-10s %s\n"  $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 > $log
             if  [ $? - ne  0 ]; then
                 echo  -e  "\nSysbench error! For more information see $log"
                 exit  -1
             fi
             result=$( 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> $log
                 INSERT 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);
EOF
     
             if  [ $? - ne  0 ]; then
                 echo  -e  "\n----------$sb_threds线程测试,第$i次插入数据库时失败----------"
                 echo  "INSERT VALUES ('$2','$4','$3',$sb_threds,$load,$result)"
                 exit  -2
             fi
             sleep  60     #让库歇一会,也让一分钟负载能够恢复到测试前的值
         done
 
     done
}
 
#结果分析函数
sb_analyse() {
      mysql -u$m_user -p$m_passwd -h$m_host -P$m_port <<EOF 2> $log
         SELECT
         scenario, 
         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_time
         FROM  test .sysbench_test group by scenario,server_name,test_type,sb_threads
EOF
}
 
#画图函数
sb_chart() {
     sb_analyse >  /tmp/mysql_oltp .dat
 
     for  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}' `; do
             let  col_num++
             if  [ $col_name == $chart_type ]; then  break ; fi
         done
         
         if  [ $chart_type ==  "request_per_second"  ]; then     #根据图表特点为不同的chart_type设置不同的key position
             key_pos= "bottom right"
             unit= ""
         elif  [ $chart_type ==  "total_time"  ]; then
             key_pos= "top right"
             unit= "(s)"
         elif  [ $chart_type ==  "95_pct_time"  ]; then
             key_pos= "top left"
             unit= "(ms)"
         fi
 
         plot_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 `; do
                 sb_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,"
             done
             plot_cmd=$( echo  $plot_cmd |  sed  's/,$//g' )
             echo  $plot_cmd | gnuplot
         else
             #只绘制指定的场景
             for  scenario  in  $*; do
                 sb_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,"
             done
             plot_cmd=$( echo  $plot_cmd |  sed  's/,$//g' )
             echo  "$plot_cmd"  | gnuplot
         fi
     done
}
 
#脚本使用说明/参数判断
if  [ $ # -eq 1 ] && [ $1 == "-h" -o $1 == "--help" ];then
     echo  -e  "\nUsage: $0 {test test_scenario test_type mysql_host mysql_port mysql_user mysql_password} | {analyse} | {chart [scenario]...}\n"
     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)\n"
     exit  -1
elif  "$1"  ==  "test"  -a  $ # -eq 7 ];then
     sb_test $1 $2 $3 $4 $5 $6 $7
elif  "$1"  ==  "analyse"  -a $ # -eq 1 ];then
     sb_analyse
elif  "$1"  ==  "chart"  ]; then
     #chart函数可不接参数,也可接任意个'测试场景'作为参数
     arg=($*)
     arg_len=${ #arg[@]}
     sb_chart ${arg[@]:1:$arg_len-1}
else
     echo  -e  "\nUsage: $0 {test test_scenario test_type mysql_host mysql_port mysql_user mysql_password} | {analyse} | {chart [scenario]...}\n"
fi
 
### by ljk 2016/10/14


清空测试数据

1
2
3
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




     本文转自kai404 51CTO博客,原文链接:http://blog.51cto.com/kaifly/1747226 ,如需转载请自行联系原作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值