閱讀278 返回首頁    go 阿裏雲 go 技術社區[雲棲]


PostgreSQL PG主備流複製機製

PostgreSQL在9.0之後引入了主備流複製機製,通過流複製,備庫不斷的從主庫同步相應的數據,並在備庫apply每個WAL record,這裏的流複製每次傳輸單位是WAL日誌的record。而PostgreSQL9.0之前提供的方法是主庫寫完一個WAL日誌文件後,才把WAL日誌文件傳送到備庫,這樣的方式導致主備延遲特別大。同時PostgreSQL9.0之後提供了Hot Standby,備庫在應用WAL record的同時也能夠提供隻讀服務,大大提升了用戶體驗。

主備總體結構

PG主備流複製的核心部分由walsender,walreceiver和startup三個進程組成。
walsender進程是用來發送WAL日誌記錄的,執行順序如下:

PostgresMain()->exec_replication_command()->StartReplication()->WalSndLoop()->XLogSendPhysical()

walreceiver進程是用來接收WAL日誌記錄的,執行順序如下:

sigusr1_handler()->StartWalReceiver()->AuxiliaryProcessMain()->WalReceiverMain()->walrcv_receive()

startup進程是用來apply日誌的,執行順序如下:

PostmasterMain()->StartupDataBase()->AuxiliaryProcessMain()->StartupProcessMain()->StartupXLOG()

下圖是PG主備總體框架圖:

master_1_

walsender和walreceiver進程流複製過程

walsender和walreceiver交互主要分為以下幾個步驟:

  1. walreceiver啟動後通過recovery.conf文件中的primary_conninfo參數信息連向主庫,主庫通過連接參數replication=true啟動walsender進程;
  2. walreceiver執行identify_system命令,獲取主庫systemid/timeline/xlogpos等信息,執行TIMELINE_HISTORY命令拉取history文件;
  3. 執行wal_startstreaming開始啟動流複製,通過walrcv_receive獲取WAL日誌,期間也會回應主庫發過來的心跳信息(接收位點、flush位點、apply位點),向主庫發送feedback信息(最老的事務id),避免vacuum刪掉備庫正在使用的記錄;
  4. 執行walrcv_endstreaming結束流複製,等待startup進程更新receiveStart和receiveStartTLI,一旦更新,進入步驟2。

walsender_3_

walreceiver和startup進程

startup進程進入standby模式和apply日誌主要過程:

  1. 讀取pg_control文件,找到redo位點;讀取recovery.conf,如果配置standby_mode=on則進入standby模式。
  2. 如果是Hot Standby需要初始化clog、subtrans、事務環境等。初始化redo資源管理器,比如Heap、Heap2、Database、XLOG等。
  3. 讀取WAL record,如果record不存在需要調用XLogPageRead->WaitForWALToBecomeAvailable->RequestXLogStreaming喚醒walreceiver從walsender獲取WAL record。
  4. 對讀取的WAL record進行redo,通過record->xl_rmid信息,調用相應的redo資源管理器進行redo操作。比如heap_redo的XLOG_HEAP_INSERT操作,就是通過record的信息在buffer page中增加一個record:

    MemSet((char *) htup, 0, sizeof(HeapTupleHeaderData));
    /* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
    memcpy((char *) htup + offsetof(HeapTupleHeaderData, t_bits),
           (char *) xlrec + SizeOfHeapInsert + SizeOfHeapHeader,
           newlen);
    newlen += offsetof(HeapTupleHeaderData, t_bits);
    htup->t_infomask2 = xlhdr.t_infomask2;
    htup->t_infomask = xlhdr.t_infomask;
    htup->t_hoff = xlhdr.t_hoff;
    HeapTupleHeaderSetXmin(htup, record->xl_xid);
    HeapTupleHeaderSetCmin(htup, FirstCommandId);
    htup->t_ctid = xlrec->target.tid;
    
    offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
    if (offnum == InvalidOffsetNumber)
        elog(PANIC, "heap_insert_redo: failed to add tuple");
    
    freespace = PageGetHeapFreeSpace(page);     /* needed to update FSM below */
    
    PageSetLSN(page, lsn);
    
    if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
        PageClearAllVisible(page);
    
    MarkBufferDirty(buffer);
    

    還有部分redo操作(vacuum產生的record)需要檢查在Hot Standby模式下的查詢衝突,比如某些tuples需要remove,而存在正在執行的query可能讀到這些tuples,這樣就會破壞事務隔離級別。通過函數ResolveRecoveryConflictWithSnapshot檢測衝突,如果發生衝突,那麼就把這個query所在的進程kill掉。

  5. 檢查一致性,如果一致了,Hot Standby模式可以接受用戶隻讀查詢;更新共享內存中XLogCtlData的apply位點和時間線;如果恢複到時間點,時間線或者事務id需要檢查是否恢複到當前目標;

  6. 回到步驟3,讀取next WAL record。

walreceiver_startup_1_

最後更新:2017-04-01 13:38:49

  上一篇:go PostgreSQL 線性回歸 - 股價預測 1
  下一篇:go PostgreSQL 徹底解決膨脹問題