天天看點

PostgreSQL 同步流複制原理和代碼淺析

資料庫acid裡面的d,持久化。 指的是對于使用者來說送出的事務,資料是可靠的,即使資料庫crash了,在硬體完好的情況下,也能恢複回來。

postgresql是怎麼做到的呢,看一幅圖,畫得比較醜,湊合看吧。

假設一個事務,對資料庫做了一些操作,并且産生了一些髒資料,首先這些髒資料會在資料庫的shared buffer中。

同時,産生這些髒資料的同時也會産生對應的redo資訊,産生的redo會有對應的lsn号(你可以了解為redo 的虛拟位址空間的一個唯一的offset,每一筆redo都有),這個lsn号也會記錄到shared buffer中對應的髒頁中。

walwriter是負責将wal buffer flush到持久化裝置的程序,同時它會更新一個全局變量,記錄已經flush的最大的lsn号。

bgwriter是負責将shared buffer的髒頁持久化到持久化裝置的程序,它在flush時,除了要遵循lru算法之外,還要通過lsn全局變量的比對,來保證髒頁對應的redo記錄已經flush到持久化裝置了,如果發現還對應的redo沒有持久化,會觸發wal writer去flush wal buffer。 (即確定日志比髒資料先落盤)

當使用者送出事務時,也會産生一筆送出事務的redo,這筆redo也攜帶了lsn号。backend process 同樣需要等待對應lsn flush到磁盤後才會傳回給使用者送出成功的信号。(保證日志先落盤,然後傳回給使用者)

PostgreSQL 同步流複制原理和代碼淺析

同步流複制,即保證standby節點和本地節點的日志雙雙落盤。

PostgreSQL 同步流複制原理和代碼淺析

postgresql使用另一組全局變量,記錄同步流複制節點已經接收到的xlog lsn,以及已經持久化的xlog lsn。

使用者在發起送出請求後,backend process除了要判斷本地wal有沒有持久化,同時還需要判斷同步流複制節點的xlog有沒有接收到或持久化(通過synchronous_commit參數控制)。

如果同步流複制節點的xlog還沒有接收或持久化,backend process會進入等待狀态。

對應的代碼和解釋如下:

committransaction @ src/backend/access/transam/xact.c

recordtransactioncommit @ src/backend/access/transam/xact.c

syncrepwaitforlsn @ src/backend/replication/syncrep.c

注意使用者進入等待狀态後,隻有主動cancel , 或者kill(terminate) , 或者主程序die才能退出無限的等待狀态。後面會講到如何将同步級别降級為異步。

前面提到了,使用者端需要等待latch的釋放信号。

那麼誰來給它這個信号了,是wal sender程序,源碼和解釋如下 :

src/backend/replication/walsender.c

syncrepreleasewaiters @ src/backend/replication/syncrep.c

syncrepwakequeue @ src/backend/replication/syncrep.c

postgresql 支援在會話中設定事務的可靠性級别。

off 表示commit 時不需要等待wal 持久化。

local 表示commit 是隻需要等待本地資料庫的wal 持久化。

remote_write 表示commit 需要等待本地資料庫的wal 持久化,同時需要等待sync standby節點wal write buffer完成(不需要持久化)。

on 表示commit 需要等待本地資料庫的wal 持久化,同時需要等待sync standby節點wal持久化。

提醒一點, synchronous_commit 的任何一種設定,都不影響wal日志持久化必須先于shared buffer髒資料持久化。 是以不管你怎麼設定,都不好影響資料的一緻性。

從前面的代碼解析可以得知,如果 backend process 進入了等待循環,隻接受幾種信号降級。 并且降級後會告警,表示本地wal已持久化,但是sync standby節點不确定wal有沒有持久化。

如果你隻配置了1個standby,并且将它配置為同步流複制節點。一旦出現網絡抖動,或者sync standby節點故障,将導緻同步事務進入等待狀态。

怎麼降級呢?

方法1.

修改配置檔案并重置

然後cancel 所有query .

收到這樣的信号,表示事務成功送出,同時表示wal不知道有沒有同步到sync standby。

同時它會讀到全局變量synchronous_commit 已經是 local了。

這樣就完成了降級的動作。

方法2.

方法1的降級需要對已有的正在等待wal sync的pid使用cancel進行處理,有點不人性化。

可以通過修改代碼的方式,做到更人性化。

syncrepwaitforlsn for循環中,加一個判斷,如果發現全局變量sync commit變成local, off了,則告警并退出。這樣就不需要人為的去cancel query了.