MySQL源碼學(xué)習(xí)之Connection Manager詳解
MySQL數(shù)據(jù)庫Connection Manager的相關(guān)知識是本文我們主要要介紹到內(nèi)容,接下來就讓我們一起來了解一下吧。
1.連接的線程數(shù)
MySQL支持單線程和多線程兩種連接線程數(shù)。如果是單線程,則在同一時刻,只能有一個connection連接到MySQL,其他的連接會被掛起。如果是多線程,則同一時刻可以支持多個connection同時連接到服務(wù)器??梢酝ㄟ^設(shè)置服務(wù)器的啟動參數(shù)來設(shè)定連接的線程數(shù):
- mysqld.exe --thread-handling=no-threads
- mysqld.exe --thread-handling=one-thread-per-connection
服務(wù)器如何通過參數(shù)來選擇使用哪種方式的呢?且看服務(wù)器中的分支代碼:
- #ifdef EMBEDDED_LIBRARY
- one_thread_scheduler(&thread_scheduler);
- #else
- if (global_system_variables.thread_handling <=
- SCHEDULER_ONE_THREAD_PER_CONNECTION)
- one_thread_per_connection_scheduler(&thread_scheduler);
- else if (global_system_variables.thread_handling == SCHEDULER_NO_THREADS)
- one_thread_scheduler(&thread_scheduler);
- else
- pool_of_threads_scheduler(&thread_scheduler); /* purecov: tested */
- #endif
這段代碼出現(xiàn)在get_options函數(shù)中,此函數(shù)是根據(jù)傳入的服務(wù)器參數(shù),設(shè)置相應(yīng)參數(shù)的模式。由這段代碼可以看出,如果定義了EMBEDDED_LIBRARY宏定義(估計應(yīng)該是嵌入式使用),則調(diào)用one_thread_scheduler,即使用單線程。如果參數(shù)小于等于SCHEDULER_ONE_THREAD_PER_CONNECTION,則調(diào)用one_thread_per_connection_scheduler,即每個連接一個線程,即多線程。
至于global_system_variables.thread_handling是如何進行設(shè)置的呢?其實就是根據(jù)我們傳遞給服務(wù)器的參數(shù)--thread-handling進行設(shè)置的,參數(shù)的設(shè)置統(tǒng)一在函數(shù)get_options中,其調(diào)用mysqld_get_one_option,其中有個分支,代碼如下:
- case OPT_THREAD_HANDLING:
- {
- global_system_variables.thread_handling=
- find_type_or_exit(argument, &thread_handling_typelib, opt->name)-1;
- break;
- }
對參數(shù)初始化有興趣的可以具體的看下get_options這個函數(shù),這里就不詳細講解了。 我們來看下one_thread_scheduler和one_thread_per_connection_scheduler的源代碼,看下他們都做了些什么?
- void one_thread_scheduler(scheduler_functions* func)
- {
- func->max_threads= 1;
- #ifndef EMBEDDED_LIBRARY
- func->add_connection= handle_connection_in_main_thread;
- #endif
- func->init_new_connection_thread= init_dummy;
- func->end_thread= no_threads_end;
- }
- void one_thread_per_connection_scheduler(scheduler_functions* func)
- {
- func->max_threads= max_connections;
- func->add_connection= create_thread_to_handle_connection;
- func->end_thread= one_thread_per_connection_end;
- }
原來就是設(shè)置了一個結(jié)構(gòu)體中scheduler_functions的參數(shù),只不過這些參數(shù)是一些函數(shù)指針罷了,也就是說在具體的調(diào)用中,
只需要調(diào)用add_connection或end_thread即可,不需要知道到底是調(diào)用了哪個函數(shù),這大概就是一種變形的多態(tài)性吧。
2.初始化網(wǎng)絡(luò)配置
網(wǎng)絡(luò)配置比較簡單,就是設(shè)置端口,創(chuàng)建套接字,綁定端口,監(jiān)聽端口。實現(xiàn)全部集中在network_init函數(shù)中,由于這個函數(shù)確
實沒什么好講的,如果對socket不熟悉的話,可以去網(wǎng)上搜索下相關(guān)知識。這里直接給出相應(yīng)的偽代碼:
- network_init
- {
- set_ports; //設(shè)置端口號,#define MYSQL_PORT 3306
- socket; //創(chuàng)建套接字
- bind; //綁定端口號
- listen; //監(jiān)聽端口號
- }
3.連接的方式
進程間通信的方式不止是SOCKET,還有其他很多方式。Mysql支持三種連接方式:namepipe、socket和shared memory,即命名管道、套接字和共享內(nèi)存的方式。這三種方式是可以共存的。默認只使用套接字。
TCP/IP套接字方式是MySQL在任何平臺下都提供的連接方式,也是網(wǎng)絡(luò)中使用得最多的一種方式。這種方式在TCP/IP連接上建立一個基于網(wǎng)絡(luò)的連接請求,一般情況下客戶端在一臺服務(wù)器上,而MySQL實例在另一臺服務(wù)器上,這兩臺機器通過一個TCP/IP網(wǎng)絡(luò)連接。
例如,我可以在Windows服務(wù)器下請求一臺遠程Linux服務(wù)器下的MySQL實例。在Windows 2000、Windows XP、Windows 2003和Windows Vista以及在此之后的 Windows操作系統(tǒng)中,如果兩個需要通信的進程在同一臺服務(wù)器上,那么可以使用命名管道,SQL Server數(shù)據(jù)庫默認安裝后的本地連接也使用命名管道。在MySQL數(shù)據(jù)庫中,需在配置文件中啟用--enable-named-pipe選項。
在MySQL 4.1之后的版本中,MySQL還提供了共享內(nèi)存的連接方式,在配置文件中添加--shared-memory。如果想使用共享內(nèi)存的方式,在連接時,Mysql客戶端還必須使用-protocol=memory選項。啟動時可以通過下面的參數(shù)進行設(shè)置。
- mysqld.exe --enable-named-pipe
- mysqld.exe --shared-memory
除了在啟動時進行參數(shù)設(shè)置外,也可以通過修改MY.INI文件進行設(shè)置。我們來看下源碼中選擇連接方式的分支函數(shù)handle_connections_methods:
- handle_connections_methods()
- {
- if (hPipe != INVALID_HANDLE_VALUE)
- {
- handler_count++;
- if (pthread_create(&hThread,&connection_attrib,
- handle_connections_namedpipes, 0))
- {
- sql_print_warning("Can't create thread to handle named pipes");
- handler_count--;
- }
- }
- if (have_tcpip && !opt_disable_networking)
- {
- handler_count++;
- if (pthread_create(&hThread,&connection_attrib,
- handle_connections_sockets, 0))
- {
- sql_print_warning("Can't create thread to handle TCP/IP");
- handler_count--;
- }
- }
- if (opt_enable_shared_memory)
- {
- handler_count++;
- if (pthread_create(&hThread,&connection_attrib,
- handle_connections_shared_memory, 0))
- {
- sql_print_warning("Can't create thread to handle shared memory");
- handler_count--;
- }
- }
- }
由于對于namepipe和memory share的通信方式不太了解,這里只研究socket的通信方式。從代碼中可以看出,handle_connections_sockets便是socket的設(shè)置,我們就來看下它。
4.socket管理創(chuàng)建新線程
socket管理其實比較簡單,直接給出其偽代碼:
- handle_connections_sockets
- {
- select; //監(jiān)視socket文件描述符
- new_socket = accept;//處理到來的客戶端連接
- thd = new THD;創(chuàng)建THD類
- vio_tmp = vio_new(new_socket,VIO_TYPE_TCPIP, 0); //初始化VIO結(jié)構(gòu)體
- my_net_init(&thd->net, vio_tmp);//初始化thd的net結(jié)構(gòu)體
- create_new_thread(thd);//為這個連接創(chuàng)建一個新的線程,如果是單線程模式的話,就不會創(chuàng)建一個新線程
- }
首先是select函數(shù)進行監(jiān)視socket端口,如果監(jiān)控到有連接,則通過accept函數(shù)接受客戶端的連接,然后新建一個THD類,將連接參數(shù)全部設(shè)置到THD類的參數(shù)上,最后調(diào)用create_new_thread函數(shù),這個函數(shù)便是重點。 我們進入這個函數(shù),看下做了啥。
- create_new_thread
- {
- ++connection_count;//全局連接數(shù)自增
- thread_count++; //全局線程數(shù)自增
- thread_scheduler.add_connection(thd);//真正創(chuàng)建線程
- }
So easy,首先將全局連接數(shù)+1,全局線程數(shù)+1,然后調(diào)用add_connection函數(shù),這個函數(shù)就是我們在上面第一步設(shè)置連接的
線程數(shù)中,one_thread_scheduler和one_thread_per_connection_scheduler中設(shè)置的一個參數(shù)。這兩者的區(qū)別便是是否創(chuàng)建了
一個新的線程來處理到來的連接。one_thread_scheduler是單線程方式,木有新建線程。我們重點研究one_thread_per_connection_scheduler,其設(shè)置的add_connection函數(shù)為create_thread_to_handle_connection:
- create_thread_to_handle_connection(THD *thd)
- {
- thread_created++;
- threads.append(thd); // 創(chuàng)建線程數(shù)自增,并加入到threads鏈表上
- pthread_create(&thd->real_id,&connection_attrib,
- handle_one_connection,
- (void*) thd);//這就是真正創(chuàng)建線程的地方了,函數(shù)便是handle_one_connection
- }
可見,最后調(diào)用了pthread_create函數(shù),這個函數(shù)便是創(chuàng)建一個新的線程,新線程的處理函數(shù)為handle_one_connection.
5.新線程處理流程
新線程處理函數(shù)為handle_one_connection,到此位置,一個新的connection被一個新創(chuàng)建的線程所單獨處理。我們看下其中
是如何進行處理的。
- handle_one_connection(void *arg)
- {
- for (;;)
- {
- lex_start(thd); //初始化詞法分析結(jié)構(gòu)體
- login_connection(thd); //用戶認證,失敗報錯
- prepare_new_connection_state(THD* thd);//Initialize THD to handle queries
- while (!net->error && net->vio != 0 && //循環(huán)處理command
- !(thd->killed == THD::KILL_CONNECTION))
- {
- if (do_command(thd))
- break; //處理失敗跳出
- }
- end_connection(thd); //關(guān)閉連接
- close_connection(thd, 0, 1);
- thread_scheduler.end_thread(thd,1);//結(jié)束線程
- return 0;
- }
- }
首先進行了詞法分析結(jié)構(gòu)體的初始化,然后進行用戶認證,認證成功后通過do_command循環(huán)執(zhí)行客戶端發(fā)過來的命令。
6.總結(jié)
整個connection manager的流程十分清晰,單線程的連接一般很少使用,大多使用多線程方式。多線程連接中其實還涉及到線程緩沖池的概念,即如果一個連接斷開后,其所創(chuàng)建的線程不會被銷毀掉,而是放到緩沖池中,等待下一個新的connection到來時,首先去線程緩沖池查找是否有空閑的線程,有的話直接使用,木有的話才去創(chuàng)建新的線程來管理這個connection。
關(guān)于MySQL數(shù)據(jù)庫Connection Manager的相關(guān)知識就介紹到這里了,希望本次的介紹能夠?qū)δ兴斋@!
【編輯推薦】






