openGauss數(shù)據(jù)庫(kù)自2020年6月30日開(kāi)源以來(lái),吸引了眾多內(nèi)核開(kāi)發(fā)者的關(guān)注。那么openGauss的多線程是如何啟動(dòng)的,一條SQL語(yǔ)句在 SQL引擎,執(zhí)行引擎和存儲(chǔ)引擎的執(zhí)行過(guò)程是怎樣的窑多,酷哥做了一些總結(jié),第一期內(nèi)容主要分析openGauss 多線程架構(gòu)啟動(dòng)過(guò)程鼻疮。
openGauss數(shù)據(jù)庫(kù)是一個(gè)單進(jìn)程多線程的數(shù)據(jù)庫(kù)怯伊,客戶端可以使用JDBC/ODBC/Libpq/Psycopg等驅(qū)動(dòng)程序,向openGauss的主線程(Postmaster)發(fā)起連接請(qǐng)求判沟。
openGauss為什么要使用多線程架構(gòu)
隨著計(jì)算機(jī)領(lǐng)域多核技術(shù)的發(fā)展耿芹,如何充分有效的利用多核的并行處理能力,是每個(gè)服務(wù)器端應(yīng)用程序都必須考慮的問(wèn)題挪哄。由于數(shù)據(jù)庫(kù)服務(wù)器的服務(wù)進(jìn)程或線程間存在著大量數(shù)據(jù)共享和同步吧秕,而多線程可以充分利用多CPU來(lái)并行執(zhí)行多個(gè)強(qiáng)相關(guān)任務(wù),例如執(zhí)行引擎可以充分的利用線程的并發(fā)執(zhí)行以提供性能迹炼。在多線程的架構(gòu)下砸彬,數(shù)據(jù)共享的效率更高,能提高服務(wù)器訪問(wèn)的效率和性能斯入,同時(shí)維護(hù)開(kāi)銷和復(fù)雜度更低砂碉,這對(duì)于提高數(shù)據(jù)庫(kù)系統(tǒng)的并行處理能力非常重要。
多線程的三大主要優(yōu)勢(shì):
優(yōu)勢(shì)一:線程啟動(dòng)開(kāi)銷遠(yuǎn)小于進(jìn)程啟動(dòng)開(kāi)銷刻两。與進(jìn)程相比增蹭,它是一種非常“節(jié)儉”的多任務(wù)操作方式磅摹。在Linux系統(tǒng)下滋迈,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間霎奢,建立眾多的數(shù)據(jù)表來(lái)維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段饼灿,這是一種“昂貴”的多任務(wù)工作方式幕侠。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址空間碍彭,共享大部分?jǐn)?shù)據(jù)晤硕,啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間。
優(yōu)勢(shì)二:線程間方便的通信機(jī)制:對(duì)不同進(jìn)程來(lái)說(shuō)硕旗,它們具有獨(dú)立的數(shù)據(jù)空間窗骑,要進(jìn)行數(shù)據(jù)的傳遞只能通過(guò)通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí)漆枚,而且很不方便。線程則不然抵知,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間墙基,所以一個(gè)線程的數(shù)據(jù)可以直接為其他線程所用,這不僅快捷刷喜,而且方便残制。
優(yōu)勢(shì)三:線程切換開(kāi)銷小于進(jìn)程切換開(kāi)銷,對(duì)于Linux系統(tǒng)來(lái)講掖疮,進(jìn)程切換分兩步:1.切換頁(yè)目錄以使用新的地址空間初茶;2.切換內(nèi)核棧和硬件上下文。對(duì)線程切換浊闪,第1步是不需要做的恼布,第2步是進(jìn)程和線程都要做的,所以明顯線程切換開(kāi)銷小搁宾。
openGauss主要線程有哪些
后臺(tái)線程功能介紹
Postmaster
主線程
入口函數(shù)PostmasterMain折汞,主要負(fù)責(zé)內(nèi)存、全局信息盖腿、信號(hào)爽待、線程池等的初始化,啟動(dòng)輔助線程并監(jiān)控線程狀態(tài)翩腐,循環(huán)**接收新的連接
Walwriter
日志寫線程
入口函數(shù)WalWriterMain鸟款,將內(nèi)存的預(yù)寫日志頁(yè)數(shù)據(jù)刷新到預(yù)寫日志文件中,保證已提交的事物永久記錄茂卦,不會(huì)丟失
Startup
數(shù)據(jù)庫(kù)啟動(dòng)線程
入口函數(shù)StartupProcessMain何什,數(shù)據(jù)庫(kù)啟動(dòng)時(shí)Postmaster主線程拉起的第一個(gè)子線程,主要完成數(shù)據(jù)庫(kù)的日志REDO(重做)操作疙筹,進(jìn)行數(shù)據(jù)庫(kù)的恢復(fù)富俄。日志REDO操作結(jié)束禁炒,數(shù)據(jù)庫(kù)完成恢復(fù)后,如果不是備機(jī)霍比,Startup線程就退出了幕袱。如果是備機(jī),那么Startup線程一直在運(yùn)行悠瞬,REDO備機(jī)接收到新的日志
Bgwriter
后臺(tái)數(shù)據(jù)寫線程
入口函數(shù)BackgroundWriterMain们豌,對(duì)共享緩沖區(qū)的臟頁(yè)數(shù)據(jù)進(jìn)行下盤
PageWriter入口函數(shù)ckpt_pagewriter_main,將臟頁(yè)數(shù)據(jù)拷貝至雙寫區(qū)域并落盤
Checkpointer
檢查點(diǎn)線程
入口函數(shù)CheckpointerMain浅妆,周期性檢查點(diǎn)望迎,所有數(shù)據(jù)文件被更新,將數(shù)據(jù)臟頁(yè)刷新到磁盤凌外,確保數(shù)據(jù)庫(kù)一致辩尊;崩潰回復(fù)后,做過(guò)checkpointer更改不需要從預(yù)寫日志中恢復(fù)
StatCollector
統(tǒng)計(jì)線程
入口函數(shù)PgstatCollectorMain康辑,統(tǒng)計(jì)信息摄欲,包括對(duì)象、sql疮薇、會(huì)話胸墙、鎖等,保存到pgstat.stat文件中按咒,用于性能迟隅、故障、狀態(tài)分析
WalSender
日志發(fā)送線程
入口函數(shù)WalSenderMain励七,主機(jī)發(fā)送預(yù)寫日志
WalReceiver
日志接收線程
入口函數(shù)WalReceiverMain智袭,備機(jī)接收預(yù)寫日志
Postgres
業(yè)務(wù)處理線程
入口函數(shù)PostgresMain:處理客戶端連接請(qǐng)求,執(zhí)行相關(guān)SQL業(yè)務(wù)
數(shù)據(jù)庫(kù)啟動(dòng)后呀伙,可以通過(guò)操作系統(tǒng)命令ps查看線程信息(進(jìn)程號(hào)為17012)
openGauss啟動(dòng)過(guò)程
下面主要介紹openGauss數(shù)據(jù)庫(kù)的啟動(dòng)過(guò)程补履,包括主線程,輔助線程及業(yè)務(wù)處理線程的啟動(dòng)過(guò)程
gs_ctl啟動(dòng)數(shù)據(jù)庫(kù)
gs_ctl是openGauss提供的數(shù)據(jù)庫(kù)服務(wù)控制工具剿另,可以用來(lái)啟停數(shù)據(jù)庫(kù)服務(wù)和查詢數(shù)據(jù)庫(kù)狀態(tài)箫锤。主要供數(shù)據(jù)庫(kù)管理模塊調(diào)用,啟動(dòng)數(shù)據(jù)庫(kù)使用如下命令
gs_ctlstart-D /opt/software/data-Z single_node
gs_ctl的入口函數(shù)在“src/bin/pg_ctl/pg_ctl.cpp”雨女,gs_ctl進(jìn)程fork一個(gè)進(jìn)程來(lái)運(yùn)行 gaussdb進(jìn)程谚攒,通過(guò)shell命令啟動(dòng)。
上圖中的cmd為“/opt/software/openGauss/bin/gaussdb -D /opt/software/openGauss/data”氛堕,進(jìn)入到數(shù)據(jù)庫(kù)運(yùn)行調(diào)用的第一個(gè)函數(shù)是main函數(shù)馏臭,在“src/gausskernel/process/main/main.cpp”文件中,在main.cpp文件中,主要完成實(shí)例Context(上下文)的初始化括儒、本地化設(shè)置绕沈,根據(jù)main.cpp文件的入口參數(shù)調(diào)用BootStrapProcessMain函數(shù)、GucInfoMain函數(shù)帮寻、PostgresMain函數(shù)和PostmasterMain函數(shù)乍狐。BootStrapProcessMain函數(shù)和PostgresMain函數(shù)是在initdb場(chǎng)景下初始化數(shù)據(jù)庫(kù)使用的。GucInfoMain函數(shù)作用是顯示GUC(grand unified configuration固逗,配置參數(shù)浅蚪,在數(shù)據(jù)庫(kù)中指的是運(yùn)行參數(shù))參數(shù)信息。正常的數(shù)據(jù)庫(kù)啟動(dòng)會(huì)進(jìn)入PostmasterMain函數(shù)烫罩。下面對(duì)這個(gè)函數(shù)進(jìn)行更詳細(xì)的介紹惜傲。
MemoryContextInit:內(nèi)存上下文系統(tǒng)初始化,主要完成對(duì)ThreadTopMemoryContext贝攒,ErrorContext盗誊,AlignContext和ProfileLogging等全局變量的初始化
pg_perm_setlocale:設(shè)置程序語(yǔ)言環(huán)境相關(guān)的全局變量
check_root:?確認(rèn)程序運(yùn)行者無(wú)操作系統(tǒng)的root權(quán)限,防止的意外文件覆蓋等問(wèn)題
如果gaussdb后的第一個(gè)參數(shù)是—boot,則進(jìn)行數(shù)據(jù)庫(kù)初始化饿这,如果gaussdb后的第一個(gè)參數(shù)是--single浊伙,則調(diào)用PostgresMain(),進(jìn)入(本地)單用戶版服務(wù)端程序长捧。之后,與普通服務(wù)器端線程類似吻贿,循環(huán)等待用戶輸入SQL語(yǔ)句串结,直至用戶輸入EOF(Ctrl+D),退出程序舅列。如果沒(méi)有指定額外啟動(dòng)選項(xiàng)肌割,程序進(jìn)入PostmasterMain函數(shù),開(kāi)始一系列服務(wù)器端的正常初始化工作帐要。
PostmasterMain函數(shù)
下面具體介紹PostmasterMain把敞。
設(shè)置線程號(hào)相關(guān)的全局變量MyProcPid、PostmasterPid榨惠、MyProgName和程序運(yùn)行環(huán)境相關(guān)的全局變量IsPostmasterEnvironment
調(diào)用postmaster_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt,...)奋早,在目前線程的top_mem_cxt下創(chuàng)建postmaster_mem_cxt全局變量和相應(yīng)的內(nèi)存上下文
MemoryContextSwitchTo(postmaster_mem_cxt)切換到postmaster_mem_cxt內(nèi)存上下文
調(diào)用getInstallationPaths(),設(shè)置my_exec_path(一般即為gaussdb可執(zhí)行文件所在路徑)
調(diào)用InitializeGUCOptions()赠橙,根據(jù)代碼中各個(gè)GUC參數(shù)的默認(rèn)值生成ConfigureNamesBool耽装、ConfigureNamesInt、ConfigureNamesReal期揪、ConfigureNamesString掉奄、ConfigureNamesEnum等?GUC參數(shù)的全局變量數(shù)組,以及統(tǒng)一管理GUC參數(shù)的guc_variables凤薛、num_guc_variables姓建、size_guc_variables全局變量诞仓,并設(shè)置與具體操作系統(tǒng)環(huán)境相關(guān)的GUC參數(shù)
while (opt = ...)?SetConfigOption,?若在啟動(dòng)gaussdb時(shí)用指定了非默認(rèn)的GUC參數(shù),則在此時(shí)加載至上一步中創(chuàng)建的全局變量中
調(diào)用checkDataDir()速兔,確認(rèn)數(shù)據(jù)庫(kù)安裝成功以及PGDATA目錄的有效性
調(diào)用CreateDataDirLockFile()墅拭,創(chuàng)建數(shù)據(jù)目錄的鎖文件
調(diào)用process_shared_preload_libraries(),處理預(yù)加載庫(kù)
為每個(gè)ListenSocket創(chuàng)建**
reset_shared憨栽,設(shè)置共享內(nèi)存和信號(hào)帜矾,主要包括頁(yè)面緩存池、各種鎖緩存池屑柔、WAL日志緩存池屡萤、事務(wù)日志緩存池、事務(wù)(號(hào))概況緩存池掸宛、各后臺(tái)線程(鎖使用)概況緩存池死陆、各后臺(tái)線程等待和運(yùn)行狀態(tài)緩存池、兩階段狀態(tài)緩存池唧瘾、檢查點(diǎn)緩存池措译、WAL日志復(fù)制和接收緩存池、數(shù)據(jù)頁(yè)復(fù)制和接收緩存池等饰序。在后續(xù)階段創(chuàng)建出的客戶端后臺(tái)線程以及各個(gè)輔助線程均使用該共享內(nèi)存空間领虹,不再單獨(dú)開(kāi)辟
將啟動(dòng)時(shí)手動(dòng)設(shè)置的GUC參數(shù)以文件形式保存下來(lái),以供后續(xù)后臺(tái)服務(wù)端線程啟動(dòng)時(shí)使用
為不同信號(hào)設(shè)置handler
調(diào)用pgstat_init()求豫,初始化狀態(tài)收集子系統(tǒng)塌衰;
調(diào)用load_hba(),加載pg_hba.conf文件蝠嘉,該文件記錄了允許連接(指定或全部)數(shù)據(jù)庫(kù)的客戶端物理機(jī)的地址和端口最疆;調(diào)用load_ident(),加載pg_ident.conf文件蚤告,該文件記錄了操作系統(tǒng)用戶名與數(shù)據(jù)庫(kù)系統(tǒng)用戶名的對(duì)應(yīng)關(guān)系努酸,以便后續(xù)處理客戶端連接時(shí)的身份認(rèn)證
調(diào)用?StartupPID = initialize_util_thread(STARTUP),進(jìn)行數(shù)據(jù)一致性校驗(yàn)杜恰。對(duì)于服務(wù)端主機(jī)來(lái)說(shuō)获诈,查看pg_control文件,若上次關(guān)閉狀態(tài)為DB_SHUTDOWNED且recovery.conf文件沒(méi)有指定進(jìn)行恢復(fù)箫章,則認(rèn)為數(shù)據(jù)一致性成立烙荷;否則,根據(jù)pg_control中檢查點(diǎn)的redo位置或者recovery.conf文件中指定的位置檬寂,讀取WAL日志或歸檔日志進(jìn)行replay(回放)终抽,直至數(shù)據(jù)達(dá)到預(yù)期的一致性狀,主要函數(shù)StartupXLOG
最后進(jìn)入ServerLoop()函數(shù),循環(huán)響應(yīng)客戶端連接請(qǐng)求
ServerLoop函數(shù)
下面來(lái)講ServerLoop函數(shù)主流程
調(diào)用gs_signal_setmask(&UnBlockSig, NULL)和gs_signal_unblock_sigusr2()昼伴,使得線程可以響應(yīng)用戶或其它線程的匾旭、指定的信號(hào)集
每隔PM_POLL_TIMEOUT_MINUTE時(shí)間修改一次socket文件和socket鎖文件的訪問(wèn)和修改時(shí)間,以免**作系統(tǒng)淘汰
判斷線程狀態(tài)(pmState)圃郊,若為PM_WAIT_DEAD_END价涝,則休眠100毫秒,并且不接收任何連接持舆;否則色瘩,通過(guò)系統(tǒng)調(diào)用poll()或select()來(lái)阻塞地讀取**端口上傳入的數(shù)據(jù),最長(zhǎng)阻塞時(shí)間PM_POLL_TIMEOUT_SECOND
調(diào)用gs_signal_setmask(&BlockSig, NULL)和gs_signal_block_sigusr2()不再接收外源信號(hào)
判斷poll()或select()函數(shù)的返回值逸寓,若小于零居兆,**出錯(cuò),服務(wù)端進(jìn)程退出竹伸;若大于零泥栖,則創(chuàng)建連接ConnCreate(),并進(jìn)入后臺(tái)服務(wù)線程啟動(dòng)流程BackendStartup()勋篓。對(duì)于父線程吧享,即postmaster線程,在結(jié)束B(niǎo)ackendStartup()的調(diào)用以后譬嚣,會(huì)調(diào)用ConnFree()钢颂,清除連接信息;若poll()或select()的返回值為零拜银,即沒(méi)有信息傳入甸陌,則不進(jìn)行任何操作
調(diào)用ADIO_RUN()、ADIO_END()?盐股,若AioCompleters沒(méi)有啟動(dòng),則啟動(dòng)之
檢查各個(gè)輔助線程的線程號(hào)是否為零耻卡,若為零疯汁,則調(diào)用initialize_util_thread啟動(dòng)
以非線程池模式為例,介紹線程的啟動(dòng)邏輯卵酪。BackendStartup函數(shù)是通過(guò)調(diào)用initialize_worker_thread(WORKE幌蚊,port)創(chuàng)建一個(gè)后臺(tái)線程處理客戶請(qǐng)求。后臺(tái)線程的啟動(dòng)函數(shù)initialize_util_thread和工作線程的啟動(dòng)函數(shù)initialize_worker_thread溃卡,最后都是調(diào)用initialize_thread函數(shù)完成線程的啟動(dòng)溢豆。
initialize_thread函數(shù)調(diào)用gs_thread_create函數(shù)創(chuàng)建線程,調(diào)用InternalThreadFunc函數(shù)處理線程
ThreadIdinitialize_thread(ThreadArg* thr_argv){gs_thread_tthread;interror_code = gs_thread_create(&thread, InternalThreadFunc,1, (void*)thr_argv);if(error_code !=0) {ereport(LOG,(errmsg("can not fork thread[%s], errcode:%d, %m",GetThreadName(thr_argv->m_thd_arg.role), error_code)));gs_thread_release_args_slot(thr_argv);returnInvalidTid;}returngs_thread_id(thread);}
InternalThreadFunc函數(shù)根據(jù)角色調(diào)用GetThreadEntry函數(shù)瘸羡,GetThreadEntry函數(shù)直接以角色為下標(biāo)漩仙,返回對(duì)應(yīng)GaussdbThreadEntryGate數(shù)組對(duì)應(yīng)的元素。數(shù)組的元素是處理具體任務(wù)的回調(diào)函數(shù)指針,指針指向的函數(shù)為GaussDbThreadMain
staticvoid*InternalThreadFunc(void* args){knl_thread_arg* thr_argv = (knl_thread_arg*)args;gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv));return(void*)NULL;}GaussdbThreadEntryGetThreadEntry(knl_thread_role role){Assert(role > MASTER && role < THREAD_ENTRY_BOUND);returnGaussdbThreadEntryGate[role];}staticGaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain,GaussDbThreadMain,GaussDbThreadMain,GaussDbThreadMain,......};
在GaussDbThreadMain函數(shù)中队他,首先初始化線程基本信息卷仑,Context和信號(hào)處理函數(shù),接著就是根據(jù)thread_role角色的不同調(diào)用不同角色的處理函數(shù)麸折,進(jìn)入各個(gè)線程的main函數(shù)锡凝,角色為WORKER會(huì)進(jìn)入PostgresMain函數(shù),下面具體介紹PostgresMain函數(shù)
PostgresMain函數(shù)
process_postgres_switches(),加載傳入的啟動(dòng)選項(xiàng)和GUC參數(shù)
為不同信號(hào)設(shè)置handler
調(diào)用sigdelset(&BlockSig, SIGQUIT)垢啼,允許響應(yīng)SIGQUIT信號(hào)
調(diào)用BaseInit()窜锯,初始化存儲(chǔ)管理系統(tǒng)和頁(yè)面緩存池計(jì)數(shù)
調(diào)用on_shmem_exit(),設(shè)置線程退出前需要進(jìn)行的內(nèi)存清理動(dòng)作芭析。這些清理動(dòng)作構(gòu)成一個(gè)鏈表(on_shmem_exit_list全局變量)锚扎,每次調(diào)用該函數(shù)都向鏈表尾端添加一個(gè)節(jié)點(diǎn),鏈表長(zhǎng)度由on_shmem_exit_index記錄放刨,且不可超過(guò)MAX_ON_EXITS宏工秩。在線程退出時(shí),從后往前調(diào)用各個(gè)節(jié)點(diǎn)中的動(dòng)作(函數(shù)指針)进统,完成清理工作
調(diào)用gs_signal_setmask (&UnBlockSig)助币,設(shè)置屏蔽的信號(hào)類型
調(diào)用InitBackendWorker進(jìn)行統(tǒng)計(jì)系統(tǒng)初始化、syscache初始化工作
BeginReportingGUCOptions如有需要?jiǎng)t打印GUC參數(shù)
調(diào)用on_proc_exit()螟碎,設(shè)置線程退出前需要進(jìn)行的線程清理動(dòng)作眉菱。設(shè)置和調(diào)用機(jī)制與on_shmem_exit()類似
調(diào)用process_local_preload_libraries(),處理GUC參數(shù)設(shè)定后的預(yù)加載庫(kù)
AllocSetContextCreate創(chuàng)建MessageContext掉分、RowDescriptionContext俭缓、MaskPasswordCtx上下文
調(diào)用sigsetjmp(),設(shè)置longjump點(diǎn)酥郭,若后續(xù)查詢執(zhí)行中出錯(cuò)华坦,在某些情況下可以返回此處重新開(kāi)始
調(diào)用gs_signal_unblock_sigusr2(),允許線程響應(yīng)指定的信號(hào)集
然后進(jìn)入for循環(huán)不从,進(jìn)行查詢執(zhí)行? ? ? ? ? ?
設(shè)置全局變量DoingCommandRead = true
調(diào)用ReadCommand(),讀取客戶端SQL語(yǔ)句
設(shè)置全局變量DoingCommandRead=false
若在上述過(guò)程中收到SIGHUP信號(hào)惜姐,表示線程需要重新加載修改過(guò)的postgresql.conf配置文件
進(jìn)入switch (firstchar),根據(jù)接收到的信息進(jìn)行分支判斷
調(diào)用pgstat_report_activity()椿息、pgstat_report_waitstatus()歹袁,告訴統(tǒng)計(jì)系統(tǒng)后臺(tái)線程正處于idle狀態(tài)
思考如何新增一個(gè)輔助線程
參考其他線程完成
涉及修改文件Postmaster.cpp
涉及修改函數(shù)GaussdbThreadGate – 定義
Serverloop – 啟動(dòng)線程
Reaper – 回收線程
GaussDBThreadMain – 入口函數(shù)