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


底層實現篇(chassis) 
 

Configfile and Commandline Options】  

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

全部 option 的解析過程均可以分爲三步: mysql

1. 提取   command-line 上的 basic option  
  • --help
  • --version
  • --defaults-file
2.  處理  defaults-file 文件  
3. 處理其他   command-line option 並覆蓋  defaults-file 文件中的相同內容  


Plugin Interface  

       chassis 爲 plugin 接口調用提供了基礎結構。值得注意的是,其不是專門用於 MySQL 的,而是能夠用於任何符合其接口要求的 plugin 。提供的功能包括:  
  1. 解析 plugin 所在路徑 
  2. 對 plugin 的加載 
  3. 對 plugin 進行版本檢查 
  4. 提供 init 和 shutdown 函數 
  5. 向 plugin 暴露配置選項 
  6. 基於線程的 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 時加入下面的參數: 
--event-threads={2 * no-of-cores} (default: 0)

       每個 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; 
--- [ label = "Accepting new connection "]; 

    MainThread -> MainThread [ label = "network_mysqld_con_accept()" ]; 
    MainThread -> MainThread [ label = "network_mysqld_con_handle()" ]; 

    MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
    WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ]; 
    WorkerThread1 -> WorkerThread1 [ label = "event_base_dispatch()" ]; 
    ...; 
    WorkerThread1 -> WorkerThread1 [ label = "network_mysqld_con_handle()" ]; 
     
    WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
     
    WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ]; 
    WorkerThread2 -> WorkerThread2 [ label = "event_base_dispatch()" ]; 
    ...; 
    WorkerThread2 -> WorkerThread2 [ label = "network_mysqld_con_handle()" ]; 
     
    WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
    ...;

       在上面的例子中,存在兩個用於處理 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 創建了對應關係,採用的輪詢方式進行處理: 
  1. 工做線程中的 event_base_dispatch() 函數在其監聽的 fd 被觸發前處於阻塞監聽狀態(在具體實現中是有定時喚醒機制的)。 
  2. 定時器事件,信號事件等都不能直接中斷 event_base_dispatch() 的運行。 
  3. 上述事件均是經過 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; 
--- [ label = "Accepting new connection "]; 

    Plugin -> MainThread [ label = "network_mysqld_con_accept()" ]; 
    MainThread -> MainThread [ label = "network_mysqld_con_handle()" ]; 

    MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
    WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ]; 
    WorkerThread1 -> WorkerThread1EventBase [ label = "Wait for event on local event base" ]; 
    ...; 
    WorkerThread1EventBase >> WorkerThread1 [ label = "Process event" ]; 
     
    WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
     
    WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ]; 
    WorkerThread2 -> WorkerThread2EventBase [ label = "Wait for event on local event base" ]; 
    ...; 
    WorkerThread2EventBase >> WorkerThread2 [ label = "Process event" ]; 
     
    WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ]; 
    ...;
相關文章
相關標籤/搜索