今天推薦一篇文章,深度分享mysql-proxy的細節。
原文轉自公眾號:架構師之路
=======================================
mysql-proxy是mysql官方提供的mysql中間件服務,上遊可接入若干個mysql-client,後端可連接若干個mysql-server,它使用mysql協議,任何連接mysql的上遊無需任何更改即可遷移至mysql-proxy上。
mysql-proxy最基本的用法,就是作為一個請求攔截,請求中轉的中間層:
進一步的,mysql-proxy可以分析與修改請求。攔截查詢和修改結果,需要通過編寫Lua腳本來完成。mysql-proxy允許用戶指定Lua腳本對請求進行攔截,對請求進行分析與修改,它還允許用戶指定Lua腳本對伺服器的返回結果進行修改,加入一些結果集或者去除一些結果集均可。
【SK畫外音:sql攔截與修改,性能分析與監控,讀寫分離,請求路由等各種功能都是通過編寫Lua腳本來完成的,mysql-proxy是個框架,具備很好的擴展性。這個框架提供了6個hook點,能夠讓用戶能夠動態的介入到client與server中的通訊中去。】
二、mysql-proxy命令參數版本顯示
使用--version參數即可:
./mysql-proxy --version
從輸出可以看到mysql-proxy的版本,以及依賴的glib,libevent,lua的版本
1 mysql-proxy0.8.3
2 chassis: mysql-proxy 0.8.3
3 glib2: 2.16.6
4 libevent: 1.4.13-stable
5 LUA: Lua 5.1.4
8 -- modules
9 proxy: 0.8.3
簡單啟動
mysql-proxy啟動至少需要指定一個後端mysql的ip和埠號,此時帶上--proxy-backend-addresses參數即可:
./mysql-proxy --proxy-backend-addresses=127.0.0.1:3306
指定配置文件啟動
mysql-proxy亦可以指定配置文件啟動,此時帶上--defaults-file來指定配置文件即可:
./mysql-proxy --defaults-file=./mysql-proxy.cnf
mysql-proxy.cnf的格式如下:
1[mysql-proxy]
2proxy-backend-addresses = 127.0.0.1:3306
需要注意,在命令行中參數前需要加入「--」,而在配置文件中則不需要。
尋求幫助
可以使用--help,或者--help-all
./mysql-proxy --help
./mysql-proxy --help-all
Proxy常用選項
選項
說明
proxy-backend-addresses=$host:$port
後端mysql的ip和port,多個以逗號分隔
proxy-read-only-backend-addresses=$host:$port
後端只讀mysql的ip和port,多個以逗號分隔
proxy-skip-profiling=$bool
是否禁用查詢性能剖析
proxy-lua-script=$file_name
lua文件
注意:
如果設置了多個後端mysql,負載均衡策略為round-robin。例如設了A和B兩臺後端,第一個請求轉發到A,第二個請求轉發到B,第三個請求轉發到A,以此類推。
Proxy服務常用選項
選項
說明
proxy-address=$host:$port
mysql-proxy的ip和port,默認port是4040
daemon=$bool
是否使用daemon模式啟動
event-threads=$count
event-handing線程數,默認值是1
keep-alive=$bool
proxy服務崩潰後自動重啟
log-file=$file_name
日誌文件
log-level=$level
日誌級別:error|warning|info|message|debug
log-use-syslog=$bool
日誌使用syslog
max-open-files=$count
最大文件句柄數
pid-file=$file_name
pid文件
典型配置文件示例
1[mysql-proxy]
2
3proxy-address = 127.0.0.1:4040
4 daemon =true
5event-threads = 2
6 keep-alive= true
7 log-file =./mysql-proxy.log
8 log-level= debug
9max-open-files = 1024
10 pid-file =./mysql-proxy.pid
11
12 proxy-lua-script= ./ro-balance.lua
13proxy-backend-addresses = 127.0.0.1:3306
14proxy-skip-profiling = false
啟動過程中提示:
2013-12-28 15:15:49: (critical)mysql-proxy-cli.c:326: loading config from './mysql-proxy.cnf' failed:permissions of ./mysql-proxy.cnf aren't secure (0660 or stricter required)
由於安全要求,必須將配置文件權限設為660(創建人可讀寫,同組人可讀),否則不允許啟動。
三、mysql-proxy腳本編程如「簡介」中所述,mysql-proxy向用戶提供了6個hook點,讓用戶實現Lua腳本來完成各種功能,這些hook點是以函數的形式提供的,用戶可以實現這些函數,在不同事件、不同操作發生時,做我們期望的事情。
connect_server()
mysql-client向proxy發起連接時,proxy會調用這個函數。用戶可以實現該函數,來做一些負載均衡的事情,例如選擇將要連向那個mysql-server。假設有多個mysql-server後端,而用戶又沒有實現這個函數,proxy默認採用輪詢(round-robin)策略。
read_handshake()
mysql-server向proxy返回「初始握手信息」時,proxy會調用這個函數。用戶可以實現這個函數,來做更多的權限驗證工作。
read_auth()
mysql-client向proxy發送認證報文(user_name, password,database)時,proxy會調用這個函數。
read_auth_result()
mysql-server向proxy返回認證結果時,proxy會調用這個函數。
read_query()
認證完成後,mysql-client每次經過proxy向mysql-server發送query報文時,proxy會調用這個函數。用戶如果要攔截請求,就可以模擬mysql-server直接返回了,當然用戶亦可以實現各種策略,修改請求,路由請求等各種不同的業務邏輯。
read_query_result()
認證完成後,mysql-server每次經過proxy向mysql-client返回query結果時,proxy會調用這個函數。需要注意,如果用戶沒有顯示實現read_query()函數,則read_query_result()函數是不會被調用的。用戶可以在此處實現各種合併策略,或者對結果集進行修改。
【SK畫外音:下圖是一個各hook函數的觸發圖(請注意請求方向)】
【SK繼續畫外音:可以發現,最重要的兩個函數其實是read_query()和read_query_result(),各種sql的改寫與結果集的改寫邏輯,都是在這兩個函數中實現的,更細節的query過程如下圖】
案例一: sql時間統計分析
不妨設mysql-client提交的原sql為:SELECT * FROM City;
proxy可以在read_query()裡將其改寫為:SELECT NOW(); SELECT *FROM City; SELECT NOW();
這樣在返回結果集時,就可以在應用層對sql時間進行記錄,以方便統計分析。
案例二:sql性能統計分析
不妨設mysql-client提交的原sql為:SELECT * FROM City;
proxy可以在read_query()裡將其改寫為: SELECT * FROM City; EXPLAIN SELECT * FROMCity;
這樣在返回結果集時,就可以在應用層對sql性能進行記錄,以方便統計分析。
需要強調的是,這兩個案例,由於proxy在read_query()時對sql進行了改寫,故在read_query_result()時,mysql-server其實返回了比原請求更多的信息,proxy一定要將多餘的信息去掉,再返回mysql-client。多說一句,可以加入一個唯一ID,來對請求sql和返回結果進行配對。
demo
需求:在業務層統計sql日誌
實現:tutorial-basic.lua
1 -- 如果是COM_QUERY,就將內容列印出來
2 functionread_query( packet )
3 if string.byte(packet) == proxy.COM_QUERYthen
4 print("we got a normal query:" .. string.sub(packet, 2))
5 end
6 end
修改配置並重啟proxy:
proxy-lua-script = tutorial-basic.lua
客戶端使用黑黑的窗口連接4040埠的proxy,並進行一系列sql操作,操作序列如下:
mysql -h127.0.0.1 -uroot -P4040
show databases;
use im;
show tables;
select * from user;
quit
通過tutorial-basic.lua,會將上述操作都記錄到日誌中,日誌序列如下:
we got a normal query: select @@version_comment limit1
we got a normal query: show databases
we got a normal query: SELECT DATABASE()
we got a normal query: show tables
we got a normal query: select * from user
【SK畫外音:咦,通過這個日誌我才知道,連上資料庫會默認發一個select @@version_comment limit 1的請求呢。use im;這個請求,為啥變成SELECT DATABASE()了呢?】
四、FAQ(1)如何實現最簡單的讀寫分離?
shell> mysql-proxy \
--proxy-backend-addresses=10.0.1.2:3306 \
--proxy-read-only-backend-addresses=10.0.1.3:3306
(2)mysql proxy支持所有版本的mysql麼?
只支持mysql5.0+的mysql協議。
(3)如果開啟負載均衡,那事務怎麼辦?所有的query會發往同一臺mysql麼?
如果用戶不專門定製Lua腳本,會發往同一臺mysql,以保證其完整性。
(4)系統上下文切換代價大麼?Lua腳本引入的額外開銷有多大?
Lua很快,對於大部分應用來說,額外開銷很小,原始包(raw packet)開銷大概在400微秒左右。
【SK畫外音:這,,,我不太相信】
(5)Lua腳本可以動態加載麼?
升級了Lua腳本,連接建立後才會讀取新的喲。
(6)如果proxy和mysql部署在一臺機器上,有什麼需要建議的呢?
proxy單獨部署也可以,和mysql部署在同一臺機器上也可以。相比mysql而言,proxy不怎麼佔CPU和內存,其性能損耗可以忽略不計。
【SK畫外音:這,,,性能損耗可以忽略,這我也不信】
(7)Lua腳本是預讀到內存裡的吧?還是說每次都要到文件系統裡讀?
客戶端連接過來時,或者腳本更新時會讀取,其他的時候都讀內存喲。
(8)加入客戶端連上來,出發了connect_server()函數,Lua腳本能連接多個mysql麼?
可以,使用指引裡有樣例代碼喲。
(9)proxy可以處理SSL連接麼?
不可以,作為中間人,不能處理加密信息。
(10)proxy不會獲取和保存我的明文密碼吧?
不會,也獲取不到。mysql協議不允許密碼以明文傳輸,傳輸的都是加密後的密文。
(11)有隔離問題,調試問題的工具麼?如果請求出錯了,我怎麼知道錯誤發生在mysql客戶端,還是mysql服務端,還是proxy呢?
你可以自己在proxy裡設置debug腳本進行調試,例如設置斷點什麼的。
(12)瞧你官網吹的,有哪個大網站用了mysql-proxy麼?請求量是什麼級別?
這問題問的,作為官網我,,,蓋亞在線(http://www.gaiaonline.com/) 就是用的mysql-proxy喲,qps可以到2400。
【SK畫外音:這是個外國的遊戲網站,頁面好醜啊。】
(13)如果我在Lua腳本用使用LuaSocket可以麼?
大哥,這可能引起阻塞的,強烈不建議這樣。