openGauss數(shù)據(jù)庫自2020年6月30日開源以來埃篓,吸引了眾多內(nèi)核開發(fā)者的關(guān)注匕争。那么openGauss的多線程是如何啟動(dòng)的葵擎,一條SQL語句在 SQL引擎胳蛮,執(zhí)行引擎和存儲(chǔ)引擎的執(zhí)行過程是怎樣的右核,酷哥做了一些總結(jié)慧脱,第一期內(nèi)容主要分析openGauss 多線程架構(gòu)啟動(dòng)過程。
openGauss數(shù)據(jù)庫是一個(gè)單進(jìn)程多線程的數(shù)據(jù)庫蒙兰,客戶端可以使用JDBC/ODBC/Libpq/Psycopg等驅(qū)動(dòng)程序磷瘤,向openGauss的主線程(Postmaster)發(fā)起連接請(qǐng)求。
01 openGauss為什么要使用多線程架構(gòu)
隨著計(jì)算機(jī)領(lǐng)域多核技術(shù)的發(fā)展搜变,如何充分有效的利用多核的并行處理能力采缚,是每個(gè)服務(wù)器端應(yīng)用程序都必須考慮的問題。由于數(shù)據(jù)庫服務(wù)器的服務(wù)進(jìn)程或線程間存在著大量數(shù)據(jù)共享和同步挠他,而多線程可以充分利用多CPU來并行執(zhí)行多個(gè)強(qiáng)相關(guān)任務(wù)扳抽,例如執(zhí)行引擎可以充分的利用線程的并發(fā)執(zhí)行以提供性能。在多線程的架構(gòu)下殖侵,數(shù)據(jù)共享的效率更高贸呢,能提高服務(wù)器訪問的效率和性能,同時(shí)維護(hù)開銷和復(fù)雜度更低拢军,這對(duì)于提高數(shù)據(jù)庫系統(tǒng)的并行處理能力非常重要楞陷。
多線程的三大主要優(yōu)勢:
優(yōu)勢一:線程啟動(dòng)開銷遠(yuǎn)小于進(jìn)程啟動(dòng)開銷。與進(jìn)程相比茉唉,它是一種非彻潭辏“節(jié)儉”的多任務(wù)操作方式。在Linux系統(tǒng)下度陆,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間艾凯,建立眾多的數(shù)據(jù)表來維護(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)勢二:線程間方便的通信機(jī)制:對(duì)不同進(jìn)程來說缚忧,它們具有獨(dú)立的數(shù)據(jù)空間悟泵,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí)闪水,而且很不方便糕非。線程則不然蒙具,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其他線程所用朽肥,這不僅快捷禁筏,而且方便。
優(yōu)勢三:線程切換開銷小于進(jìn)程切換開銷衡招,對(duì)于Linux系統(tǒng)來講篱昔,進(jìn)程切換分兩步:1.切換頁目錄以使用新的地址空間;2.切換內(nèi)核棧和硬件上下文始腾。對(duì)線程切換州刽,第1步是不需要做的,第2步是進(jìn)程和線程都要做的浪箭,所以明顯線程切換開銷小穗椅。
02 openGauss主要線程有哪些
數(shù)據(jù)庫啟動(dòng)后,可以通過操作系統(tǒng)命令ps查看線程信息(進(jìn)程號(hào)為17012)
03 openGauss啟動(dòng)過程
下面主要介紹openGauss數(shù)據(jù)庫的啟動(dòng)過程奶栖,包括主線程匹表,輔助線程及業(yè)務(wù)處理線程的啟動(dòng)過程。
gs_ctl啟動(dòng)數(shù)據(jù)庫
gs_ctl是openGauss提供的數(shù)據(jù)庫服務(wù)控制工具宣鄙,可以用來啟停數(shù)據(jù)庫服務(wù)和查詢數(shù)據(jù)庫狀態(tài)袍镀。主要供數(shù)據(jù)庫管理模塊調(diào)用,啟動(dòng)數(shù)據(jù)庫使用如下命令:gs_ctl start -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)程來運(yùn)行 gaussdb進(jìn)程苇羡,通過shell命令啟動(dòng)。
上圖中的cmd為“/opt/software/openGauss/bin/gaussdb -D /opt/software/openGauss/data”鼻弧,進(jìn)入到數(shù)據(jù)庫運(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場景下初始化數(shù)據(jù)庫使用的够傍。GucInfoMain函數(shù)作用是顯示GUC(grand unified configuration,配置參數(shù)挠铲,在數(shù)據(jù)庫中指的是運(yùn)行參數(shù))參數(shù)信息冕屯。正常的數(shù)據(jù)庫啟動(dòng)會(huì)進(jìn)入PostmasterMain函數(shù)。下面對(duì)這個(gè)函數(shù)進(jìn)行更詳細(xì)的介紹拂苹。
1.MemoryContextInit:內(nèi)存上下文系統(tǒng)初始化安聘,主要完成對(duì)ThreadTopMemoryContext,ErrorContext,AlignContext和ProfileLogging等全局變量的初始化浴韭。
2.pg_perm_setlocale:設(shè)置程序語言環(huán)境相關(guān)的全局變量丘喻。
3.check_root: 確認(rèn)程序運(yùn)行者無操作系統(tǒng)的root權(quán)限,防止的意外文件覆蓋等問題念颈。
4.如果gaussdb后的第一個(gè)參數(shù)是—boot,則進(jìn)行數(shù)據(jù)庫初始化泉粉,如果gaussdb后的第一個(gè)參數(shù)是--single,則調(diào)用PostgresMain()榴芳,進(jìn)入(本地)單用戶版服務(wù)端程序嗡靡。之后,與普通服務(wù)器端線程類似窟感,循環(huán)等待用戶輸入SQL語句讨彼,直至用戶輸入EOF(Ctrl+D),退出程序肌括。如果沒有指定額外啟動(dòng)選項(xiàng)点骑,程序進(jìn)入PostmasterMain函數(shù),開始一系列服務(wù)器端的正常初始化工作谍夭。
PostmasterMain 函數(shù)
下面具體介紹PostmasterMain黑滴。
1.設(shè)置線程號(hào)相關(guān)的全局變量MyProcPid、PostmasterPid紧索、MyProgName和程序運(yùn)行環(huán)境相關(guān)的全局變量IsPostmasterEnvironment袁辈。
2.調(diào)用postmaster_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt,...),在目前線程的top_mem_cxt下創(chuàng)建postmaster_mem_cxt全局變量和相應(yīng)的內(nèi)存上下文珠漂。
3. MemoryContextSwitchTo(postmaster_mem_cxt)切換到postmaster_mem_cxt內(nèi)存上下文晚缩。
4.調(diào)用getInstallationPaths(),設(shè)置my_exec_path(一般即為gaussdb可執(zhí)行文件所在路徑)媳危。
5.調(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ù)荆陆。
6. while (opt = ...) SetConfigOption, 若在啟動(dòng)gaussdb時(shí)用指定了非默認(rèn)的GUC參數(shù),則在此時(shí)加載至上一步中創(chuàng)建的全局變量中集侯。
7.調(diào)用checkDataDir()被啼,確認(rèn)數(shù)據(jù)庫安裝成功以及PGDATA目錄的有效性帜消。
8.調(diào)用CreateDataDirLockFile(),創(chuàng)建數(shù)據(jù)目錄的鎖文件趟据。
9.調(diào)用process_shared_preload_libraries()券犁,處理預(yù)加載庫挺举。
10.為每個(gè)ListenSocket創(chuàng)建監(jiān)聽纽绍。
11. reset_shared,設(shè)置共享內(nèi)存和信號(hào)貌笨,主要包括頁面緩存池咳促、各種鎖緩存池稚新、WAL日志緩存池、事務(wù)日志緩存池跪腹、事務(wù)(號(hào))概況緩存池褂删、各后臺(tái)線程(鎖使用)概況緩存池、各后臺(tái)線程等待和運(yùn)行狀態(tài)緩存池冲茸、兩階段狀態(tài)緩存池屯阀、檢查點(diǎn)緩存池、WAL日志復(fù)制和接收緩存池轴术、數(shù)據(jù)頁復(fù)制和接收緩存池等难衰。在后續(xù)階段創(chuàng)建出的客戶端后臺(tái)線程以及各個(gè)輔助線程均使用該共享內(nèi)存空間,不再單獨(dú)開辟逗栽。
12.將啟動(dòng)時(shí)手動(dòng)設(shè)置的GUC參數(shù)以文件形式保存下來盖袭,以供后續(xù)后臺(tái)服務(wù)端線程啟動(dòng)時(shí)使用。
13.為不同信號(hào)設(shè)置handler彼宠。
14.調(diào)用pgstat_init()鳄虱,初始化狀態(tài)收集子系統(tǒng)。
15.調(diào)用load_hba()凭峡,加載pg_hba.conf文件拙已,該文件記錄了允許連接(指定或全部)數(shù)據(jù)庫的客戶端物理機(jī)的地址和端口;調(diào)用load_ident()摧冀,加載pg_ident.conf文件悠栓,該文件記錄了操作系統(tǒng)用戶名與數(shù)據(jù)庫系統(tǒng)用戶名的對(duì)應(yīng)關(guān)系,以便后續(xù)處理客戶端連接時(shí)的身份認(rèn)證按价。
16.調(diào)用 StartupPID = initialize_util_thread(STARTUP),進(jìn)行數(shù)據(jù)一致性校驗(yàn)笙瑟。對(duì)于服務(wù)端主機(jī)來說楼镐,查看pg_control文件,若上次關(guān)閉狀態(tài)為DB_SHUTDOWNED且recovery.conf文件沒有指定進(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。
17. 最后進(jìn)入ServerLoop()函數(shù)描睦,循環(huán)響應(yīng)客戶端連接請(qǐng)求膊存。
ServerLoop函數(shù)
下面來講ServerLoop函數(shù)主流程1.調(diào)用gs_signal_setmask(&UnBlockSig, NULL)和gs_signal_unblock_sigusr2(),使得線程可以響應(yīng)用戶或其它線程的忱叭、指定的信號(hào)集隔崎。
2.每隔PM_POLL_TIMEOUT_MINUTE時(shí)間修改一次socket文件和socket鎖文件的訪問和修改時(shí)間,以免被操作系統(tǒng)淘汰韵丑。
3.判斷線程狀態(tài)(pmState)爵卒,若為PM_WAIT_DEAD_END,則休眠100毫秒撵彻,并且不接收任何連接钓株;否則,通過系統(tǒng)調(diào)用poll()或select()來阻塞地讀取監(jiān)聽端口上傳入的數(shù)據(jù)陌僵,最長阻塞時(shí)間PM_POLL_TIMEOUT_SECOND轴合。
4.調(diào)用gs_signal_setmask(&BlockSig, NULL)和gs_signal_block_sigusr2()不再接收外源信號(hào)。
5.判斷poll()或select()函數(shù)的返回值拾弃,若小于零值桩,監(jiān)聽出錯(cuò),服務(wù)端進(jìn)程退出豪椿;若大于零奔坟,則創(chuàng)建連接ConnCreate(),并進(jìn)入后臺(tái)服務(wù)線程啟動(dòng)流程BackendStartup()搭盾。對(duì)于父線程咳秉,即postmaster線程,在結(jié)束BackendStartup()的調(diào)用以后鸯隅,會(huì)調(diào)用ConnFree()澜建,清除連接信息;若poll()或select()的返回值為零蝌以,即沒有信息傳入炕舵,則不進(jìn)行任何操作。
6.調(diào)用ADIO_RUN()跟畅、ADIO_END() 咽筋,若AioCompleters沒有啟動(dòng),則啟動(dòng)之徊件。
7.檢查各個(gè)輔助線程的線程號(hào)是否為零奸攻,若為零蒜危,則調(diào)用initialize_util_thread啟動(dòng)。
以非線程池模式為例睹耐,介紹線程的啟動(dòng)邏輯辐赞。BackendStartup函數(shù)是通過調(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)。
1.initialize_thread函數(shù)調(diào)用gs_thread_create函數(shù)創(chuàng)建線程捎迫,調(diào)用InternalThreadFunc函數(shù)處理線程晃酒。
ThreadId initialize_thread(ThreadArg* thr_argv){
gs_thread_t thread;int error_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);return InvalidTid;}
return gs_thread_id(thread);}
2.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彰导。
static void* InternalThreadFunc(void* args){knl_thread_arg* thr_argv = (knl_thread_arg*)args;gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv));return (void*)NULL;}GaussdbThreadEntry GetThreadEntry(knl_thread_role role){Assert(role > MASTER && role < THREAD_ENTRY_BOUND);return GaussdbThreadEntryGate[role];}static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain<MASTER>,GaussDbThreadMain<WORKER>,GaussDbThreadMain<THREADPOOL_WORKER>,GaussDbThreadMain<THREADPOOL_LISTENER>,......};
3.在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ù)
1.process_postgres_switches()笋轨,加載傳入的啟動(dòng)選項(xiàng)和GUC參數(shù)。
2.為不同信號(hào)設(shè)置handler赊淑。
3.調(diào)用sigdelset(&BlockSig, SIGQUIT)爵政,允許響應(yīng)SIGQUIT信號(hào)。
4.調(diào)用BaseInit()陶缺,初始化存儲(chǔ)管理系統(tǒng)和頁面緩存池計(jì)數(shù)钾挟。
5.調(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),鏈表長度由on_shmem_exit_index記錄苫费,且不可超過MAX_ON_EXITS宏汤锨。在線程退出時(shí),從后往前調(diào)用各個(gè)節(jié)點(diǎn)中的動(dòng)作(函數(shù)指針)百框,完成清理工作泥畅。
6.調(diào)用gs_signal_setmask (&UnBlockSig),設(shè)置屏蔽的信號(hào)類型。
7.調(diào)用InitBackendWorker進(jìn)行統(tǒng)計(jì)系統(tǒng)初始化位仁、syscache初始化工作。
8. BeginReportingGUCOptions如有需要?jiǎng)t打印GUC參數(shù)方椎。
9.調(diào)用on_proc_exit()聂抢,設(shè)置線程退出前需要進(jìn)行的線程清理動(dòng)作。設(shè)置和調(diào)用機(jī)制與on_shmem_exit()類似棠众。
10.調(diào)用process_local_preload_libraries()琳疏,處理GUC參數(shù)設(shè)定后的預(yù)加載庫。
11. AllocSetContextCreate創(chuàng)建MessageContext闸拿、RowDescriptionContext空盼、MaskPasswordCtx上下文。
12.調(diào)用sigsetjmp()新荤,設(shè)置longjump點(diǎn)揽趾,若后續(xù)查詢執(zhí)行中出錯(cuò),在某些情況下可以返回此處重新開始苛骨。
13.調(diào)用gs_signal_unblock_sigusr2()篱瞎,允許線程響應(yīng)指定的信號(hào)集。
14.然后進(jìn)入for循環(huán)痒芝,進(jìn)行查詢執(zhí)行俐筋。
1.調(diào)用pgstat_report_activity()、pgstat_report_waitstatus()严衬,告訴統(tǒng)計(jì)系統(tǒng)后臺(tái)線程正處于idle狀態(tài)。
2.設(shè)置全局變量DoingCommandRead = true粱挡。3.調(diào)用ReadCommand(),讀取客戶端SQL語句抱怔。4.設(shè)置全局變量DoingCommandRead=false嘀倒。5.若在上述過程中收到SIGHUP信號(hào)测蘑,表示線程需要重新加載修改過的postgresql.conf配置文件碳胳。
6.進(jìn)入switch (firstchar),根據(jù)接收到的信息進(jìn)行分支判斷味混。
- END -