Postgresql主從非同步流複製方案
資料庫的備份工作在日常生產中極為重要,如果你諮詢一個DBA如何才能設計出高可用的資料備份與恢復方案,相信很多人都會從架構上給出很多容災的意見。但歸根到底,如果業務環節中資料庫還牽涉到分散式環境,我認為一個好的方案需要達到三大要求:
- 多副本
- 持久化
- 一致性
日常架構設計中,我們不僅要保證資料額的成功備份,還要保證備份的資料可以快速恢復。在眾多備份恢復可靠性方案中主從複製
技術,可以說是最常見的實現,本文主要是介紹postgresql主備資料庫的非同步流複製的環境搭建與主備切換的操作實踐,除了能把一些基礎的原理運用在日常的資料庫運維中,也可以加深對Postgresql資料庫的底層知識瞭解。
下面的學習與實踐主要針對PostgreSQL的非同步流複製(本文沒有涉及到同步複製、邏輯複製等,如果大家想了解其它的備份方案,可以閱讀相關官方文件或其他資料介紹)。
非同步流複製的中心思想是:主庫上提交事務時不需要等待備庫接收WAL日誌流並寫入到備庫WAL日誌檔案時便返回成功,因此非同步流複製的TPS會相對同步流複製要高,延遲更低。
環境準備
作業系統 | 伺服器IP | 節點名稱 | 角色 |
---|---|---|---|
centos 7.2 | 172.17.0.2 | pghost1 | 主庫 |
centos 7.2 | 172.17.0.5 | pghost2 | 備庫 |
主要目錄規範:
-
資料目錄: /data/pg10/pg_root
-
表空間目錄: /data/pg10/pg_tbs
-
應用程式目錄: /apps/svr/pgsql
要注意的是:編譯安裝Pg我們使用的是root賬戶,但是一般情況下,我們對資料庫的部署操作等應該使用非root的pg超級管理員賬戶,所以需要我們預先建立相關使用者和目錄,並設定相關許可權:
$ groupadd postgres $ useradd postgres -g postgres $ passwd postgres $ mkdir -p /data/pg10/pg_root $ mkdir -p /data/pg10/tbs $ chown -R postgres:postgres /data/pg10
實驗用的postgresql為10.0版本
pghost1 和 pghost2 分別下載該版本的原始碼安裝包
wget https://ftp.postgresql.org/pub/source/v10.0/postgresql-10.0.tar.gz
下載後進行解壓
tar -zxvf postgresql-10.0.tar.gz
安裝前依賴
由於 configure過程中依賴作業系統包zlib、readline等,所以我實用yum預先安裝:
yum groupinstall "Development tools” yum install -y bison flex readline readline-devel zlib zlib-devel
主備庫資料庫安裝
安裝前,我們先分別對pghost1 和 pghost2建立postgresql的偏好環境變數
vi /etc/profile.d/pgsql.sh
追加以下內容:
export PGPORT=1921 export PGUSER=postgres export PGDATA=/data/pg10/pg_root export LANG=en_US.utf8 export PGHOME=/apps/svr/pgsql export LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib export PATH=$PGHOME/bin:$PATH:. export MANPATH=$PGHOME/share/man:$MANPATH alias rm='rm -i' alias ll='ls -lh'
儲存檔案,並讓環境變數生效:
source /etc/profile.d/pgsql.sh
再進入剛剛解壓的 postgresql-10.0 目錄中,執行以下命令:
./configure —prefix=/apps/svr/pgsql_10.0/ --with-pgport=1921
之後進行編譯安裝:
gmake gmake install
安裝完成後,我們可以使用以下命令確認是否安裝成功:
$ postgres --version postgres (PostgreSQL) 10.0
複製功能部署
在啟動資料庫服務搭建主從結構前,有幾個比較重要的配置檔案需要我們額外地進行建立與設定的,它們分別是:
-
postgreql.conf
-
pg_hba.conf
-
recovery.conf
-
.pgpass
下面我們會在實踐中,具體地對上述的檔案的配置進行相關說明
上一節,我們編譯安裝好了postgresql,我們接下來切換操作使用者
su postgresql
然後使用initdb工具初始化資料庫:
initdb -D /data/pg10/pg_root -E UTF8 --locale=C -U postgres -W
執行上述命令後,在/data/pg10/pg_root目錄下會產生系統資料檔案,
PG_VERSIONpg_dynshmempg_multixactpg_snapshotspg_tblspcpostgresql.auto.conf basepg_hba.confpg_notifypg_statpg_twophasepostgresql.conf globalpg_ident.confpg_replslotpg_stat_tmppg_wal pg_commit_tspg_logicalpg_serialpg_subtranspg_xact
之後我們開始配置/data/pg10/pg_root/postgresql.conf
,修改以下幾個關鍵項:
listen_addresses = '0.0.0.0' wal_level = replica archive_mode = on archive_command = '/bin/date' max_wal_senders = 10 wal_keep_segments = 16 hot_standby = on
注:主庫和備庫的 /data/pg10/pg_root/postgresql.conf 配置建議完全一致
接下來我們在備庫
上配置/data/pg10/pg_root/pg_hba.conf
hostreplicationrepuser172.17.0.2/32md5 hostreplicationrepuser172.17.0.5/32md5
其實最好主庫也配置一份,因為主庫和備庫的角色不是靜止的,在手動或庫出現故障情況下,它們的角色會互相更換。
之後,我們先啟動主庫 pghost1了 (記得切換到postgres使用者):
$ pg_ctl start ... ... database system is ready to accept connections done server started
使用PostgreSQL的超級管理員postgres登入到建立流複製使用者repuser,流複製使用者需要有 REPLICATION許可權和LOGIN許可權
$ psql psql (10.0) Type "help" for help. postgres=# CREATE USER repuser REPLICATIONLOGIN CONNECTION LIMIT 5 ENCRYPTED PASSWORD 'domac123'; CREATE ROLE
以上命令基本完成主庫上的配置,接下來我們需要熱備生成一個備庫,製作備庫過程中主庫仍然可以讀寫,不影響業務,我們在主庫上建立備份任務:
postgres=# select pg_start_backup('domacli_bak'); pg_start_backup ----------------- 0/2000060 (1 row)
pg_start_backup() 函式會在主庫上發起一個線上備份,命令執行後,將資料檔案壓縮拷貝到備份節點上:
$ tar czvf pg_root.tar.gz pg_root --exclude=pg_root/pg_wal $ scp pg_root.tar.gz [email protected]:/data/pg10
pg_wal目錄不是必須複製的,可以排除這個目錄,以節省空間,然後我們回到備庫的/data/pg10下,執行主庫備份檔案的解壓:
$ tar xvf pg_root.tar.gz
解壓後,我們回到主節點,執行停止備份命令,結束這次備份流程
postgres=# select pg_stop_backup(); NOTICE:pg_stop_backup complete, all required WAL segments have been archived pg_stop_backup ---------------- 0/2000168 (1 row)
以上的命令表示完成線上備份,但備庫上扔需要做一些配置,我們回到備庫上,配置 /data/pg10/pg_root/recovery.conf檔案,如果該檔案不存在,可以執行以下命令,在軟體目錄中複製一個:
cp $PGHOME/share/recovery.conf.sample /data/pg10/pg_root/recovery.conf
備庫的 recovery.conf 配置以下引數
recovery_target_timeline = 'latest' standby_mode = on primary_conninfo = 'host=172.17.0.2port=1921 user=repuser'
主要觀察recovery.conf中的引數primary_conninfo
中的user=repuser
,還記得我們前面在主庫上建立的流傳輸使用者repuser嗎?由於主備直接資料同步需要在使用者下執行操作,而主庫上我們建立repuser的時候,為了安全我設定了密碼, 但recovery.conf我們沒有配置明文密碼,那麼程式的密碼如何獲得呢?
我們建議把密碼設定在 ~/.pgpass中:
$ cd ~ $ touch .pgpass $ chmod 0600 .pgpass
填寫以下內容:
172.17.0.2:1921:replication:repuser:domac123 172.17.0.5:1921:replication:repuser:domac123
好了,當這些備註都就緒之後,我們可以開始啟動我們的備庫了:
$ pg_ctl start ... database system is ready to accept read only connections done server started
如果備庫正常啟動,我們可以在主備兩庫上觀察WAL發生與接收程序是否都同時工作,以確認非同步流工作是否正常工作
主庫上:
ps -ef | grep wal postgres693969350 23:16 ?00:00:00 postgres: wal writer process postgres698369350 23:42 ?00:00:00 postgres: wal sender process repuser 172.17.0.5(45910) streaming 0/3000140
備庫上:
ps -ef | grep wal postgres 26481 264790 23:42 ?00:00:00 postgres: wal receiver processstreaming 0/3000140 postgres 26486 264480 23:42 ?00:00:00 grep --color=auto wal
當然,穩妥起見,我們最好多動手動手,嘗試在主庫上建立並插入資料,觀察備庫上是否同步這些操作,我們再主庫上建立一張表:
postgres=# create table test_ms(id int4); CREATE TABLE postgres=# insert into test_ms values(6); INSERT 0 1
主庫上,我們建立test_ms表,並插入了一條資料,我們就可以在備庫
上進行查詢觀察是否同步成功:
postgres=# select * from test_ms; id ---- 6 (1 row)
接下來,我們再主庫上,再操作
postgres=# insert into test_ms values(9); INSERT 0 1 postgres=# delete from test_ms where id=6; DELETE 1
這個時候,我們發現備庫的資料也都正常同步上了:
postgres=# select * from test_ms; id ---- 9 (1 row)
那麼我們如果在備份上進行資料操作,情況會怎樣呢?我們再備份上執行:
postgres=# insert into test_ms values(6); ERROR:cannot execute INSERT in a read-only transaction STATEMENT:insert into test_ms values(6); ERROR:cannot execute INSERT in a read-only transaction
觀察這些錯誤日誌,我們可以瞭解到,非同步流主從結構中,作為從節點的備庫目前處於的是隻讀狀態,它不能進行任何寫入操作。
主備切換
前面介紹了流複製的部署,但要注意的是主庫和備庫的角色不是靜態存在的,在維護過程中可以對兩者的進行角色的切換,舉個例子,當主庫掛掉的時候,需要迅速進行主備切換,讓備庫升級為主庫,原主庫降級到備庫,主備切換是PostgreSQL高可用的基礎,下面就介紹相關的操作。
postgresql 9.0版本流複製只能通過建立檔案方式進行主備切換,9.1後,開始支援使用pg_ctl promote觸發方式,相比檔案觸發方式操作更方便
操作前,我們先介紹一個系統函式查用來判斷主備角色的方法:
postgres=# select pg_is_in_recovery(); pg_is_in_recovery ------------------- f (1 row)
如果返回
f
說明是主庫,返回
t
說明是備庫
pg_ctl promote 切換方式
我們使用以下的步驟進行主備切換:
1、關閉主庫,建議使用 -m fast 模式關閉
$ pg_ctl stop -m fast
2、在備庫上執行pg_ctl promote命令啟用備庫,如果recovery.conf變成recovery.done表示備庫已切換成主庫
pg_ctl promote waiting for server to promote....2018-09-30 00:10:30.222 UTC [26480] LOG:received promote request LOG:redo done at 0/4000028 LOG:last completed transaction was at log time 2018-09-29 23:50:52.502513+00 LOG:selected new timeline ID: 2 LOG:archive recovery complete LOG:database system is ready to accept connections Sun Sep 30 00:10:30 UTC 2018 Sun Sep 30 00:10:30 UTC 2018 done server promoted
命令執行後,如果原來的 recovery.conf 更名為 recovery.done, 表示切換成功
3、這時如果需要將老的主庫切換成備庫,在老的主庫的$PGDATA目錄下也建立recovery.conf檔案(建立方式跟之前介紹的一樣,內容可以和原從庫pghost2的一樣,只是primary_conninfo的IP換成對端pghost2的IP)
例如,主庫上的 recovery.conf 設定為:
recovery_target_timeline = 'latest' standby_mode = on primary_conninfo = 'host=172.17.0.5port=1921 user=repuser'
與此同時,和原備庫pghost2一樣,我們建議把repuser的密碼設定在pghost1 ~/.pgpass中:
$ cd ~ $ touch .pgpass $ chmod 0600 .pgpass
填寫以下內容:
172.17.0.2:1921:replication:repuser:domac123 172.17.0.5:1921:replication:repuser:domac123
4、啟動老的主庫pghost1,這時觀察主、備進行是否正常,嚴格點可以在新的主庫上對剛才的test_ms表進行操作,觀察資料是否同步成功。
pg_ctl start
我們在新主庫(pghost2)上執行:
postgres=# select pg_is_in_recovery(); pg_is_in_recovery ------------------- f (1 row)
發現它目前的角色已經是主庫了, 在新備庫(pghost1)上繼續執行:
postgres=# select pg_is_in_recovery(); pg_is_in_recovery ------------------- t (1 row)
發現它目前的角色也已經切換為備庫了
我們再pghost2上,執行資料插入操作:
postgres=# insert into test_ms values(11); INSERT 0 1
這時,pghost1上也觀察到資料同步成功:
postgres=# select * from test_ms; id ---- 9 11 (2 rows)
到這裡為止,主從切換的演練基本完成了
總結
非同步流複製模式中,主庫提交的事務不會等待備庫接收WAL日誌流並返回確認資訊,因此非同步流複製模式下主庫與備庫的資料版本上會存在一定的處理延遲,延遲的時間主要受主庫壓力、備庫主機效能、網路頻寬等影響,當正常情況下,主備的延遲通常在毫秒級的範圍內,當主庫宕機,這個延遲就主要受到故障發現與切換時間的影響而拉長,不過雖然如此,這些資料延遲的問題,可以從架構或相關自動化運維手段不斷優化設定。