本文是我在学习和验证ProxySQL的过程当中,从初识(对其机制猜测或凭几回命令的结果臆断其原理),到逐渐深刻(模拟各类场景测试、抓包分析、与做者交流)过程当中的思路
和方法
和结论
的记录。
笔者初识proxysql的时候是1.2.1版本,如今几经演进,已经到了1.4.1版本,本文也几经修改,力求跟得上软件的最新进度。php
ProxySQL项目网址html
mysql_query_rules
表中依据digest
,match_pattern
,client_addr
等维度控制哪类语句能够缓存rpm包下载地址前端
https://github.com/sysown/proxysql/releases 推荐rpm形式安装node
Installing from sourcemysql
Make sure you have installed the equivalent for each of these packages for your operating system:laravel
automake bzip2 cmake make gcc #>4.4版本 gcc-c++ git openssl openssl-devel patch
git clone https://github.com/sysown/proxysql.gitc++
Go to the directory where you cloned the repo (or unpacked the tarball) and run:git
make sudo make install
Compilation time should be around a couple of minutes for the first time around. The configuration file will be found at /etc/proxysql.cnf afterwards.github
在make这一步遇到了错误:web
g++ -fPIC -c -o obj/ProxySQL_GloVars.oo ProxySQL_GloVars.cpp -std=c++11 -I../include -I../deps/jemalloc/jemalloc/include/jemalloc -I../deps/mariadb-client-library/mariadb_client/include -I../deps/libconfig/libconfig-1.4.9/lib -I../deps/re2/re2 -I../deps/sqlite3/sqlite3 -O2 -ggdb -Wall cc1plus: 错误:没法识别的命令行选项“-std=c++11” make[1]: *** [obj/ProxySQL_GloVars.oo] 错误 1 make[1]: Leaving directory `/usr/local/src/proxysql-master/lib' make: *** [build_lib] 错误 2
网查是因为gcc版本低致使,centos 6的yum源(以及epel源)都只能获取到4.4.7版本
包 gcc-4.4.7-17.el6.x86_64 已安装而且是最新版本 包 gcc-c++-4.4.7-17.el6.x86_64 已安装而且是最新版本 而centos7上为4.8版本
换到centos7上,将上述软件安装/更新以后,make步骤完成,可是make install步骤又出了问题:
install -m 0755 src/proxysql /usr/local/bin install -m 0600 etc/proxysql.cnf /etc install -m 0755 etc/init.d/proxysql /etc/init.d if [ ! -d /var/lib/proxysql ]; then mkdir /var/lib/proxysql ; fi update-rc.d proxysql defaults make: update-rc.d:命令未找到 make: *** [install] 错误 127
update-rc.d是ubuntu的自启动脚本管理软件,未成功安装不影响使用。
安装完成后,自动在/etc/init.d/proxysql增长服务管理脚本(须要把/usr/local/bin/加入\$PATH或者软链至 \$PATH目录下,脚本中直接用到proxysql命令)
配置文件/etc/proxysql.cnf和配置数据库文件/var/lib/proxysql/proxysql.db,若是存在 “proxysql.db”文件,则启动过程不解析proxysql.cnf文件;配置文件只在第一次启动的时候读取
官方推荐用admin interface方式
登录admin interface:
mysql -uadmin -padmin -P6032 -h127.0.0.1 登录成功后,可经过对main库(默认登录后即在此库)的global_variables表中的 admin-admin_credentials admin-mysql_ifaces 两个变量进行更改来修改登陆认证
注意:admin interface对配置的存储是基于SQLite的,SQLite支持标准的SQL语法,与mysql也基本兼容。可是没法用use语句切换数据库,做者对use语句作了兼容(不报错),可是却没有实际效果。
两种方式,区别在于: 1. 一种是在往mysql_servers表中添加server时就为其划分好hostgroup_id(例如0表示写组,1表示读组) 2. 另外一种往mysql_servers表中添加server时不区分hostgroup_id(例如所有设为0),而后经过mysql_replication_hostgroups表中的值,根据proxysql检测到的各server的read_only变量值来自动为后端server设置hostgroup_id 这里强烈推荐用第一种方式: 由于第一种是彻底由咱们控制的;而第二种假如咱们误将读server的read_only属性设置为0,则proxysql会将其从新分配到写组,这绝对是不指望的。
MySQL [(none)]> select * from mysql_servers; +--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+ | hostgroup_id | hostname | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment | +--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+ | 0 | 192.168.1.21 | 3307 | ONLINE | 1 | 0 | 1000 | 0 | 0 | 0 | | | 1 | 192.168.1.10 | 3306 | ONLINE | 1 | 0 | 1000 | 0 | 0 | 0 | | | 1 | 192.168.1.4 | 3306 | ONLINE | 1 | 0 | 1000 | 0 | 0 | 0 | | +--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+ #proxysql server的IP为:192.168.1.34
配置好一主(db1,hostgroup0)两从(db2和db3,hostgroup1) ,而且在'mysql_query_rules'表中增长一条路由规则
insert into mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply) values (10,1,'^SELECT',1,1)
意为全部以select开头的语句路由到hostgroup1,其他语句路由到hostgroup0
在两台server的mysql-client分别链接至proxysql的6033端口,执行'select @@hostname'观察其分配到的后端server
以mysql -e的形式执行该命令则可以看到请求在两台读server之间变换
[root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N. db1 [root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N db5 [root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N db5 [root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N db1 [root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N db1 [root@db1 ~]# mysql -udm -p'dm' -h192.168.1.34 -P6033 -e "select @@hostname" -s -N db5
再实验下mysql -e跟多条语句会如何 [root@db1 ~]# mysql -udm -p'dm' -P6033 -h192.168.1.34 -e "select @@hostname;select @@hostname;select @@hostname" -s -N dm-web5 dm-web5 dm-web5
由以上结果可能会猜测并可印证:在一个client的一个连接周期内,全部query路由到同一台后端。
可是:这只是个假象(是由于正好用到了select @ 语句。按做者所说: sends a query that implicitly disables multiplexing. For example, if you run "SELECT @a" , ProxySQL will disable multiplexing for that client and will always use the same backend connection.),proxysql的负载方式目前仅为加权轮询
一种(通过做者确认的),并没有其余机制。
对上述架构用sysbench作只读测试,过程当中关闭(service mysqld stop)某一台server (测试均在mysql-monitor_enabled=false的前提下)
测试命令
alias sysbench_test='sysbench --test=/usr/share/doc/sysbench/tests/db/oltp.lua\ --mysql-user=dm --mysql-password='dm' --mysql-port=6033\ --mysql-host=192.168.1.34 --oltp-tables-count=16\ --num-threads=8 run --oltp-skip-trx=on --oltp-read-only=on'
结果以下
[root@db3 ~]# sysbench_test sysbench 0.5: multi-threaded system evaluation benchmark Running the test with following options: Number of threads: 8 Random number generator seed is 0 and will be ignored Threads started! ALERT: mysql_drv_query() for query 'SELECT c FROM sbtest16 WHERE id=4964' failed: 2013 Lost connection to MySQL server during query ALERT: mysql_drv_query() for query 'SELECT c FROM sbtest12 WHERE id=4954' failed: 2013 Lost connection to MySQL server during query ALERT: mysql_drv_query() for query 'SELECT c FROM sbtest7 WHERE id BETWEEN 4645 AND 4645+99' failed: 2013 Lost connection to MySQL server during query
不是说好的自动重连/重执行吗?为毛报错了呢(atlas有一样问题)
可是二者经过上述的sysbench命令所抛出的错误通过屡次比较,有不一样: proxysql报错就一种 failed: 2013 Lost connection to MySQL server during query atlas报错则是两种 failed: 2013 Lost connection to MySQL server during failed: 1317 Query execution was interrupted 是否是说明re-execute有效呢?(no,其实这个思路就不对)
测试方法错了:其实关闭后端mysql服务来测试“reconnect”特性应该说从本质上就是不对的,mysql正常关闭会kill掉其上的全部processlist,咱们能够用mysql-client来作一些验证
mysql> select @@hostname; +------------+ | @@hostname | +------------+ | db1 | +------------+ 1 row in set (0.00 sec) **经过别的tty重启该mysql** mysql> select @@hostname; ERROR 2006 (HY000): MySQL server has gone away No connection. Trying to reconnect... Connection id: 4 Current database: *** NONE *** +------------+ | @@hostname | +------------+ | db1 | +------------+ 1 row in set (0.00 sec)
能够看到mysql-client是具备重连(reconnect)功能的,而后咱们来作一个kill掉mysql-client线程(也就是mysql在关闭时会kill掉全部线程)的操做
mysql> select @@hostname; +------------+ | @@hostname | +------------+ | db1 | +------------+ 1 row in set (0.00 sec) **在mysql服务端查询并kill掉这个连接** mysql> select @@hostname; ERROR 2013 (HY000): Lost connection to MySQL server during query mysql>
咱们看到了以前sysbenche测试过程当中关闭一台从库时的报错。也就是说这个场景下这个报错不是proxysql的‘reconnect’机制的问题,倒更像是‘re-execute’机制的问题。而kill操做应该理解为是mysql“主动”的动做,而非“异常”,因此这就不符合proxysql 的"re-execute"特性了,故而会报错。
应该换一种方式来进行这个测试:
模拟网络层没法通讯的异常
咱们在两台slave上经过iptables阻断peoxysql到本机的3306端口来模拟没法连接和连接中断异常
在sysbench开始以后再重启iptables已经创建的连接会被保留,因此新规则要放到第一条的位置
-A INPUT -s 192.168.1.34 -p tcp -m tcp --dport 3306 -j DROP
首先 :启动sysbench只读测试
而后 :在测试结束前,修改db1的iptables,禁止来自proxysql的请求进入
而后 :观察sysbench的输出和proxysql.log的输出
结果 :sysbench等待很长时间,依然没法完成,同时,proxysql也不会把db1标记为SHUNNED
proxysql.log输出:
2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 133 on 192.168.1.4:3306 2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 136 on 192.168.1.4:3306 2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 135 on 192.168.1.4:3306 2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 137 on 192.168.1.4:3306 2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 138 on 192.168.1.4:3306 2016-09-02 11:37:54 MySQL_Session.cpp:49:kill_query_thread(): [WARNING] KILL QUERY 134 on 192.168.1.4:3306 能够看到在mysql-default_query_timeout=30000,30s以后proxysql确实kill了超时的语句。 30s这个时间能够经过执行‘service iptables restart’的时间和proxysql.log里日志的时间来断定,这个设置30s是有效的
而后,直接对后端node(db1)进行一样的测试,发现结果是同样的,sysbench等待很长时间依然没法完成也无报错。
从这一点彷佛能够断定在这个测试方法下,未达到预期结果的缘由可能不在proxysql,而在sysbench自己。
经过对重启iptables以后的通讯进行抓包(分别对针对proxysql和mysql在这个测试场景下进行抓包),命令以下:
~]# date; service iptables restart; tcpdump -i em2 host 192.168.1.35 and port 3306 and host not 192.168.1.10 -w /tmp/sysbench-proxysql-network-issue.pacp
~]# date; service iptables restart; tcpdump -i em2 host 192.168.1.34 and port 3306 and host not 192.168.1.10 -w /tmp/sysbench-proxysql-network-issue.pacp
发现,sysbench“一直”在重传因为iptables新规则而没法返回的几个请求,因此就成了“无穷尽等待的样子” (atlas 在这个场景下有一样问题)
照理说,proxysql kill掉了一些查询,会返回给sysbench错误,但为何sysbench并未将错误展现出来呢(多是由于re-execute机制)
最后,经过跟做者沟通,发现是因为没开启monitor模块,致使proxysql没法检测到后端出了什么类型的错误,也就没法执行对应各类后端错误的一些操做(以前我是特地关掉了monitor模块)
prepare
语句的支持好多框架用prepare语句来避免SQL注入等安全问题,同时能在MySQL解析查询方面下降一些开销,因此对于prepare语句支持与否也很重要
首先要从MySQL协议层面了解prepare语句,分为两种(参考)
- Prepared Statements in Application Programs
或者称为BINARY protocol
抓包分析一次prepare
,set
,execute
过程,能够观察到,客户端发送的是COM_STMT_PREPARE
- Prepared Statements in SQL Scripts
或者称为TEXT protocol
抓包分析一次prepare
,set
,execute
过程,可观察到客户端发送的是COM_QUERY
关于prepare语句,做者给出的回复和计划以下:
这是我在实验1.2.1版本时跟做者的沟通。如今已经发布1.3.5了,从1.3版本开始,两种协议都支持了。可是在设置字符集方面,对于binary protocol 的prepare set names xxx和普通query(set names 'utf8' collate 'utf8_general_ci')(注意加了校对规则,不加校对规则能够)语句还没法正确处理(例如,php的laravel框架默认就是以第一种形式设置字符集)。
修正:经测试1.3.7版本,以上两prepare语句的小bug都已解决
MySQL supports two type of prepared statements:
using API
using SQL Further details here
SQL support currently not supported either, because PREPARE doesn't disable multiplexing, so it is possible that ProxySQL sends PREPARE in a connection, and EXECUTE into another.SQL support will be fixed in v1.2.3 , see #684 .
API support is planned in ProxySQL 1.3 .
首先来精确理解一下这两个词,依做者回复
They are very related to each other
Connection pool
is a cache of connections that can be reused.Multiplexing
is a technique to reuse these connections.
后期逐渐了解到了proxysql的multiplexing。链接池是一个共享的池子里面有跟后端建立好的一些链接,一个服务端脚本的执行过程当中可能有屡次sql请求:
很显然,对于链接池的使用效率上来讲,理论上多数状况下proxysql的方式会更加高效而且与DB间维护的连接数量会更少
测试场景:
10条`select * from test.test_table`,10条`select @@hostname`; ProxySQL/Atlas IP 192.168.1.35;两个读节点IP分别为192.168.1.37和192.168.1.38; 每次测试完以前重启ProxySQL/Atlas;
分两次测试,第一次测试脚本以下(每条命令一次链接)
!#/bin/sh for i in {1..10};do mysql -uuser -p'passwd' -P6033 -h192.168.1.35 -e "select @@hostname;" #select @xxx,会禁用multiplexing done for i in {1..10};do mysql -uuser -p'passwd' -P6033 -h192.168.1.35 -e "select id from test.test_table;" #普通查询 done
第二次测脚本以下(一次链接,执行全部命令)
for i in {1..10};do query1 += 'select @@hostname;' #select @xxx,会禁用multiplexing query2 += 'select id from test.test_table;' #普通查询 done echo $query1$query2 | mysql -uuser -p'passwd' -P6033 -h192.168.1.35
对ProxySQL进行测试分析
经过tcpdump抓包wireshark分析,经过ProxySQL对20条查询的路由状况及其和后端MySQL之间的创建链接状况(经过ProxySQL上的原端口号)来分析链接池和禁用multiplexing的状况。结果汇总以下
第一次测试(每条命令一次链接)
select @xxx #会禁用multiplexing 1.35源端口 1.35---->1.37 42094 42096 42097 42099 42102 (转发5次) 1.35---->1.38 37971 37974 37976 37977 37979 (转发5次) 普通查询 1.35源端口 1.35---->1.37 42105 (转发3次) 1.35---->1.38 37980 (转发7次)
第二次测试(一次链接,执行全部命令)
select @xxx #会禁用multiplexing 1.35源端口 1.35---->1.37 (转发0次) 1.35---->1.38 37817 (转发10次) 普通查询 1.35源端口 1.35---->1.37 (转发0次) 1.35---->1.38 37817 (转发10次)
对比来看Atlas的分时分析
第一次测试(每条命令一次链接)
select @xxx 1.35源端口 1.35---->1.37 (转发0次) 1.35---->1.38 38405 38407 38409 38411 38413 (转发10次) 38415 38417 38419 38421 38423 普通查询 1.35源端口 1.35---->1.37 (转发0次) 1.35---->1.38 38385 38387 38389 38391 38393 (转发10次) 38395 38397 38399 38401 38403
第二次测脚本以下(一次链接,执行全部命令)
select @xxx 1.35源端口 1.35---->1.37 42435 (转发5次) 1.35---->1.38 38312 (转发5次) 普通查询 1.35源端口 1.35---->1.37 42435 (转发5次) 1.35---->1.38 38312 (转发5次)
由上面测试分析结果能够明显的看到
ProxySQL的负载均衡策略是基于权重的轮询,但并非严格的逐条轮询。并且能够看到在一次链接以内:因为某些语句(select @xx 或者prepare语句)致使ProxySQL自动关闭multiplexing以后,在本次连接以后的全部语句都会被路由到同一台MySQL atlas作的仅仅是轮询转发,他不会去区分查询类型(例若有些查询是要路由到后端惟一的MySQL) 并且,在`第一次测试(每条命令一次链接)`中,atlas将全部的20条请求都路由到了1.38这台MySQL,而且每次都要新建链接(并无用到其链接池)
能够主动
来看一下其相关参数: | mysql-monitor_enabled | true | | mysql-monitor_history | 600000 | | mysql-monitor_connect_interval | 120000 | | mysql-monitor_connect_timeout | 200 | | mysql-monitor_ping_interval | 60000 | | mysql-monitor_ping_max_failures | 3 | | mysql-monitor_ping_timeout | 100 |
这两种检测的区别,据做者回复和抓包分析,总结以下:
Ping is done using mysql_ping()
经过抓包分析,是经过现有的连接(链接池中)发送一个Request Ping
语句
Connect is done using mysql_real_connect()
这是一个客户端到服务器创建连接
,登陆
,退出登陆
,关闭连接
的完整过程
这两个函数的返回值不一样,能够帮助proxysql理解与后端连接除了哪些问题。
模拟场景,验证以上各设置并加深理解其故障检测机制:
两个前提
修改web1 MySQL配置文件中max_connections = 3
;重启web1 MySQL,在其余tty打开几个MySQL连接以确保proxysql没法连接该MySQL。同时在proxysql server上打开抓包功能tcpdump -i em2 host 192.168.1.4 and port 3306 -w /tmp/web_shun.pcap
,而后经过wireshark对比上面各值进行分析。
附件:数据包文件
经过分析可验以如下设置的有效性
mysql-monitor_connect_interval mysql-monitor_ping_interval mysql-monitor_ping_max_failures mysql-shun_recovery_time_sec mysql-ping_interval_server_msec
能够被动
被动就是将全局变量‘mysql-monitor_enabled’置为false,这种状况下,后端server故障后,proxysql不会主动探知,而是在有请求被“正常”路由到该server以后才会在runtime层更改该server状态为‘SHUNNED’ 或者从新变为‘ONLINE’。这个过程,应用无感知(表现为mysql-client命令和sysbench均无报错)
相关变量为
| mysql-shun_on_failures | 5 | | mysql-shun_recovery_time_sec | 60 | | mysql-query_retries_on_failure | 1 | | mysql-connect_retries_on_failure | 5 | | mysql-connect_retries_delay | 1 | | mysql-connection_max_age_ms | 0 | | mysql-connect_timeout_server | 1000 | | mysql-connect_timeout_server_max | 10000 |
###7、关于proxysql和mysql中的最大链接数
首先明确下MySQL中的最大链接数由max_connections
变量控制,proxysql中的最大链接数有两个方面的设置mysql_users.max_connections
和mysql_servers.max_connections
下面就直接说下个人结论 摘自我github上的issue
- if the
mysql_servers.max_connections
is reached, some of the connections will wait until themysql-connect_timeout_server_max
is reached. then proxysql will return error messageSQLSTATE[HY000]: General error: 9001 Max connect timeout reached while reaching hostgroup 1 after 10000ms
.- if the
mysql_users.max_connections
is reached, then the client will see the error message1040: Too many connections
- If backend's global variable 'max_connections' is reached and proxysql has no
ConnFree
with the backend, then client can accomplish the connection with proxysql but all queries will returnERROR 1040 (#HY00): Too many connections
, and the monitor will consider this backend isshun
and will be loged to the proxysql.log
###8、据我所知的bug和不足
bug:
SQLSTATE[HY000]: General error: 2057 A stored procedure returning result sets of different size was called. This is not supported by libmysql
set names xxx
语句还不能有效处理set names xxx collate xxxx
还不能有效处理###总结
稳定性方面:目前咱们已经部分业务切换到了ProxySQL上,运行一直稳定,未遇到cpu负载高或者占用内存多等状况。
运维/DBA友好:借助于ProxySQL的错误日志,咱们发现了以前没注意到的一些SQL问题(如主键重复类的问题等)。还经过stats库里的相关表定位了一些问题。
性能方面:也强于atlas(参见文章)
我的以为,解决掉上述几点bug和不足的话(且不说可能会出现的其余feature),ProxySQL就会更增强大和完美。