mysql-proxy學習(四)——性能測試

1.     硬件配置

hwconfig

Summary:        Intel S5500WBV, 2 x Xeon E5620 2.40GHz,23.5GB / 24GB 1067MHz
System:         Intel S5500WBV
Processors:     2 x Xeon E5620 2.40GHz 133MHz FSB (HTenabled, 8 cores, 16 threads)
Memory:         23.5GB / 24GB 1067MHz == 6 x 4GB, 2 xempty
千兆網卡

2.     測試case

長連接每個連接發送100,000 select 1的請求報文(即請求報文大小爲13,響應報文大小爲56)。建議在測試server前先用netperf測試一下網絡環境,正常的局域網網絡應該發送上面的報文應該可以達到300,000 PPS;併發60直接連接mysql-server qps可以達到90000。CMD:bin/mysql-proxy --daemon --pid-file=log/proxy.pid--proxy-address=:4040 --log-level=debug --log-file=log/proxy.log--proxy-backend-addresses=10.232.64.76:17907 --event-threads=16,即下面的測試不走lua腳本,也沒有連接池 c-cons:b-cons = 1:1。

3.     實驗數據

Event-threads Concurrent-cons QPS CPU CS
1 1 3800 30%  
1 10 30000 96%  
1 30 33000 100% 400
4 1 4200 48%  
4 10 37400 300%  
4 30 53000 360% 120000
4 50 69131 350% 150000
4 80 67000 350% 150000
12 1 3797 50% 110000
12 5 20000 300% 500000
12 12 37233 600% 600000
12 30 51890 800% 480000
12 50 53255 900% 400000
12 80 53426 950% 350000
16 10 29308 850% 832000
16 30 48390 1110% 580000
16 50 48056 1200% 400000
16 80 47000 1350% 300000

 在測試的過程中通過mpstat -I SUM -u -P ALL 1,發現所有的網卡中斷都只跑在0 CPU上,之前的網卡SMP是設置好的,然後使用perf導致機器掛了幾次,重啓後之前的SMP設置就無效了。所有上面的所有數據都是沒有開通網卡SMP的數據(網卡SMP見http://jasonwu.me/category/Linux)。後面又試着把網卡的SMP開啓,發現數據還不如上面的,不過差別不大低2000左右,本來以爲開啓了可以將intr及soft分攤到各個CPU上,以此來提高性能,最後攤是攤開了,但qps並沒有上去。看來性能並不在內核網絡處理上,集中式的單CPU處理已經可以達到上面我們的QPS甚至更高的要求,而開通SMP對於這種壓力不夠的影響可能是負的,至於原因超出了我現在的知識範圍(可能是代碼的親緣性上去了,歡迎大牛指點);當然我們這裏並不是說開通網卡的SMP會比不開好,只是我們現在的瓶頸還不在網卡的處理上,當我們上層的處理能力超過單CPU的網卡接收能力的話,那麼此時開通網卡SMP,應該就可以達到更高的性能。

4.     Mysql-proxy的網絡模型

      之前的文章裏說過mysql-proxy是通過libevent的事件驅動的模型,但缺少介紹多線程在之個模型中的作用及關係:在mysql-proxy上線程是event-thread,即處理event的線程。Mysql-proxy主要有三類event fd:listen fd、socketpair[2]、net-read(write)。第一個很明顯就是mysql的監聽端口默認4040,這個事件只註冊在main-thread線程上;第二個是一個socket對,即兩個socket fd,其中socketpair[0]用於讀(所有的線程都會關心這個event readable,回調函數chassis_event_handle),socketpair[1]用於寫,當要add一個event(這個event就是第三類的event)的時候(chassis_event_add),會將該event放到一個全局的隊列裏,然後往socketpair[1]寫一個”.”,然後所有線程都會被喚醒一次,喚醒後它們從全局隊列裏取得剛纔保存的event,然後將這個event註冊到自己的event-base上,那麼當該event被觸發的話,將由新的這個取得的線程來處理,另外,被喚醒的線程將盡量多取點event,只要它能夠獲得取隊列的鎖;第三類就是用於實際報文傳送的read/write socket(它們的回調函數都是network_mysqld_con_handle),這一類與第二類的關係是:當我們要去recv或send socket的時候出現EAGAIN 錯誤,那麼我們就應該去重新註冊一下該事件(所有的第三類事件都是非EV_PERSIST),此時就會調用chassis_event_add函數,這就將觸發新的event可能被別的線程獲得處理。
        擴展一下當多個event-threads的時候將會出現什麼情況:①多個線程競爭一個event-queue;②當一個第三類socket出現EAGAIN,那麼將喚醒所有的線程(驚羣);③一個連接的client/severe socket不是固定在一個線程上執行,即任何的第三類event將隨機的在任意線程上運行,在每次交互過程中都會發生變化遷移。簡而言之就是event-thread不關心event是屬於哪個連接的,它只關心這個event可用與否,當出現不可用時,它就該這個event push到全局的event-queue,然後喚醒所有的線程去競爭它。
       上面是mysql-proxy的網絡模型下面我們再來看一下核心處理流程。這個處理流程主要包括下面4個部分:報文接收發送event處理(network_mysqld_con_handle),該函數完成報文的讀寫、狀態的判斷轉移;然後調用plugin_call,該函數根據狀態獲得相應狀態下的hook function;再接下來執行狀態的回調函數(proxy_read_auth_result,proxy_send_query_result,proxy_connect_server…);最後就是回調函數再去調用lua script裏的相應函數,完成定製的任務(如讀寫分離,輸出select報文,結果集等一系列的操作)。注:前面三個都是由代碼編譯後寫死了,但第4個過程我們可以定製,修改,這也是mysql-proxy留着一個擴展的地方。另外即使我們上面沒有指定lua script,但是前面三個過程都是要走的,只是第4個不會被調用。

5.     數據分析

瞭解了網絡模型後,我們再來看一下上面的數據:首先對於1個event-thread的30個連接的時候CPU已經喫滿了,顯然再多個併發連接數也無益了。所以我們可以看到當event-thread增加到4的時候,同樣的併發連接數QPS都上去了,另一個顯著的變化是CS也上去了,這個就是因爲上面我們說的驚羣現象,它把所有的線程都喚醒一次,而且醒來後還得去競爭event-queue,這就無形中增加了多個上下文切換的時機。所以同樣可以解釋當併發越多的時候,CS就越大(在併發連接數相同的情況下),另外一個比較好解釋的現象是在event-thread相同的情況下併發連接數越多,CS上升,然而奇怪的是當併發連接數再大的話CS反而下降,這個可能的原因是因爲chassis_event_handle在取event的時候是儘可能多的取,當併發連接數多的時候,它可能一次會獲得多個event,減少了event的擴散。
       當然最令我不解的是爲什麼event-thread增加了QPS卻下降了,我們以4個thread爲參考值,那麼我們系統16CPU,即使不能線性,那麼折半應該不過分吧,那麼多少也得69131 * 2 ~= 100000這個級別。當然有一種可能的解釋是CS太多了,但以我個人的經驗一般CS是果並不是因,也就是有其它真正的原因導致CS高,然後間接影響QPS,或者並不是CS的原因(因爲當解決了這個瓶頸之後CS可能也會跟着下降下去),另外在16個event-threads的時候每個CPU上有user:sys:soft:idle大概=12%:65%:8%:15%;上面的現象MS現在通過代碼不能那麼容易的解釋(個人實力未達),所以只能藉助各種工具。

6.     工具分析

       爲了防止某個邏輯CPU單獨執行某一種操作,影響測試結果,所以我們在進行性能排查的時候,我們又把網卡SMP設置爲多個CPU。我們這裏使用的檢測工具是perf。通過perf record -g -p PID的方式,獲得詳細性能數據:



可以看到兩個大頭分別是調用futex_wake、futex_wait(http://hi.baidu.com/luxiaoyi/blog/item/3db9a302ba9a0f074bfb51e3.html);上面我們說到核心處理流程有4個過程,通過上面的perf report可以看到出現了前面兩個過程(network_mysqld_con_handle,plugin_call),我們再回代碼裏:

Plugin_call(…)

{…

       LOCK_LUA(srv->priv->sc);   <===>  g_mutex_lock(sc->mutex);

       ret = (*func)(srv, con);

       UNLOCK_LUA(srv->priv->sc);

}

Perf果然是神器,之前一直在看網絡模型,懷疑的重點也在驚羣上,完全沒注意到這個地方。至於原因也非常的簡單,雖然說在第二個過程plugin_call還沒開始調用lua相關函數,但是在第三個過程裏就要使用到,所有的線程共享相同的一些全局luatable,可見這個鎖粒度還是挺大的。

7.     驗證

這裏只是先說一下理論:一、防止驚羣,將由一對socketpair改爲多對即每個線程一對;二、現在的所有event是隨機的由任何一個線程來處理,是否可以將一對event(client與backend server)固定爲一個線程來處理,減少線程數據遷移?三、減少LOCK_LUA(srv->priv->sc)的鎖粒度,或者去掉lua部分。

歡迎大牛指正。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章