0、前言
之前也做了一些流復制的實驗,今天就想着把了解的PostgreSQL流復制的內容總結下,整理了這篇文章。
1、概述
1.1、什么是流復制?
如果有人問你PostgreSQL的流復制究竟是什么?你大概會說通過wal日志來進行數據同步之類的,的確如此,流復制大概就是這么回事。
但是准確的來說:PostgreSQL通過wal日志來傳送的方式有兩種:基於文件的日志傳送和流復制。
不同於基於文件的日志傳送,流復制的關鍵在於“流”,所謂流,就是沒有界限的一串數據,類似於河里的水流,是連成一片的。因此流復制允許一台后備服務器比使用基於文件的日志傳送更能保持為最新的狀態。
比如我們有一個大文件要從本地主機發送到遠程主機,如果是按照“流”接收到的話,我們可以一邊接收,一邊將文本流存入文件系統。這樣,等到“流”接收完了,硬盤寫入操作也已經完成。
1.2、流復制發展歷史
流復制之前的手段:
像我們上面說的,pg在流復制出現之前,使用的就是基於文件的日志傳送:對wal日志進行拷貝,因此從庫始終落后主庫一個日志文件,並且使用rsync工具同步data目錄。
而流復制出現是從2010年推出的pg9.0開始的,其歷史大致為:
- 起源:pg9.0開始支持流式物理復制,用戶可以通過流式復制,構建只讀備庫
(主備物理復制,塊級別一致)。流式物理復制可以做到極低的延遲(通常在1毫秒以內)。 - 同步流復制:pg9.1開始支持同步復制,但是當時只支持一個同步流復制備節點(例如配置了3個備,只有一個是同步模式的,其他都是異步模式)。同步流復制的出現,保證了數據的0丟失。
- 級聯流復制:pg9.2支持級聯流復制。即備庫還可以再連備庫。
- 流式虛擬備庫:pg9.2還支持虛擬備庫,即就是只有WAL,沒有數據文件的備庫。
- 邏輯復制:pg9.4開始可以實現邏輯復制,邏輯復制可以做到對主庫的部分復制,例如表級復制,而不是整個集群的塊級一致復制。
- 增加多種同步級別:pg9.6版本開始可以通過synchronous_commit參數,來配置事務的同步級別。
1.3、流復制概述
流復制其原理為:備庫不斷的從主庫同步相應的數據,並在備庫apply每個WAL record,這里的流復制每次傳輸單位是WAL日志的record。
PostgreSQL物理流復制按照同步方式分為兩類:
- 異步流復制
- 同步流復制
物理流復制具有以下特點:
1、延遲極低,不怕大事務
2、支持斷點續傳
3、支持多副本
4、配置簡單
5、備庫與主庫物理完全一致,並支持只讀
2、流復制基礎
在學習流復制之前,我們先來了解一些相關的基礎知識。
2.1、wal日志介紹
這里簡單介紹下:
WAL日志機制保證了事務的持久性和數據完整性,同時又避免了頻繁IO對性能的影響。
為了保證數據庫中數據的持久性,即事務提交后,即使數據庫出現故障也能保證數據的可靠性。最簡單的方法就是:數據一提交就刷到磁盤上。但是這樣對於事務非常頻繁的系統,一有事務提交就去刷新臟數據,會對數據庫性能產生非常不好的影響。因此使用wal日志來記錄數據的更改,這樣每當發生事務提交,只需要通過提交wal日志即可,而且wal日志的提交是順序的,性能也很高。
2.2、wal日志解讀
對數據庫操作會以record為單位首先記錄到wal日志中,在checkpoint時才對數據進行刷
盤(background writer會定時刷臟數據,但最終還是都由checkpoint確認都刷盤成功)。
聊了這么久wal日志,我們都還不知道wal日志在哪?長啥樣。。。
wal日志位置:
$PGDATA/pg_wal(pg10之前叫pg_xlog)
wal日志文件命名規則:
我們看到的wal日志是這樣的:000000010000000100000092
其中前8位:00000001表示timeline;
中間8位:00000001表示logid;
最后8位:00000092表示logseg
wal日志LSN編號規則:
1/920001F8(高32位/低32位)
對照關系:
1、wal日志的logseg前6位始終是0,后兩位是LSN低32位/16MB(2*24),即LSN的前兩位。如上例中logseg最后兩位是92,LSN低32前兩位也是92。
2、LSN在wal日志中的偏移量即LSN低32位中后24位對應的十進制值。
例如當前wal日志偏移量為504
bill=# select pg_walfile_NAME_OFFSET(pg_current_wal_lsn()); pg_walfile_name_offset -------------------------------- (000000010000000100000092,504) (1 row) bill=# select x'1F8'::int; int4 ------ 504 (1 row)
2.3、wal日志內部詳解
接下來我們來看看wal日志里面究竟記錄的是些什么內容。如果你直接查看wal日志,可能會收到下面這樣的提示:
因為wal日志是二進制格式的文件,不過我們可以使用pg_waldump這個工具來將其轉換成可讀的文件。
例1:
首先來看看insert數據時wal日志里面記錄了些什么。
bill=# begin; BEGIN bill=# select pg_current_wal_lsn(); pg_current_wal_lsn -------------------- 1/92021290 (1 row) bill=# insert into tbl values(1,'bill'); INSERT 0 1 bill=# select pg_current_wal_lsn(); pg_current_wal_lsn -------------------- 1/92021308 (1 row) bill=# end; COMMIT
接下來我們看看wal日志里面內容:可以看到wal日志里面記錄了上面的insert操作。
例2:
我們再看看update時wal日志里面記錄的內容:
bill=# select pg_current_wal_lsn(); pg_current_wal_lsn -------------------- 1/92021450 (1 row) bill=# update tbl set info = 'foucus' where id = 1; UPDATE 1
這里簡單介紹下這條記錄的內容:
rmgr: Heap len (rec/tot): 65/ 177, tx: 717, lsn: 1/92021450, prev 1/92021418, desc: HOT_UPDATE off 1 xmax 717 flags 0x20 ; new off 2 xmax 0, blkref #0: rel 1663/16395/17623 blk 0 FPW
- rmgr: Heap :PostgreSQL內部將WAL日志歸類到20多種不同的資源管理器。這條WAL記錄所屬資源管理器為 Heap,即堆表。除了Heap還有Btree,Transaction等。
- len (rec/tot): 65/ 177:wal記錄的長度。
- tx: 717: 事務號。
- lsn: 1/92021450:本條wal記錄的lsn。
- prev 1/92021418:上條wal記錄的lsn。
- desc: HOT_UPDATE off 1 xmax 717 flags 0x20 ; new off 2 xmax 0: 這是一條熱更新類型的記錄,舊數據
offset為1,xmax為717。舊tuple在page中的位置為1(即ctid的后半部分),新tuple在page中的位置為2。 - blkref #0: rel 1663/16395/17623 blk 0 :引用的第一個page(新tuple所在page)所屬的堆表文件為1663/13543/16469,塊號為0(即ctid的前半部分)
3、流復制原理
3.1、日志提交過程
從上圖我們可以看到流復制中日志提交的大致流程為:
1、事務commit后,日志在主庫寫入wal日志,還需要根據配置的日志同步級別,等待從庫反饋的接收結果。
2、主庫通過日志傳輸進程將日志塊傳給從庫,從庫接收進程收到日志開始回放,最終保證主從數據一致性。
3.2、流復制同步級別
PostgreSQL通過配置synchronous_commit (enum)參數來指定事務的同步級別。我們可以根據實際的業務需求,對不同的事務,設置不同的同步級別。
synchronous_commit = off # synchronization level;
# off, local, remote_write, or on
- remote_apply:事務commit或rollback時,等待其redo在primary、以及同步standby(s)已持久化,並且其redo在同步
standby(s)已apply。 - on:事務commit或rollback時,等待其redo在primary、以及同步standby(s)已持久化。
- remote_write:事務commit或rollback時,等待其redo在primary已持久化; 其redo在同步standby(s)已調用write接口(寫到 OS, 但是還沒有調用持久化接口如fsync)。
- local:事務commit或rollback時,等待其redo在primary已持久化;
- off:事務commit或rollback時,等待其redo在primary已寫入wal buffer,不需要等待其持久化;
不同的事務同步級別對應的數據安全級別越高,對應的對性能影響也就越大。上述從上至下安全級別越來越低。
詳細的同步流復制原理見:
CommitTransaction @ src/backend/access/transam/xact.c
RecordTransactionCommit @ src/backend/access/transam/xact.c
4、流復制配置過程
PostgreSQL物理流復制大致過程為:
1、PG軟件安裝
2、postgresql.conf參數配置
3、pg_hba.conf配置
4、pg_basebackup方式部署備庫
5、配置簡單
6、備庫與主庫物理完全一致,並支持只讀
4.1、異步流復制參數配置
postgresql.conf :
wal_level = replica # minimal, replica, or logical max_wal_senders = 10 wal_keep_segments = 1024 hot_standby = on
pg_hba.conf :
host replication postgres # max number of walsender processes # in logfile segments, 16MB each; 0 disables 192.168.7.180/32 md5
standby recovery.conf :
recovery_target_timeline = 'latest' standby_mode = on primary_conninfo = 'host=192.168.7.180 port=1921 user=bill password=xxx
4.2、同步流復制參數配置
postgresql.conf :
wal_level = replica # minimal, replica, or logical max_wal_senders = 10 # max number of walsender processes wal_keep_segments = 1024 # in logfile segments, 16MB each; 0 disables hot_standby = on synchronous_commit = remote_write、on、remote_apply synchronous_standby_names = 'standby2'
pg_hba.conf :
host replication postgres 192.168.7.180/32 md5
standby recovery.conf :
recovery_target_timeline = 'latest' standby_mode = on primary_conninfo = 'host=192.168.7.180 port=1921 user=bill password=xxx application_name=standby2'
另外我們可以通過設置synchronous_standby_names參數來指定一個支持同步復制的后備服務器的列表,其支持格式大致為:
1、synchronous_standby_names =standby_name [, ...] 2、synchronous_standby_names =[FIRST] num_sync ( standby_name [, ...]) 3、synchronous_standby_names =ANY num_sync ( standby_name [, ...] )
4.3、主備切換流程
1、關閉主庫,建議使用-m fast模式關閉。 2、在備庫上執行pg_ctl promote命令激活備庫,如果recovery.conf變成recovery.done表示 備庫已切換成為主庫。 3、這時需要將老的主庫切換成備庫,在老的主庫的 $PGDATA目錄下創建recovery.conf文 件(如果此目錄下不存在recovery.conf文件,可以根據$PGHOME/recovery.conf. sample模板文件復制一個,如果此目錄下存在recovery.done文件,需將recovery.done文件重命名為 recovery.conf),配置和老的從庫一樣,只是primary_conninfo參數中的IP換成對端IP。 4、啟動老的主庫,這時觀察主、備進程是否正常,如果正常表示主備切換成功。
4.4、復制槽slot
因為pg在歸檔模式下,對於已經完成歸檔的wal日志會自動清理,所以提供了復制槽來避免主庫在所有的備庫收到 WAL 日志之前不會移除它們,並且主庫也不會移除可能導致恢復沖突的行,即使備庫斷開也是如此。
例子:
創建一個復制槽:
bill=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot'); slot_name | lsn -------------+----- node_a_slot |
bill=# SELECT slot_name, slot_type, active FROM pg_replication_slots; slot_name | slot_type | active -------------+-----------+-------- node_a_slot | physical | f (1 row)
要配置后備機使用這個槽,在備庫的recovery.conf中應該配置 primary_slot_name,例如:
standby_mode = 'on' primary_conninfo = 'host=192.168.7.180 port=1921 user=bill password='xxx' primary_slot_name = 'node_a_slot'
5、pg12流復制新特性
PostgreSQL12中流復制有了一些改變:
把recovery.conf的內容全部移入postgresql.conf,配置恢復、archive based standby、stream based standby,都在postgresql.conf中。postgresql.conf以及對應的兩個signal文件來表示進入recovery 模式或standby模式。
例子:
1、典型恢復模式配置
postgresql.conf:
# stream恢復模式配置 #primary_conninfo = '' 或 # archvie恢復模式配置 #restore_command = '' hot_standby = on # 配置是否跨時間線 #recovery_target_timeline = 'latest' # 配置恢復目標,例如 # 立即(達到一致性即停止恢復)、時間、XID、restore point name, LSN. #recovery_target = '' # 'immediate' to end recovery as soon as a # consistent state is reached # (change requires restart) #recovery_target_name = '' # the named restore point to which recovery will proceed # (change requires restart) #recovery_target_time = '' # the time stamp up to which recovery will proceed # (change requires restart) #recovery_target_xid = '' # the transaction ID up to which recovery will proceed # (change requires restart) #recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed # (change requires restart) #recovery_target_inclusive = on # Specifies whether to stop: # just after the specified recovery target (on) # just before the recovery target (off) # (change requires restart) # 恢復目標到達后,暫停恢復、激活、停庫 #recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' # (change requires restart)
在 $PGDATA目錄中,touch recovery.signal
2、典型standby模式配置
postgresql.conf:
# stream恢復模式配置 #primary_conninfo = '' 或 # archvie恢復模式配置 #restore_command = '' hot_standby = on # 配置是否跨時間線 #recovery_target_timeline = 'latest'
在 $PGDATA目錄中,touch standby.signal
3、如果standby.signal , recovery.signal兩個文件都配置了,則優先為standby mode