本文是我在學習和驗證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就會更增強大和完美。