天天看點

【原創】MySQL Proxy - 底層實作篇

底層實作篇(chassis)    

【 configfile and commandline options】   

       glib2 提供了 config-file 解析和 command-line option 解析功能。 其提供了将 option 以相同方式暴露給調用者的方法,以及從 configfile 和 commandline 擷取 option 的功能。   

所有 option 的解析過程都可以分為三步:

1. 提取   command-line 上的 basic option   

--help

--version

--defaults-file

2.  處理  defaults-file 檔案   

3. 處理其餘   command-line option 并覆寫  defaults-file 檔案中的相同内容   

【 plugin interface 】   

       chassis 為 plugin 接口調用提供了基礎結構。值得注意的是,其不是專門用于 mysql 的,而是可以用于任何符合其接口要求的 plugin 。提供的功能包括:   

解析 plugin 所在路徑 

對 plugin 的加載 

對 plugin 進行版本檢查 

提供 init 和 shutdown 函數 

向 plugin 暴露配置選項 

基于線程的 i/o

       由于  chassis 不是僅針對于 mysql 設計的,是以其可以用于加載任何種類的 plugin ,隻要該 plugin 提供了符合 chassis 要求的 init 和 shutdown 函數。   

就  mysql proxy 本身而言,一般情況下加載的 plugin 為:   

plugin-proxy

plugin-admin

【 threaded io 】

       從 mysql proxy 0.8 版本開始,已經添加了基于線程的 network-io 以使 proxy 能夠按照可用 cpu 和網卡的數量進行線性擴充。  

       使能 network-threading 功能隻需要在啟動 proxy 時加入下面的參數:  

<a href="http://my.oschina.net/moooofly/blog/114247#">?</a>

1

<code>--event-threads={2 * no-of-cores} (default: 0)</code>

       每一個 event-thread 都通過 "event_base_dispatch()" 進行 loop ,并針對 network-event 或者 time-event 執行相關函數。這些線程隻具有兩種狀态:執行函數狀态和 idle 狀态。如果其處于 idle 狀态,則其能夠從 event-queue 中擷取要進行等待的新 event ,然後将其添加到自身的等待清單中。 

       connection 是可以在多個 event-thread 之間“跳躍”的:因為隻要是 idle 狀态的 event-thread 就能夠擷取到 wait-for-event request - 即具體的事件 - 并進行等待,觸發後執行相關代碼。無論何時,隻要目前 connection 需要重新等待事件(也就是之前事件所對應的操作已經完成),其就會将自身從所線上程中 unregister ,之後重新向全局 event-queue 發送 wait-for-event request 以擷取新事件。  

       一直到 mysql proxy 0.8 版本,腳本代碼的執行都是單線程方式:通過一個全局 mutex 來保護 plugin 的接口操作。因為 connection 或者是處于發送包的狀态,或者是處于調用 plugin 函數的狀态,是以網絡事件将會按照并行方式被處理,僅在多個 connection 需要調用同一個 plugin 函數的時候才會無法并行。  

       chassis_event_thread_loop() 函數就是 event-thread 的主循環實體(其中調用 event_base_dispatch() 函數),而函數 chassis_event_threads_init_thread() 用于設定要監聽的事件和對應的回調。  

下面的描述的是一種典型控制流(不包含連接配接池的情況)  

涉及到的實體:eventrequestqueue, mainthread, workerthread1, workerthread2; 

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<code>--- [ label =</code><code>"accepting new connection "</code><code>];</code>

<code>    </code><code>mainthread -&gt; mainthread [ label =</code><code>"network_mysqld_con_accept()"</code> <code>];</code>

<code>    </code><code>mainthread -&gt; mainthread [ label =</code><code>"network_mysqld_con_handle()"</code> <code>];</code>

<code>    </code><code>mainthread -&gt; eventrequestqueue [ label =</code><code>"add wait-for-event request"</code> <code>];</code>

<code>    </code><code>workerthread1 &lt;- eventrequestqueue [ label =</code><code>"retrieve event request"</code> <code>];</code>

<code>    </code><code>workerthread1 -&gt; workerthread1 [ label =</code><code>"event_base_dispatch()"</code> <code>];</code>

<code>    </code><code>...;</code>

<code>    </code><code>workerthread1 -&gt; workerthread1 [ label =</code><code>"network_mysqld_con_handle()"</code> <code>];</code>

<code>     </code> 

<code>    </code><code>workerthread1 -&gt; eventrequestqueue [ label =</code><code>"add wait-for-event request"</code> <code>];</code>

<code>    </code><code>workerthread2 &lt;- eventrequestqueue [ label =</code><code>"retrieve event request"</code> <code>];</code>

<code>    </code><code>workerthread2 -&gt; workerthread2 [ label =</code><code>"event_base_dispatch()"</code> <code>];</code>

<code>    </code><code>workerthread2 -&gt; workerthread2 [ label =</code><code>"network_mysqld_con_handle()"</code> <code>];</code>

