MySQL里面為了提高客戶端請(qǐng)求創(chuàng)建連接過程的性能,提供了一個(gè)連接池也就是
創(chuàng)新互聯(lián)專注于通許企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,成都商城網(wǎng)站開發(fā)。通許網(wǎng)站建設(shè)公司,為通許等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站設(shè)計(jì),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
Thread_Cache池,將空閑的連接線程放在連接池中,而不是立即銷毀.這樣的好處就是,當(dāng)又有一個(gè)新的請(qǐng)求的時(shí)候,mysql不會(huì)立即去創(chuàng)建連接
線程,而是先去Thread_Cache中去查找空閑的連接線程,如果存在則直接使用,不存在才創(chuàng)建新的連接線程.
有關(guān)Thread_Cache在MySQL有幾個(gè)重要的參數(shù),簡(jiǎn)單介紹如下:
thread_cache_size
Thread_Cache
中存放的最大連接線程數(shù).在短連接的應(yīng)用中Thread_Cache的功效非常明顯,因?yàn)樵趹?yīng)用中數(shù)據(jù)庫的連接和創(chuàng)建是非常頻繁的,如果不使用
Thread_Cache那么消耗的資源是非常可觀的!在長連接中雖然帶來的改善沒有短連接的那么明顯,但是好處是顯而易見的.但并不是越大越好大了反而
浪費(fèi)資源這個(gè)的確定一般認(rèn)為和物理內(nèi)存有一定關(guān)系,如下:
復(fù)制代碼 代碼如下:
1G — 8
2G — 16
3G — 32
3G — 64
如果短連接多的話可以適當(dāng)加大.
thread_stack
每個(gè)連接被創(chuàng)建的時(shí)候,mysql分配給它的內(nèi)存.這個(gè)值一般認(rèn)為默認(rèn)就可以應(yīng)用于大部分場(chǎng)景了,除非必要非則不要?jiǎng)铀?
thread_handing
運(yùn)用Thread_Cache處理連接的方式,5.1.19添加的新特性.有兩個(gè)值可選[no-threads|one-thread-per-
connection] 看字面意思大家也該猜出八九分了,呵呵,no-threads
服務(wù)器使用一個(gè)線程,one-thread-per-connection
服務(wù)器為每個(gè)客戶端請(qǐng)求使用一個(gè)線程.原手冊(cè)中提到,no-threads是在Linux下調(diào)試用的.
復(fù)制代碼 代碼如下:
mysql show variables like 'thread%';
+——————-+—————————+
| Variable_name | Value |
+——————-+—————————+
| thread_cache_size | 32 |
| thread_handling | one-thread-per-connection |
| thread_stack | 196608 |
+——————-+—————————+
3 rows in set (0.01 sec)
mysql show status like '%connections%';
+———————-+——–+
| Variable_name | Value |
+———————-+——–+
| Connections | 199156 |
| Max_used_connections | 31 |
+———————-+——–+
2 rows in set (0.00 sec)
mysql show status like '%thread%';
+————————+——–+
| Variable_name | Value |
+————————+——–+
| Delayed_insert_threads | 0 |
| Slow_launch_threads | 0 |
| Threads_cached | 3 |
| Threads_connected | 6 |
| Threads_created | 8689 |
| Threads_running | 5 |
+————————+——–+
6 rows in set (0.00 sec)
通過以上3個(gè)命令,可以看到服務(wù)器的 thread_cache池中最多可以存放32個(gè)連接線程,為每個(gè)客戶端球使用一個(gè)線程.為每個(gè)連接的線程分配192k的內(nèi)存空間.
服 務(wù)器總共有199156次連接,最大并發(fā)連接數(shù)為31,當(dāng)前在thread_cashe池中的連接數(shù)為3個(gè),連接數(shù)為6個(gè),處于活躍狀態(tài)的有5個(gè),共創(chuàng)建 了8689次連接.顯然這里以短連接為主.可以算出thread_cache命中率,公式為:
復(fù)制代碼 代碼如下:
Thread_Cache_Hit=(Connections-Thread_created)/Connections*100%
當(dāng)前服務(wù)器的Thread_cache命中率約為95.6%這個(gè)結(jié)果我還是比較滿意的.但是可以看出 thread_cache_size有點(diǎn)多余改成16或8更合理一些.
one-connection-per-thread
根據(jù)scheduler_functions的模板,我們也可以列出one-connection-per-thread方式的幾個(gè)關(guān)鍵函數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static scheduler_functions con_per_functions=
{ max_connection+1, // max_threads
NULL,
NULL,
NULL, // init
Init_new_connection_handler_thread, // init_new_connection_thread
create_thread_to_handle_connection, // add_connection
NULL, // thd_wait_begin
NULL, // thd_wait_end
NULL, // post_kill_notification
one_thread_per_connection_end, // end_thread
NULL // end
};
1.init_new_connection_handler_thread
這個(gè)接口比較簡(jiǎn)單,主要是調(diào)用pthread_detach,將線程設(shè)置為detach狀態(tài),線程結(jié)束后自動(dòng)釋放所有資源。
2.create_thread_to_handle_connection
這個(gè)接口是處理新連接的接口,對(duì)于線程池而言,會(huì)從thread_id%group_size對(duì)應(yīng)的group中獲取一個(gè)線程來處理,而one-connection-per-thread方式則會(huì)判斷是否有thread_cache可以使用,如果沒有則新建線程來處理。具體邏輯如下:
(1).判斷緩存的線程數(shù)是否使用完(比較blocked_pthread_count 和wake_pthread大小)
(2).若還有緩存線程,將thd加入waiting_thd_list的隊(duì)列,喚醒一個(gè)等待COND_thread_cache的線程
(3).若沒有,創(chuàng)建一個(gè)新的線程處理,線程的入口函數(shù)是do_handle_one_connection
(4).調(diào)用add_global_thread加入thd數(shù)組。
3.do_handle_one_connection
這個(gè)接口被create_thread_to_handle_connection調(diào)用,處理請(qǐng)求的主要實(shí)現(xiàn)接口。
(1).循環(huán)調(diào)用do_command,從socket中讀取網(wǎng)絡(luò)包,并且解析執(zhí)行;
(2). 當(dāng)遠(yuǎn)程客戶端發(fā)送關(guān)閉連接COMMAND(比如COM_QUIT,COM_SHUTDOWN)時(shí),退出循環(huán)
(3).調(diào)用close_connection關(guān)閉連接(thd-disconnect());
(4).調(diào)用one_thread_per_connection_end函數(shù),確認(rèn)是否可以復(fù)用線程
(5).根據(jù)返回結(jié)果,確定退出工作線程還是繼續(xù)循環(huán)執(zhí)行命令。
4.one_thread_per_connection_end
判斷是否可以復(fù)用線程(thread_cache)的主要函數(shù),邏輯如下:
(1).調(diào)用remove_global_thread,移除線程對(duì)應(yīng)的thd實(shí)例
(2).調(diào)用block_until_new_connection判斷是否可以重用thread
(3).判斷緩存的線程是否超過閥值,若沒有,則blocked_pthread_count++;
(4).阻塞等待條件變量COND_thread_cache
(5).被喚醒后,表示有新的thd需要重用線程,將thd從waiting_thd_list中移除,使用thd初始化線程的thd-thread_stack
(6).調(diào)用add_global_thread加入thd數(shù)組。
(7).如果可以重用,返回false,否則返回ture
線程池與epoll
在引入線程池之前,server層只有一個(gè)監(jiān)聽線程,負(fù)責(zé)監(jiān)聽mysql端口和本地unixsocket的請(qǐng)求,對(duì)于每個(gè)新的連接,都會(huì)分配一個(gè)獨(dú)立線程來處理,因此監(jiān)聽線程的任務(wù)比較輕松,mysql通過poll或select方式來實(shí)現(xiàn)IO的多路復(fù)用。引入線程池后,除了server層的監(jiān)聽線程,每個(gè)group都有一個(gè)監(jiān)聽線程負(fù)責(zé)監(jiān)聽group內(nèi)的所有連接socket的連接請(qǐng)求,工作線程不負(fù)責(zé)監(jiān)聽,只處理請(qǐng)求。對(duì)于overscribe為1000的線程池設(shè)置,每個(gè)監(jiān)聽線程需要監(jiān)聽1000個(gè)socket的請(qǐng)求,監(jiān)聽線程采用epoll方式來實(shí)現(xiàn)監(jiān)聽。
Select,poll,epoll都是IO多路復(fù)用機(jī)制,IO多路復(fù)用通過一種機(jī)制,可以監(jiān)聽多個(gè)fd(描述符),比如socket,一旦某個(gè)fd就緒(讀就緒或?qū)懢途w),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。epoll相對(duì)于select和poll有了很大的改進(jìn),首先epoll通過epoll_ctl函數(shù)注冊(cè),注冊(cè)時(shí),將所有fd拷貝進(jìn)內(nèi)核,只拷貝一次不需要重復(fù)拷貝,而每次調(diào)用poll或select時(shí),都需要將fd集合從用戶空間拷貝到內(nèi)核空間(epoll通過epoll_wait進(jìn)行等待);其次,epoll為每個(gè)描述符指定了一個(gè)回調(diào)函數(shù),當(dāng)設(shè)備就緒時(shí),喚醒等待者,通過回調(diào)函數(shù)將描述符加入到就緒鏈表,無需像select,poll方式采用輪詢方式;最后select默認(rèn)只支持1024個(gè)fd,epoll則沒有限制,具體數(shù)字可以參考cat /proc/sys/fs/file-max的設(shè)置。epoll貫穿在線程池使用的過程中,下面我就epoll的創(chuàng)建,使用和銷毀生命周期來描述epoll在線程中是如何使用的。
線程池初始化,epoll通過epoll_create函數(shù)創(chuàng)建epoll文件描述符,實(shí)現(xiàn)函數(shù)是thread_group_init;
端口監(jiān)聽線程監(jiān)聽到請(qǐng)求后,創(chuàng)建socket,并創(chuàng)建THD和connection對(duì)象,放在對(duì)應(yīng)的group隊(duì)列中;
工作線程獲取該connection對(duì)象時(shí),若還未登錄,則進(jìn)行登錄驗(yàn)證
若socket還未注冊(cè)到epoll,則調(diào)用epoll_ctl進(jìn)行注冊(cè),注冊(cè)方式是EPOLL_CTL_ADD,并將connection對(duì)象放入epoll_event結(jié)構(gòu)體中
若是老連接的請(qǐng)求,仍然需要調(diào)用epoll_ctl注冊(cè),注冊(cè)方式是EPOLL_CTL_MOD
group內(nèi)的監(jiān)聽線程調(diào)用epoll_wait來監(jiān)聽注冊(cè)的fd,epoll是一種同步IO方式,所以會(huì)進(jìn)行等待
請(qǐng)求到來時(shí),獲取epoll_event結(jié)構(gòu)體中的connection,放入到group中的隊(duì)列
線程池銷毀時(shí),調(diào)用thread_group_close將epoll關(guān)閉。
備注:
1.注冊(cè)在epoll的fd,若請(qǐng)求就緒,則將對(duì)應(yīng)的event放入到events數(shù)組,并將該fd的事務(wù)類型清空,因此對(duì)于老的連接請(qǐng)求,依然需要調(diào)用epoll_ctl(pollfd, EPOLL_CTL_MOD, fd, ev)來注冊(cè)。
線程池函數(shù)調(diào)用關(guān)系
(1)創(chuàng)建epoll
tp_init-thread_group_init-tp_set_threadpool_size-io_poll_create-epoll_create
(2)關(guān)閉epoll
tp_end-thread_group_close-thread_group_destroy-close(pollfd)
(3)關(guān)聯(lián)socket描述符
handle_event-start_io-io_poll_associate_fd-io_poll_start_read-epoll_ctl
(4)處理連接請(qǐng)求
handle_event-threadpool_process_request-do_command-dispatch_command-mysql_parse-mysql_execute_command
(5)工作線程空閑時(shí)
worker_main-get_event-pthread_cond_timedwait
等待thread_pool_idle_timeout后,退出。
(6)監(jiān)聽epoll
worker_main-get_event-listener-io_poll_wait-epoll_wait
(7)端口監(jiān)聽線程
main-mysqld_main-handle_connections_sockets-poll
one-connection-per-thread函數(shù)調(diào)用關(guān)系
(1) 工作線程等待請(qǐng)求
handle_one_connection-do_handle_one_connection-do_command-
my_net_read-net_read_packet-net_read_packet_header-net_read_raw_loop-
vio_read-vio_socket_io_wait-vio_io_wait-poll
備注:與線程池的工作線程有監(jiān)聽線程幫助其監(jiān)聽請(qǐng)求不同,one-connection-per-thread方式的工作線程在空閑時(shí),會(huì)調(diào)用poll阻塞等待網(wǎng)絡(luò)包過來;
而線程池的工作線程只需要專心處理請(qǐng)求即可,所以使用也更充分。
(2)端口監(jiān)聽線程
與線程池的(7)相同
參考文檔
線程池緩存大小
( 當(dāng)客戶端斷開連接后 將當(dāng)前線程緩存起來 當(dāng)在接到新的連接請(qǐng)求時(shí)快速響應(yīng) 無需創(chuàng)建新的線程 )
查看 thread_cache_size
show global variables like 'thread_cache_size';
設(shè)置 thread_cache_size
set global thread_cache_size = 20; (立即生效重啟后失效)
MySQL 配置文件 my.cnf 中 mysqld 下添加 thread_cache_size
[mysqld]
thread_cache_size = 20
可以通過如下幾個(gè)MySQL狀態(tài)值來適當(dāng)調(diào)整線程池的大小
可以通過 show global status like 'Threads_%'; 命令查看以上4個(gè)狀態(tài)值
當(dāng) Threads_cached 越來越少 但 Threads_connected 始終不降 且 Threads_created 持續(xù)升高
這時(shí)可適當(dāng)增加 thread_cache_size 的大小
MySQL 5.7 參考手冊(cè) - thread_cache_size