標籤:
http://mysql.taobao.org/monthly/2016/02/09/
概述
MySQL 原有線程調度方式有每個串連一個線程(one-thread-per-connection)和所有串連一個線程(no-threads)。
no-threads一般用於調試,生產環境一般用one-thread-per-connection方式。one-thread-per-connection 適合於低並髮長串連的環境,而在高並發或大量短串連環境下,大量建立和銷毀線程,以及線程環境切換,會嚴重影響效能。另外 one-thread-per-connection 對於大量串連數擴充也會影響效能。
為瞭解決上述問題,MariaDB、Percona、Oracle MySQL 都推出了線程池方案,它們的實現方式大體相似,這裡以 Percona 為例來簡略介紹實現原理,同時會介紹我們在其基礎上的一些改進。
實現
線程池方案下,使用者的每個串連不再對應一個線程。線程池由一系列 worker 線程組成,這些worker線程被分為thread_pool_size個group。使用者的串連按 round-robin 的方式映射到相應的group 中,一個串連可以由一個group中的一個或多個worker線程來處理。
listener 線程
每個group中有一個listener線程,通過epoll的方式來監聽本group中串連的事件。listener線程同時也是worker線程,listener線程不是固定的。
listener線程監聽到串連事件後會將事件放入優先順序隊列中,listener線程作為worker線程也處理一些串連事件,以減少環境切換。
listener線程會檢查優先順序隊列是否為空白,如果為空白表示網路空閑,listener線程會作為worker線程處理第一個監聽事件,其他事件仍然放入優先順序隊列中。
另外,當沒有活躍線時,listener會喚醒一個線程,如果沒有線程可以喚醒,且當前group只有一個線程且為listener,則建立一個線程。
優先順序隊列
分為高優先順序隊列和普通隊列,已經開啟的事務並且tickets不為0,放入高優先隊列,否則放入普通隊列。每個串連在thread_pool_high_prio_tickets次被放到優先隊列中後,會移到普通隊列中。worker線程先從高優先隊列取event處理,只有當高優先隊列為空白時才從普通隊列取event處理。
通過優先順序隊列,可以讓已經開啟的事務或短事務得到優先處理,及時提交釋放鎖等資源。
worker 線程
worker線程負責從優先隊列取事件處理。如果沒有取到event,會嘗試從epoll中取一個,如果沒有取到再進入等待,如果等待超過thread_pool_idle_timeout worker線程會退出。
- timer 線程
每隔thread_pool_stall_limit時間檢查一次。
- listener沒有接收新的事件,listener正在等待時需調用
wake_or_create_thread,重新建立listener;
- 從上一次檢查起,worker線程沒有收到新的事件,並且隊列不為空白,則認為發生了stall,需喚醒或建立worker線程;
- 檢查
net_wait_timeout是否逾時,如果逾時退出串連,而不是退出worker線程。
- 何時喚醒或建立worker線程
- 從隊列中取事件時發現沒有活躍線程時;
- worker線程發生等待且沒有活躍線程時;
- timer線程認為發生了stall;
重要參數解析
thread_pool_oversubscribe
一個group中活躍線程和等待中的線程超過thread_pool_oversubscribe時,不會建立新的線程。
此參數可以控制系統的並發數,同時可以防止調度上的死結,考慮如下情況,A、B、C三個事務,A、B 需等待C提交。A、B先得到調度,同時活躍線程數達到了thread_pool_max_threads上限,隨後C繼續執行提交,此時已經沒有線程來處理C提交,從而導致A、B一直等待。thread_pool_oversubscribe控制group中活躍線程和等待中的線程總數,從而防止了上述情況。
thread_pool_stall_limit
timer線程活動訊號間隔時間。此參數設定過小,會導致建立過多的線程,從而產生較多的線程環境切換,但可以及時處理鎖等待的情境,避免死結。參數設定過大,對長語句有益,但會阻塞短語句的執行。參數設定需視具體情況而定,例如99%的語句10ms內可以完成,那麼我們可以將就thread_pool_stall_limit設定為10ms
一些改進
lock tables read 的處理
對於聲明 lock tables read 等明確聲明表鎖的事件,放入高優先順序隊列。
binlog dump線程的處理
binlog dump線程是典型的長事務情境,當多個binlog dump線程分配到同一個group中時,group中的線程很容易超過thread_pool_oversubscribe限制,從而導致效能下降。
最佳化方法是修改binlog dump線程不受thread_pool_oversubscribe限制。
豐富診斷資訊information_schema.thread_group_status
show create table THREAD_GROUP_STATUS\G *************************** 1. row *************************** Table: THREAD_GROUP_STATUS Create Table: CREATE TEMPORARY TABLE `THREAD_GROUP_STATUS` ( `ID` int(21) unsigned NOT NULL DEFAULT ‘0‘, `THREAD_COUNT` int(21) unsigned NOT NULL DEFAULT ‘0‘, `ACTIVE_THREAD_COUNT` int(21) unsigned NOT NULL DEFAULT ‘0‘, `CONNECTION_COUNT` int(21) unsigned NOT NULL DEFAULT ‘0‘, `WAITING_THREAD_COUNT` int(21) unsigned NOT NULL DEFAULT ‘0‘, `DUMP_COUNT` bigint(21) unsigned NOT NULL DEFAULT ‘0‘, `LOW_QUEUE_COUNT` bigint(21) unsigned NOT NULL DEFAULT ‘0‘, `HIGH_QUEUE_COUNT` bigint(21) unsigned NOT NULL DEFAULT ‘0‘ ) ENGINE=MEMORY DEFAULT CHARSET=utf8
線程池調度異常,無法串連的處理
對於本地登入的使用者,走老的one_thread_per_connection邏輯,從而解決無法串連的情況。
串連池和線程池的區別
最後說一點串連池和線程池的區別。串連池和線程池是兩個獨立的概念,串連池是在用戶端的最佳化,緩衝客戶的串連,避免重複建立和銷毀串連。而線程池是伺服器端的最佳化。兩者的最佳化角度不同,不相關,因此兩種最佳化可以同時使用。
mysql線程池