<code>    </code><code>workerthread2 -&gt; eventrequestqueue [ label =</code><code>"add wait-for-event request"</code> <code>];</code>

       在上面的例子中,存在兩個用于處理 event 的工作線程(設定 --event-threads=2 ),每個線程都有自己的 event_base 。以 proxy plugin 為例,首先将 network_mysqld_con_accept() 函數設定為被監聽 socket 的回調,當有新連接配接發生時被觸發。該回調函數是注冊在主線程的 event_base 上的(同時也是全局 chassis 的 event_base)。在設定了連接配接相關結構 network_mysqld_con 後,程式将進入到狀态機處理函數 network_mysqld_con_handle() 中,此時仍然處于主線程中。 

       狀态機将進行入起始狀态:con_state_init ,在目前代碼實作中該狀态是主線程所必進入的第一個狀态。接下來 mysql proxy 要做的事,要麼是和 client 互動,要麼是和 server 進行互動(即或者等待 socket 可讀,或者主動向 backend server 建立連接配接),而狀态機函數 network_mysqld_con_handle() 将設定等待處理事件(對應結構體為 chassis_event_op_t)。簡單來說就是将 event 結構添加到異步隊列中,具體講,就是通過向之前建立的 wakeup-pipe 的寫檔案描述符寫入一個位元組,以産生一個檔案描述符事件。這樣就可以向所有線程通知有新事件請求需要處理。  

       該 pipe 的實作是 libevent 對應實作的一個翻版,其将各種事件與基于檔案描述符的 event-handler 建立了對應關系,采用的輪詢方式進行處理:  

工作線程中的 event_base_dispatch() 函數在其監聽的 fd 被觸發前處于阻塞監聽狀态(在具體實作中是有定時喚醒機制的)。 

定時器事件,信号事件等都不能直接中斷 event_base_dispatch() 的運作。 

上述事件均是通過 write(pipe_fd, ".", 1); 來觸發 fd-event 的可讀,進而通過回調來進行處理。

       在檔案 chassis-event-thread.c 中可以看到,通過 pipe 實作了向工作線程通知:在全局 event-queue 中有東東需要處理。從函數 chassis_event_handle() 可以看出,所有處于 idle 狀态的線程都有平等機會進行事件處理,是以這些線程就能夠“并行的”從全局事件隊列中拉取 event ,并将其添加到自身的監聽事件清單中。  

       通過調用 chassis_event_add() 或者 chassis_event_add_local() 函數可以将 event 添加到 event-queue 中。一般情況下,所有事件都由全局 event_base 負責處理。隻有在使用 connection pool 的情況下,才會強制将與特定 server connection 對應的 events 投遞到特定線程,即将目前 connection 加入到 connection pool 中的那個線程。  

       如果 event 被投遞到全局 event_base 中,那麼不同的線程都可以擷取這個事件,并可以對無保護的 connection pool 資料結構進行修改,可能會導緻競争冒險和崩潰。令這個内部資料結構成為具有線程安全性質是 0.9 release 版本的工作,目前隻提供了最小限度的線程安全性。  

       典型情況是,某個線程會從 event queue 中擷取 request 資訊(理論上,發送 wait request 的線程很可能也是處理這個 request 的線程),并将其添加到自身以 thread-local-store 方式儲存的 event_base 中,并在對應 fd 有事件觸發時獲得通知。  

       該處理過程将一直持續到目前 connection 被 client 或者 server 關閉,或者發生了導緻的 socket 關閉的網絡錯誤。此後将無法處理任何新的 request 。  

       單獨一個線程就足以處理任何添加到其 thread-local 的 event_base 上面的 event 。隻有在一個新的 blocking i/o 操作發生時(一般來說也就是重新進入 event_base_dispatch() 阻塞時),event 才會在不同線程間被“跳躍着”處理,除此外沒有其他例外。是以理論上講,可能會出現一個線程處理了所有活躍的 socket 事件,而另一個線程一直處于 idle 狀态。  

       然而,由于等待網絡事件的發生的狀态是常态(意思就是實際處理的速度都很快),是以(從機率上講)活躍 connection 在所有線程中的分布必定是很均勻的,也就會減輕單個線程處理活躍 connection 的壓力。   

       值得注意的是,盡管在下面的說明中沒有具體指出,主線程目前會在 accept 狀态後參與到對後續 event 的進行中。這不是一個非常理想的實作方式,因為所有 accept 動作本身就需要在主線程中完成。但從另一方面講,這個問題暫時也沒成為實際工作中的瓶頸顯現出來:  

涉及到的實體:plugin, mainthread, mainthreadeventbase, eventrequestqueue, workerthread1, workerthread1eventbase, workerthread2, workerthread2eventbase;  

<code>    </code><code>plugin -&gt; mainthread [ label =</code><code>"network_mysqld_con_accept()"</code> <code>];</code>

<code>    </code><code>workerthread1 -&gt; workerthread1eventbase [ label =</code><code>"wait for event on local event base"</code> <code>];</code>

<code>    </code><code>workerthread1eventbase &gt;&gt; workerthread1 [ label =</code><code>"process event"</code> <code>];</code>

<code>    </code><code>workerthread2 -&gt; workerthread2eventbase [ label =</code><code>"wait for event on local event base"</code> <code>];</code>

<code>    </code><code>workerthread2eventbase &gt;&gt; workerthread2 [ label =</code><code>"process event"</code> <code>];</code>

繼續閱讀