mysql併發執行緒控制之thread pool和優先佇列

myownstars發表於2015-03-08

之前根據官方資料整理了一篇筆記,現在讀起來感覺就是在湊數,在網上看到高人的一篇帖子,現在重新整理記錄一下。

Maria引入thread poolpercona在此基礎上引入優先佇列,依次對這兩種特性進行描述。

 

1 maria實現細節

1、整個連線池內部被分成N個小的groupN預設為cpu的個數,可以透過引數thread_pool_size設定,group之間透過round robin的方式分配連線,group內透過競爭方式處理連線,一個worker執行緒只屬於一個group

2、每個group有一個動態的listenerworker執行緒在迴圈取event時,發現佇列為空時會充當listener透過epoll的方式監聽資料,並將監聽到的event放到group中的佇列;
注:每個group都建立一個epollfd,而空閒連線的socket都加入其中被監聽,只有當event queue為空時才會有worker呼叫epoll_wait;

3、延時建立執行緒,group中的活動執行緒數為0或者group被阻塞時,worker執行緒會被建立,worker執行緒被建立的時間間隔會隨著group內已有的執行緒數目的增加而變大;

4worker執行緒,數目動態變化,這也是相對於5.1版本的一個改進,併發較大時會建立更多的worker執行緒,當從佇列中取不到eventwork執行緒將休眠,超過thread_pool_idle_timeout後結束生命;

5timer執行緒,它會每隔一段時間做兩件事情:

   1)檢查每個group是否被阻塞,判定條件是:group中的佇列中有event,但是自上次timer檢查到現在還沒有worker執行緒從中取出event並處理;

   2kill超時連線並做一些清理工作;

connection生命週期

1 客戶端發起連線,acceptor執行緒呼叫add_connection scheduler callback接收;

每個worker都可充當acceptor,將認證和執行緒初始化offloadworker是為了避免可能的single acceptor阻塞;

登入完畢後,threadpoolconnection socket開啟非同步讀;

2 客戶端呼叫querysocket發出訊號,由worker負責處理;


呼叫介面

static scheduler_functions tp_scheduler_functions=

{

...

tp_init,               // init

NULL,              // init_new_connection_thread

tp_add_connection,         // add_connection

tp_wait_begin,          // thd_wait_begin

tp_wait_end,            // thd_wait_end

tp_post_kill_notification,   // post_kill_notification

NULL,              // end_thread

tp_end             // end

};

以上函式呼叫時都會傳入THD

tp_init

執行緒池初始化工作,對每個group初始化pollfd,啟動timer執行緒

tp_add_connection

根據thdthread_id採用round robin方式將其分配到一個group中,將thd封裝到connection_t的一個結構體中,放到group中的佇列中,入隊時會判斷group中的活動worker執行緒數(thread_group->active_thread_count)是否大於0,否則將喚醒或者建立一個worker執行緒(wake_or_create_thread

Worker執行緒工作邏輯

迴圈從group佇列中get_event並呼叫handler_event

for(;;)

{

  connection = get_event(&this_thread, thread_group, &ts);

  if (!connection)

    break;

  this_thread.event_count++;

  handle_event(connection);

}

get_event

從佇列中取event取不到則充當listener透過epoll從網路監聽事件,如果最終還是沒能取到event,休眠一段時間,直到被喚醒或者超時退出;

handle_event
1
)連線驗證;2)處理連線請求直到連線空閒,為空閒連線設定一個超時期限,之後將連線的socket fd繫結到group中的epollfd


timer_thread執行邏輯

做了兩件事情:1)檢查是否有group被阻塞,阻塞將呼叫wake_or_create_thread2)檢查是否有連線超時,超時將呼叫tp_post_kill_notification

for (;;)

{

/* Check stalls in thread groups */

for(i=0; i< array_elements(all_groups);i++)

{

  if(all_groups[i].connection_count)

  check_stall(&all_groups[i]);

}

/* Check if any client exceeded wait_timeout */

if (timer->next_timeout_check <= timer->current_microtime)

  timeout_check(timer);

}

 

wake_or_create_thread

喚醒或者建立執行緒,被三處呼叫:

1connection加入到queue中時,group中的佇列中沒有活動的worker執行緒:

tp_add_connection

|->queue_put

  |->wake_or_create_thread

2timer執行緒檢查是否有group阻塞,滿足兩個條件中的任一個即被呼叫:group中沒有listener並且佇列為空;group被阻塞

timer_thread

|->check_stall

  |->wake_or_create_thread

3)事務被阻塞(如:鎖等待)透過MYSQL_CALLBACK介面呼叫,等待之前會確定group中有活動的worker執行緒,否則喚醒或者建立worker執行緒

tp_wait_begin

|->wait_begin

  |->|->wake_or_create_thread

 

 

 

2 優先佇列

MariaDB版本有缺陷,為了發揮執行緒池的優勢,需要儘量控制執行緒池中執行緒數目,否則會退化成one-thread-per-connection,而如果嚴格控制執行緒池中執行緒資料,可能會出現排程上的死鎖。

percona在移植MariaDB threadpool的實現後進一步最佳化了執行緒池效能,透過引入優先佇列很好解決了這個問題。

 

工作原理

建立多個group(預設等同於cpu core數量),每個group可有多個worker

執行緒根據connection id被分配到group(生命週期內不變)workersql為單位處理,保證每個連線都能及時得到響應;

每個group有兩個任務佇列,優先佇列:存放已開啟事務的sql,保證事務優先被處理完(儘早釋放鎖);優先佇列為空時才處理普通佇列;

可避免排程上的死鎖:(AB被分到不同的group中,A事務已經開啟,並且獲得了鎖,可能無法立即得到排程執行,B事務依賴A事務釋放鎖資源,但是先於A得到排程);

額外建立一個timer執行緒,定期檢查groups,若發現woker異常則及時喚醒(堵塞/超時/worker執行緒數目不夠)

group任務佇列為空(客戶端連線卻不為空)為空閒連線設定一個超時期限,之後將連線的socket fd繫結到group中的epollfd執行緒則呼叫epoll_wait()批次取任務;

引數

root@(none) 05:33:27>show global variables like '%thread_pool%';

+-------------------------------+--------------+

| Variable_name                 | Value        |

+-------------------------------+--------------+

| thread_pool_high_prio_mode    | transactions |

| thread_pool_high_prio_tickets | 4294967295   |

| thread_pool_idle_timeout      | 60           |

| thread_pool_max_threads       | 100000       |

| thread_pool_oversubscribe     | 3            |

| thread_pool_size              | 24           |

| thread_pool_stall_limit       | 500          |

+-------------------------------+--------------+

7 rows in set (0.00 sec)

thread_pool_high_prio_mode

有三個取值:transactions / statements / none

transactions(default): 使用優先佇列和普通佇列,對於事務已經開啟的statement,放到優先佇列中,否則放到普通佇列中

statements:只使用優先佇列

none: 只是用普通佇列,本質上和statements相同,都是隻是用一個佇列

thread_pool_high_prio_tickets

取值0~4294967295,當開啟了優先佇列模式後(thread_pool_high_prio_mode=transactions),每個連線最多允許thread_pool_high_prio_tickets次被放到優先佇列中,之後放到普通佇列中,預設為4294967295

thread_pool_idle_timeout

worker執行緒最大空閒時間,單位為秒,超過限制後會退出,預設60

thread_pool_max_threads

threadpool中最大執行緒數目,所有groupworker執行緒總數超過該限制後不能繼續建立更多執行緒,預設100000

thread_pool_oversubscribe

一個group中執行緒數過載限制,當一個group中執行緒數超過次限制後,繼續建立worker執行緒會被延遲,預設3

thread_pool_size

threadpoolgroup數量,預設為cpu核心數,server啟動時自動計算

thread_pool_stall_limit

timer執行緒檢測間隔,單位為毫秒,預設500ms

thread_pool_stall_limit 是後臺timer執行緒檢測任務是否堵塞的時間間隔,在併發壓力較大時,該引數設定過大可能會造成timer執行緒無法及時喚醒/建立worker執行緒

 

測試結果

詳情可見

 

 

參考資料

http://blog.chinaunix.net/uid-28364803-id-3431242.html


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1452252/,如需轉載,請註明出處,否則將追究法律責任。

相關文章