MySQL Proxy:底層實現篇

來源:互聯網
上載者:User

底層實現篇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 。提供的功能包括:

由於 chassis 不是僅針對於 MySQL 設計的,所以其可以用於載入任何種類的 plugin ,只要該 plugin 提供了符合 chassis 要求的 init 和 shutdown 函數。

就 MySQL Proxy 本身而言,一般情況下載入的 plugin 為:

 
  1. plugin-proxy  
  2. plugin-admin 

Threaded IO 】

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

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

 
  1. --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;

 
  1. --- [ label = "Accepting new connection "];   
  2.  
  3.     MainThread -> MainThread [ label = "network_mysqld_con_accept()" ];   
  4.     MainThread -> MainThread [ label = "network_mysqld_con_handle()" ];   
  5.  
  6.     MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  7.     WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  8.     WorkerThread1 -> WorkerThread1 [ label = "event_base_dispatch()" ];   
  9.     ...;   
  10.     WorkerThread1 -> WorkerThread1 [ label = "network_mysqld_con_handle()" ];   
  11.        
  12.     WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  13.        
  14.     WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  15.     WorkerThread2 -> WorkerThread2 [ label = "event_base_dispatch()" ];   
  16.     ...;   
  17.     WorkerThread2 -> WorkerThread2 [ label = "network_mysqld_con_handle()" ];   
  18.        
  19.     WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  20.     ...; 

在上面的例子中,存在兩個用於處理 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 建立了對應關係,採用的輪詢方式進行處理:


在檔案 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;

 
  1. --- [ label = "Accepting new connection "];   
  2.  
  3.     Plugin -> MainThread [ label = "network_mysqld_con_accept()" ];   
  4.     MainThread -> MainThread [ label = "network_mysqld_con_handle()" ];   
  5.  
  6.     MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  7.     WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  8.     WorkerThread1 -> WorkerThread1EventBase [ label = "Wait for event on local event base" ];   
  9.     ...;   
  10.     WorkerThread1EventBase >> WorkerThread1 [ label = "Process event" ];   
  11.        
  12.     WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  13.        
  14.     WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  15.     WorkerThread2 -> WorkerThread2EventBase [ label = "Wait for event on local event base" ];   
  16.     ...;   
  17.     WorkerThread2EventBase >> WorkerThread2 [ label = "Process event" ];   
  18.        
  19.     WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  20.     ...; 

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.