今天咱們對postmaster的如下細節進行討論:html
backend的啓動和client的鏈接請求的認證 客戶端取消查詢時的處理 接受pg_ctl的shutdown請求進行shutdown處理
##2.與前端的交互 ###2.1backend的啓動和client的鏈接請求的認證 關於backend的啓動,其函數調用棧以下:前端
PostmasterMain() |->ServerLoop() |->initMasks() |->for(;;) |->select() <--監聽端口 |->ConnCreate() <--建立connection相關的數據結構 |->BackendStartup() <--創建後端進程backend process |->PostmasterRandom() |->canAcceptConnections() |->fork_process() |->InitPostmasterChild() |->ClosePostmasterPorts() |->BackendInitialize() |->ProcessStartupPacket() |->BackendRun() |->PostgresMain() |->ConnFree() <--釋放connection相關的數據結構
簡單來講,在系統調用select()中咱們監聽客戶端的鏈接請求,當讀到一個客戶端請求時咱們將爲其建立相關數據結構,作一下初始化。注意此時只是監聽接受了請求,這個請求是否合法(例如password是否正確)在此時是不作判斷的。判斷是放在BackendStartup()中的。node
你可能會疑惑:BackendStartup()用來建立backend,可是建立了backend以後才作驗證是否是有點晚?sql
咱們來看BackendStartup()的處理(具體的你們看上面的調用棧和source)。數據庫
typedef struct bkend { pid_t pid; /* process id of backend */ long cancel_key; /* cancel key for cancels for this backend */ int child_slot; /* PMChildSlot for this backend, if any */ /* * Flavor of backend or auxiliary process. Note that BACKEND_TYPE_WALSND * backends initially announce themselves as BACKEND_TYPE_NORMAL, so if * bkend_type is normal, you should check for a recent transition. */ int bkend_type; bool dead_end; /* is it going to send an error and quit? */ bool bgworker_notify; /* gets bgworker start/stop notifications */ dlist_node elem; /* list link in BackendList */ } Backend;
(backend的數據結構比較重要,我先引用在這裏)後端
首先,程序會調用PostmasterRandom()函數產生一個cancelkey。這個cancelkey是作什麼的呢?它是用來標記前端發來的cancel指令的:即前端發來一個停止當前SQL文的操做的指令時(好比你按Crtl+C), postmaster會先經過backendPID先在後端的backendList中找到對應的backend,再利用這個cancelkey來和這個backend作驗證(這個在下一節會提到)。服務器
而後調用canAcceptConnections來判斷當前postmaster的狀態是否能夠接受鏈接?讀者又疑惑了:前面不是已經接受鏈接了麼?前面的select只是調用select()系統調用獲取了這個鏈接請求(甚至連鏈接請求也算不上,只是收到了一個Client發來的packet,多是startup_packet,也多是cancel_request_packet),至因而不是能接受鏈接,咱們有兩個判斷:數據結構
若是不知足條件。咱們把將要啓動的backend標記爲dead_end。就是說這個後端只是用來向前端報錯用的,報錯以後當即退出。因此咱們就不給它分配Slot了。判斷能夠鏈接了以後,咱們就給它分配好slot。dom
繼續往下走。調用fork_process()啓動一個進程(固然就是用來做爲backend的了)。backend啓動起來了以後,咱們就能夠脫離postmaster,把後面的一切交給backend本身處理了。隨之而來的InitPostmasterChild()就是用來初始化backend進程,將環境句柄從postmaster切換到backend。而後調用ClosePostmasterPorts()關閉此時不須要的文件描述符。ide
而後調用BackendInitialize作進一步的初始化。這裏咱們比較感興趣的可能就是它調用了ProcessStartupPacket()獲取前端發送的StartupPacket併爲之分配內存,作一些簡單的判斷處理。驗證部分放在了後面。
最後,咱們調用BackendRun()真正的運行這個backend。 從代碼咱們可知,BackendRun()函數也只是一個殼子,他只是切換了內存上下文到TopMemoryContext而且獲取postmaster的命令行上 -o參數指定的一些參數,而後將這些參數傳給了PostgresMain()函數到這裏,咱們看出PostgresMain()函PostmasterMain()很像。都是命令的入口。並且後面咱們看到PostgresMain()函數裏面也有一個Loop。就是循讀取客戶端發來的SQL文。
InitPostgres()函數是PostgresMain()調用的一個很是重要的初始化函數,天然它的做用是作初始化。作哪些初始化呢?
我列舉一些:
InitProcessPhase2() Add my PGPROC struct to the ProcArray SharedInvalBackendInit() shared cache invalidation communication(inval) ProcSignalInit() Register the current process in the procsignal array RegisterTimeout() Register timeout RelationCacheInitialize() Initialize relationcache InitCatalogCache() Initialize catalog cache InitPlanCache() Initialize callbacks for inval EnablePortalManager() Portals are objects representing the execution state of a query, This module provides memory management services for portals InitializeClientEncoding() initialize client encoding
關於PostgresMain()其它的我很少說,和PostmasterMain()很像,只不過處理的全部對象都是針對banckend的,具體看代碼吧。
到這裏咱們能夠回答爲啥要建立了backend以後才作驗證:
postmaster只充當一箇中介的角色,不過多地涉及共享內存和其餘會引發錯誤的操做,使得postmaster主程序更健壯和穩定。同時若是在ServerLoop裏面花時間作驗證我以爲也太費時間了。
哦,忘了,咱們還沒說client的鏈接請求的認證。下面是函數調用棧:
BackendRun() |->PostgresMain() |->InitPostgres() |->PerformAuthentication() |->ClientAuthentication()
PerformAuthentication()在InitPostgres中是在EnablePortalManager()以後調用的,這個時候大部分backend進程自己的初始化工做都已完畢。
這裏作Client驗證的入口是ClientAuthentication()了。它的主要工做以下:
hba_getauthmethod() //獲取hba文件中和該條請求匹配的auth method | v switch(auth_method)//根據auth_method和請求信息作出相應的處理 | v status == STATUS_OK ? sendAuthRequest() : auth_failed() //根據返回值決定向client發送驗證packet仍是拒絕請求
須要說明的是當驗證失敗,拒絕client的請求後,程序在這裏就報錯退出了。這樣,這個backend就是一個dead_end的backend,他會在postmaster的指揮下退出,具體細節見後面幾節內容。
###2.2客戶端取消查詢時的中介
當咱們在client(例如psql命令行)中運行一個很長的SQL查詢(並非說必定要很長的查詢,只是若是時間過短的話你根原本不及cancel~)時,此時因爲各類緣由你想停止這條查詢,因而你按下了Crtl+C鍵。當即在客戶端上顯示:
postgres=# select * from test order by id asc ; Cancel request sent ERROR: canceling statement due to user request
那咱們來看一看postgres是如何處理這樣的cancel吧。
先上圖:
對應上圖,咱們針對涉及的進程分別列出函數調用棧: client: psql命令在初始化的時候調用setup_cancel_handler()在psql的MainLoop以前註冊了一個信號處理函數,在收到client的SIGINT(也就是你按下Ctrl+C)後,調用handle_sigint()處理這個信號。處理成功後,打印:
Cancel request sent
(src/bin/psql/startup.c) main() |->setup_cancel_handler() |->pqsignal(SIGINT, handle_sigint) |->successResult = MainLoop(stdin)
postmaster:processCancelRequest: postmaster在接收到client發來的packet後,創建一個後端進程(backend)去處理它,當發現它是一個cancel_request_packet後,調用processCancelRequest()函數處理這個packet,經過PID向對應的backend發送SIGINT信號。
PostmasterMain() |->ServerLoop() |->for(;;) |->BackendStartup() <--創建後端進程backend process |->BackendInitialize() |->ProcessStartupPacket() |->processCancelRequest() |->signal_child(bp->pid, SIGINT)
backend:pqsignal(SIGINT, StatementCancelHandler): postgresMain()函數上註冊下面這個信號處理函數,它接受postmaster發來的SIGINT信號,進行對應的處理,設置兩個全局變量:
pqsignal(SIGINT, StatementCancelHandler) { ... InterruptPending = true; QueryCancelPending = true; ... }
而這兩個全局變量又決定了CHECK_FOR_INTERRUPTS()是否生效:
#define CHECK_FOR_INTERRUPTS() \ do { \ if (InterruptPending) \ ProcessInterrupts(); \ } while(0)
咱們進ProcessInterrupts()函數,發現他就是用來處理client的停止請求的:
ProcessInterrupts(){ ... InterruptPending = false; ... { LockErrorCleanup(); ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to user request"))); } ... }
報錯消息就是咱們上面所見的那條了。
###2.3接受pg_ctl的shutdown請求
咱們常常會使用pg_ctl 來控制postgres服務器,好比start,stop和reload等等。start參數就是對應着服務器的啓動,這個在Postgres中postmaster代碼解析(上)中咱們已經討論過。這裏咱們來討論下指定stop參數的處理。
在開始討論以前,咱們先看下PMState這個枚舉類型。
typedef enum { PM_INIT, /* postmaster starting */ PM_STARTUP, /* waiting for startup subprocess */ PM_RECOVERY, /* in archive recovery mode */ PM_HOT_STANDBY, /* in hot standby mode */ PM_RUN, /* normal "database is alive" state */ PM_WAIT_BACKUP, /* waiting for online backup mode to end */ PM_WAIT_READONLY, /* waiting for read only backends to exit */ PM_WAIT_BACKENDS, /* waiting for live backends to exit */ PM_SHUTDOWN, /* waiting for checkpointer to do shutdown * ckpt */ PM_SHUTDOWN_2, /* waiting for archiver and walsenders to * finish */ PM_WAIT_DEAD_END, /* waiting for dead_end children to exit */ PM_NO_CHILDREN /* all important children have exited */ } PMState;
這個枚舉類型標註的是數據庫當前的狀態。其中PM_RUN是一個分水嶺。從PM_INIT到PM_RUN,數據庫逐漸從初始化狀態轉換爲正常的運行狀態。而從PM_RUN到PM_NO_CHILDREN,數據庫逐漸由正常運行狀態轉換到能夠關閉的狀態。理解了這個有助於咱們理解數據庫的啓動和關閉的時序。上面每一個狀態後面的註釋已經能很好地解釋每一個狀態間的轉換條件了,我這裏不贅述了。
對於pg_ctl的stop參數,咱們有三種模式:
模式 | 發送的signal | signal的處理 |
---|---|---|
smart | SIGTERM | Wait for children to end their work, then shut down |
fast | SIGINT | Abort all children with SIGTERM (rollback active transactions and exit) and shut down when they are gone |
immediate | SIGQUIT | abort all children with SIGQUIT, wait for them to exit, terminate remaining ones with SIGKILL, then exit without attempt to properly shut down the database system. |
這裏咱們就先以smart模式展開討論,其餘的模式其實也是相似的。
首先執行"pg_ctl stop -m smart",這個時候其實就是向postmaster發送了一個SIGTERM信號;
postmaster收到SIGTERM信號,觸發pqsignal(SIGTERM, pmdie),調用pmdie()函數去處理SIGTERM信號;
pmdie |->SignalSomeChildren(SIGTERM,BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) 向autovacuum和bgworker子進程轉發SIGTERM信號 |->PostmasterStateMachine() 更新數據庫的狀態PM_State
pmdie中的處理如上所示。pmdie調用SignalSomeChildren()向指定的進程發送SIGTERM信號,一樣這些進程自己也有信號處理函數,在接收到postmaster的SIGTERM信號進行相關處理並終止。(子進程終止後會向父進程發送一個SIGCHLD信號,這是操做系統的固有處理)。PostmasterStateMachine()是一個工具函數,在postmaster不少的信號處理函數中都會調用該函數來根據數據庫當前的PM_State和相關進程的死活來更新PM_State。
這個時候咱們看backend進程:
backend:pqsignal(SIGTERM, die); //die
backend進程自己的信號處理函數在收到SIGTERM信號後調用die函數進程exit處理。
話題再回到postmaster,當它收到子進程的SIGCHLD信號時,觸發pqsignal(SIGCHLD, reaper),會調用reaper()函數處理子進程發來的SIGCHLD信號:
reaper() |->switch(PID) 根據PID類型判斷子進程類型,分別進行處理 |->PostmasterStateMachine() 更新數據庫的狀態PM_State
這樣postmaster會一直收到子進程的SIGCHLD信號,並進行相應處理後更新PM_State。
那何時肯定全部的子進程都結束了呢?仍是看PostmasterStateMachine()函數:
PostmasterStateMachine() 當最後一個backend(dead_end)結束時,reaper處理子進程經過調用PostmasterStateMachine更新當前狀態, 將當前狀態由PM_WAIT_DEAD_END轉換爲PM_NO_CHILDREN時: PM_WAIT_DEAD_END -> pmState = PM_NO_CHILDREN 說明說有子進程都已退出,postmaster調用ExitPostmaster結束自身: ExitPostmaster()
本節討論就是這樣,下次準備討論:
後端process的管理 DB的shoutdown的處理 backend異常結束時的處理 BootstrapMain()的處理
先把flag立下來,省得本身忘了。歡迎你們點贊~