大型自適應(yīng)的網(wǎng)站開發(fā)互動(dòng)營銷案例100
GPDB - 高可用 - 流復(fù)制狀態(tài)
GPDB的高可用基于流復(fù)制,通過FTS進(jìn)行自動(dòng)故障切換。自動(dòng)故障切換需要根據(jù)primary-mirror流復(fù)制的各種狀態(tài)進(jìn)行判斷。本節(jié)就聊聊primary-mirror流復(fù)制的各種狀態(tài)。同樣適用于PgSQL
1、WalSndState
typedef enum WalSndState
{WALSNDSTATE_STARTUP = 0,WALSNDSTATE_BACKUP,WALSNDSTATE_CATCHUP,WALSNDSTATE_STREAMING,WALSNDSTATE_STOPPING
} WalSndState;
WalSndState保存的是wal sender進(jìn)程的狀態(tài)信息,變量值如上代碼。
WALSNDSTATE_STARTUP表示啟動(dòng)狀態(tài);
WALSNDSTATE_BACKUP表示備份狀態(tài)
WALSNDSTATE_CATCHUP表示追趕狀態(tài)
WALSNDSTATE_STREAMING表示流復(fù)制狀態(tài)
WALSNDSTATE_STOPPING表示wal sender即將退出
2、什么時(shí)候切換到WALSNDSTATE_STOPPING
1)集群shutdown有三種方式:smart、fast、immediate
三種標(biāo)記值分別為:
#define SmartShutdown 1
#define FastShutdown 2
#define ImmediateShutdown 3
Smart shutdown:不允許有新連接,待已有連接全部結(jié)束后關(guān)閉數(shù)據(jù)庫;
Fast shutdown:不允許新連接,向所有活躍的服務(wù)進(jìn)程發(fā)送SIGTERM信號,讓他們立即退出,之后等待所有子進(jìn)程退出并關(guān)閉數(shù)據(jù)庫
Immediate shutdown:不允許新連接,主進(jìn)程postgres向所有子進(jìn)程發(fā)送SIGQUIT信號并立即退出,所有子進(jìn)程也會(huì)立即退出。下次啟動(dòng)會(huì)回放WAL日志進(jìn)行恢復(fù)。
2)如果shutdown模式不為immediate,則集群shutdown的時(shí)候,postgres主進(jìn)程會(huì)向checkpoint進(jìn)程發(fā)送SIGUSR2信號:
3)checkpoint進(jìn)程的SIGUSR2信號處理函數(shù)為ReqShutdownHandler,從上圖的代碼邏輯可見,ReqShutdownHandler會(huì)將shutdown_requested置為true,并喚醒MyLatch。
4)checkpoint進(jìn)程接著調(diào)用ShutdownXLog,然后proc_exit(0)退出checkpoint進(jìn)程。
5)ShutdownXLog函數(shù)調(diào)用WalSndInitStopping向所有sender進(jìn)程發(fā)送SIGUSR1信號;然后調(diào)用WalSndWaitStopping等待所有sender進(jìn)程退出,每個(gè)10ms判斷一次。
6)sender進(jìn)程SIGUSR1信號處理函數(shù)procsignal_sigusr1_handler檢查信號來自PROCSIG_WALSND_INIT_STOPPING,然后將got_STOPPING置為true
7)流復(fù)制的sender處理完SIGUSR1信號后,繼續(xù)返回信號前處理流程。Sender的發(fā)送日志函數(shù)為XLogSendPhysical,此時(shí)got_STOOPING已為true,所以調(diào)用WalSndSetState將walsnd->state切換到WALSNDSTATE_STOPPING狀態(tài),然后調(diào)用FTSReplicationStatusUpdateForWalState更新WAL復(fù)制狀態(tài)
8)另外當(dāng)sender進(jìn)程從WalSndLoop退出后(replication_active置為false),這個(gè)時(shí)候,Wal sender進(jìn)程才接收到信號,HandleWalSndInitStopping中也可以看到,會(huì)向自己發(fā)送SIGTERM信號,信號處理函數(shù)die,即退出進(jìn)程(因?yàn)榱鲝?fù)制終止了,不必管它了)。
9)若,sender進(jìn)程還沒從WalSndLoop退出(replication_active置為true),這個(gè)時(shí)候,Wal sender進(jìn)程接收到信號,HandleWalSndInitStopping中也可以看到,他會(huì)設(shè)置got_STOPPING為true,讓W(xué)AL sender進(jìn)程發(fā)送完WAL后退出WalSndLoop循環(huán)后調(diào)用proc_exit自行退出。
2、sender進(jìn)程什么時(shí)候退出?
書接上文,產(chǎn)生個(gè)問題:WalSndLoop何時(shí)退出?若沒有shutdown,何時(shí)再發(fā)起流復(fù)制?
Wal sender進(jìn)程接收到mirror發(fā)來的start replication命令后,進(jìn)入StartReplication開始流復(fù)制。
1)WalSndLoop循環(huán)中,通過XLogSendPhysical函數(shù)不斷發(fā)送WAL
2)XLogSendPhysical函數(shù)發(fā)送WAL達(dá)到一個(gè)時(shí)間線的末尾節(jié)點(diǎn)位置時(shí),向mirror的receiver進(jìn)程發(fā)送CopyDone消息,即開頭為‘c’的消息,并將streamingDoneSending變量改為true
3)receiver進(jìn)程的入口函數(shù)WalReceiverMain,通過walrcv_receive::libpqrcv_receive不斷接收WAL日志和消息。當(dāng)接收到發(fā)來的CopyDone消息后返回-1
4)接著,返回到WalReceiverMain函數(shù)中,當(dāng)walrcv_receive返回-1后,一路下來會(huì)退出接收消息和日志的循環(huán),并進(jìn)入walrcv_endstreaming再向primary發(fā)送個(gè)CopyDone消息
5)primary的ProcessRepliesIfAny處理mirror發(fā)來的消息,當(dāng)接收到CopyDone消息后,將streamingDoneReceiving改為true
6)返回WalSndLoop循環(huán),當(dāng)streamingDoneSending和streamingDoneReceiving都為true時(shí)退出循環(huán)
總結(jié)一句話:primary發(fā)完一個(gè)時(shí)間線內(nèi)的WAL,切換下一個(gè)時(shí)間線時(shí),會(huì)退出發(fā)送WAL日志的循環(huán)stop streaming;當(dāng)然mirror的receiver進(jìn)程發(fā)起下一個(gè)時(shí)間線的日志拉取,即再次調(diào)用libpqrcv_startstreaming函數(shù)向primary發(fā)送START_REPLICATION命令后,primary仍舊會(huì)再次進(jìn)入WalSndLoop循環(huán)發(fā)送WAL日志。
3、什么時(shí)候進(jìn)入WALSNDSTATE_BACKUP?
exec_replication_command:進(jìn)行基礎(chǔ)備份的時(shí)候
exec_replication_command:進(jìn)行基礎(chǔ)備份的時(shí)候switch (cmd_node->type){case T_BaseBackupCmd:PreventInTransactionBlock(true, "BASE_BACKUP");SendBaseBackup((BaseBackupCmd *) cmd_node);| parse_basebackup_options(cmd->options, &opt);| WalSndSetState(WALSNDSTATE_BACKUP);| perform_base_backup(&opt);break;...}
進(jìn)行基礎(chǔ)備份,也就是構(gòu)建mirror的時(shí)候進(jìn)入該狀態(tài)。
4、什么時(shí)候進(jìn)入WALSNDSTATE_STARTUP?
1)sender進(jìn)程剛fork出來,InitWalSenderSlot初始化的時(shí)候
2)WalSndLoop進(jìn)程退出后又進(jìn)入startup狀態(tài),因?yàn)橄聜€(gè)時(shí)間線的復(fù)制即將開始
3)sender進(jìn)程遇到ERROR故障,跳回到PostgresMain回退操作處,回退事務(wù)后,進(jìn)入WalSndErrorCleanup,若沒有stop則重新設(shè)置為startup狀態(tài),等待接收start replication命令重新開始復(fù)制。
PostgresMainif (am_walsender)InitWalSender();//sender進(jìn)程的初始化|-- InitWalSenderSlot|-- for (i = 0; i < max_wal_senders; i++){| WalSnd *walsnd = &WalSndCtl->walsnds[i];| SpinLockAcquire(&walsnd->mutex);| if (walsnd->pid != 0){| //找一個(gè)空閑的slot| SpinLockRelease(&walsnd->mutex);| continue;| }else{| walsnd->pid = MyProcPid;| walsnd->state = WALSNDSTATE_STARTUP;| ...| break;| }| }|-- on_shmem_exit(WalSndKill, 0);
StartReplication:sender的WalSndLoop退出后又進(jìn)入startup狀態(tài)WalSndLoop(XLogSendLogical);...if (got_STOPPING)proc_exit(0);WalSndSetState(WALSNDSTATE_STARTUP);EndCommand("COPY 0", DestRemote);
PostgresMain//sender進(jìn)程遇到ERROR報(bào)錯(cuò),sender進(jìn)程需要再次start replication才能進(jìn)入傳輸walif (sigsetjmp(local_sigjmp_buf, 1) != 0){AbortCurrentTransaction();if (am_walsender)WalSndErrorCleanup();|-- if (got_STOPPING || got_SIGUSR2)| proc_exit(0);|-- WalSndSetState(WALSNDSTATE_STARTUP);...for (;;){firstchar = ReadCommand(&input_message);switch (firstchar){case 'Q':{if (am_walsender){if (!exec_replication_command(query_string))exec_simple_query(query_string);}else if (am_ftshandler)HandleFtsMessage(query_string);else if (am_faulthandler)HandleFaultMessage(query_string);elseexec_simple_query(query_string);send_ready_for_query = true;break;}case 'M': ...}}
5、什么時(shí)候進(jìn)入WALSNDSTATE_CATCHUP?
開始流復(fù)制前,設(shè)置成catchup狀態(tài)。
StartReplication:開始流復(fù)制前WalSndSetState(WALSNDSTATE_CATCHUP);/* Send a CopyBothResponse message, and start streaming */pq_beginmessage(&buf, 'W');pq_sendbyte(&buf, 0);pq_sendint16(&buf, 0);pq_endmessage(&buf);pq_flush();WalSndLoop(XLogSendLogical);...
6、什么時(shí)候進(jìn)入WALSNDSTATE_STREAMING?
當(dāng)前時(shí)間線內(nèi)沒有要發(fā)送的日志了,并且沒有下一個(gè)時(shí)間線需要切換發(fā)送日志,則將其改為streaming狀態(tài)。
WalSndLoopfor (;;){if (!pq_is_send_pending())send_data();elseWalSndCaughtUp = false;...//現(xiàn)在沒有要發(fā)送的了if (WalSndCaughtUp && !pq_is_send_pending()){if (MyWalSnd->state == WALSNDSTATE_CATCHUP)WalSndSetState(WALSNDSTATE_STREAMING);}...}