1. 前言
PostgreSQL是一款功能,性能,可靠性均可以和高端的國外商業數據庫相媲美的開源數據庫。並且PostgreSQL的許可和生態徹底開放,不被任何一個單一的公司或國家所操控,保證了使用者沒有後顧之憂。國內愈來愈多的企業開始用PostgreSQL代替原來昂貴的國外商業數據庫。php
在部署PostgreSQL到生產環境中時,選擇適合的高可用方案是一項必不可少的工做。本文介紹基於Patroni的PostgreSQL高可用的部署方法,供你們參考。html
PostgreSQL的開源HA工具備不少種,下面幾種算是比較經常使用的node
- PAF(PostgreSQL Automatic Failomianver)
- repmgr
- Patroni
它們的比較能夠參考: https://scalegrid.io/blog/managing-high-availability-in-postgresql-part-1/python
其中Patroni不只簡單易用並且功能很是強大。linux
- 支持自動failover和按需switchover
- 支持一個和多個備節點
- 支持級聯複製
- 支持同步複製,異步複製
- 支持同步複製下備庫故障時自動降級爲異步複製(功效相似於MySQL的半同步,可是更加智能)
- 支持控制指定節點是否參與選主,是否參與負載均衡以及是否能夠成爲同步備機
- 支持經過
pg_rewind
自動修復舊主 - 支持多種方式初始化集羣和重建備機,包括
pg_basebackup
和支持wal_e
,pgBackRest
,barman
等備份工具的自定義腳本 - 支持自定義外部callback腳本
- 支持REST API
- 支持經過watchdog防止腦裂
- 支持k8s,docker等容器化環境部署
- 支持多種常見DCS(Distributed Configuration Store)存儲元數據,包括etcd,ZooKeeper,Consul,Kubernetes
所以,除非只有2臺機器沒有多餘機器部署DCS的狀況,Patroni是一款很是值得推薦的PostgreSQL高可用工具。下面將詳細介紹基於Patroni搭建PostgreSQL高可用環境的步驟。web
2. 實驗環境
主要軟件sql
- CentOS 7.8
- PostgreSQL 12
- Patroni 1.6.5
- etcd 3.3.25
機器和VIP資源docker
-
PostgreSQL數據庫
- node1:192.168.234.201
- node2:192.168.234.202
- node3:192.168.234.203
-
etcdbootstrap
- node4:192.168.234.204
-
VIP
- 讀寫VIP:192.168.234.210
- 只讀VIP:192.168.234.211
環境準備
全部節點設置時鐘同步
yum install -y ntpdate ntpdate time.windows.com && hwclock -w
若是使用防火牆須要開放postgres,etcd和patroni的端口。
- postgres:5432
- patroni:8008
- etcd:2379/2380
更簡單的作法是將防火牆關閉
setenforce 0 sed -i.bak "s/SELINUX=enforcing/SELINUX=permissive/g" /etc/selinux/config systemctl disable firewalld.service systemctl stop firewalld.service iptables -F
3. etcd部署
由於本文的主題不是etcd的高可用,因此只在node4上部署單節點的etcd用於實驗。生產環境至少須要部署3個節點,可使用獨立的機器也能夠和數據庫部署在一塊兒。etcd的部署步驟以下
安裝須要的包
yum install -y gcc python-devel epel-release
安裝etcd
yum install -y etcd
編輯etcd配置文件/etc/etcd/etcd.conf
, 參考配置以下
ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="http://192.168.234.204:2380" ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://192.168.234.204:2379" ETCD_NAME="etcd0" ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.234.204:2380" ETCD_ADVERTISE_CLIENT_URLS="http://192.168.234.204:2379" ETCD_INITIAL_CLUSTER="etcd0=http://192.168.234.204:2380" ETCD_INITIAL_CLUSTER_TOKEN="cluster1" ETCD_INITIAL_CLUSTER_STATE="new"
啓動etcd
systemctl start etcd
設置etcd自啓動
systemctl enable etcd
3. PostgreSQL + Patroni HA部署
在須要運行PostgreSQL的實例上安裝相關軟件
安裝PostgreSQL 12
yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postgresql12-server postgresql12-contrib
安裝Patroni
yum install -y gcc epel-release yum install -y python-pip python-psycopg2 python-devel pip install --upgrade pip pip install --upgrade setuptools pip install patroni[etcd]
建立PostgreSQL數據目錄
mkdir -p /pgsql/data chown postgres:postgres -R /pgsql chmod -R 700 /pgsql/data
建立Partoni service配置文件/etc/systemd/system/patroni.service
[Unit] Description=Runners to orchestrate a high-availability PostgreSQL After=syslog.target network.target [Service] Type=simple User=postgres Group=postgres #StandardOutput=syslog ExecStart=/usr/bin/patroni /etc/patroni.yml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process TimeoutSec=30 Restart=no [Install] WantedBy=multi-user.target
建立Patroni配置文件/etc/patroni.yml
,如下是node1的配置示例
scope: pgsql namespace: /service/ name: pg1 restapi: listen: 0.0.0.0:8008 connect_address: 192.168.234.201:8008 etcd: host: 192.168.234.204:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 master_start_timeout: 300 synchronous_mode: false postgresql: use_pg_rewind: true use_slots: true parameters: listen_addresses: "0.0.0.0" port: 5432 wal_level: logical hot_standby: "on" wal_keep_segments: 100 max_wal_senders: 10 max_replication_slots: 10 wal_log_hints: "on" initdb: - encoding: UTF8 - locale: C - lc-ctype: zh_CN.UTF-8 - data-checksums pg_hba: - host replication repl 0.0.0.0/0 md5 - host all all 0.0.0.0/0 md5 postgresql: listen: 0.0.0.0:5432 connect_address: 192.168.234.201:5432 data_dir: /pgsql/data bin_dir: /usr/pgsql-12/bin authentication: replication: username: repl password: "123456" superuser: username: postgres password: "123456" basebackup: max-rate: 100M checkpoint: fast tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
完整的參數含有可參考Patroni手冊中的 YAML Configuration Settings,其中PostgreSQL參數可根據須要自行補充。
其餘PG節點的patroni.yml須要相應修改下面3個參數
- name
node1~node4
分別設置pg1~pg4
- restapi.connect_address
- 根據各自節點IP設置
- postgresql.connect_address
- 根據各自節點IP設置
啓動Patroni
先在node1上啓動Patroni。
systemctl start patroni
初次啓動Patroni時,Patroni會初始建立PostgreSQL實例和用戶。
[root@node1 ~]# systemctl status patroni ● patroni.service - Runners to orchestrate a high-availability PostgreSQL Loaded: loaded (/etc/systemd/system/patroni.service; disabled; vendor preset: disabled) Active: active (running) since Sat 2020-09-05 14:41:03 CST; 38min ago Main PID: 1673 (patroni) CGroup: /system.slice/patroni.service ├─1673 /usr/bin/python2 /usr/bin/patroni /etc/patroni.yml ├─1717 /usr/pgsql-12/bin/postgres -D /pgsql/data --config-file=/pgsql/data/postgresql.conf --listen_addresses=0.0.0.0 --max_worker_processe... ├─1719 postgres: pgsql: logger ├─1724 postgres: pgsql: checkpointer ├─1725 postgres: pgsql: background writer ├─1726 postgres: pgsql: walwriter ├─1727 postgres: pgsql: autovacuum launcher ├─1728 postgres: pgsql: stats collector ├─1729 postgres: pgsql: logical replication launcher └─1732 postgres: pgsql: postgres postgres 127.0.0.1(37154) idle
再在node2上啓動Patroni。node2將做爲replica加入集羣,自動從leader拷貝數據並創建複製。
[root@node2 ~]# systemctl status patroni ● patroni.service - Runners to orchestrate a high-availability PostgreSQL Loaded: loaded (/etc/systemd/system/patroni.service; disabled; vendor preset: disabled) Active: active (running) since Sat 2020-09-05 16:09:06 CST; 3min 41s ago Main PID: 1882 (patroni) CGroup: /system.slice/patroni.service ├─1882 /usr/bin/python2 /usr/bin/patroni /etc/patroni.yml ├─1898 /usr/pgsql-12/bin/postgres -D /pgsql/data --config-file=/pgsql/data/postgresql.conf --listen_addresses=0.0.0.0 --max_worker_processe... ├─1900 postgres: pgsql: logger ├─1901 postgres: pgsql: startup recovering 000000010000000000000003 ├─1902 postgres: pgsql: checkpointer ├─1903 postgres: pgsql: background writer ├─1904 postgres: pgsql: stats collector ├─1912 postgres: pgsql: postgres postgres 127.0.0.1(35924) idle └─1916 postgres: pgsql: walreceiver streaming 0/3000060
查看集羣狀態
[root@node2 ~]# patronictl -c /etc/patroni.yml list + Cluster: pgsql (6868912301204081018) -------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +--------+-----------------+--------+---------+----+-----------+ | pg1 | 192.168.234.201 | Leader | running | 1 | | | pg2 | 192.168.234.202 | | running | 1 | 0.0 | +--------+-----------------+--------+---------+----+-----------+
爲了方便平常操做,設置全局環境變量PATRONICTL_CONFIG_FILE
echo 'export PATRONICTL_CONFIG_FILE=/etc/patroni.yml' >/etc/profile.d/patroni.sh
添加如下環境變量到~postgres/.bash_profile
export PGDATA=/pgsql/data export PATH=/usr/pgsql-12/bin:$PATH
設置postgres擁有免密的sudoer權限
echo 'postgres ALL=(ALL) NOPASSWD: ALL'> /etc/sudoers.d/postgres
4. 自動切換和腦裂防禦
Patroni在主庫故障時會自動執行failover,確保服務的高可用。可是自動failover若是控制不當會有產生腦裂的風險。所以Patroni在保障服務的可用性和防止腦裂的雙重目標下會在特定場景下執行一些自動化動做。
故障位置 | 場景 | Patroni的動做 |
---|---|---|
備庫 | 備庫PG中止 | 中止備庫PG |
備庫 | 中止備庫Patroni | 中止備庫PG |
備庫 | 強殺備庫Patroni(或Patroni crash) | 無操做 |
備庫 | 備庫沒法鏈接etcd | 無操做 |
備庫 | 非Leader角色可是PG處於生產模式 | 重啓PG並切換到恢復模式做爲備庫運行 |
主庫 | 主庫PG中止 | 重啓PG,重啓超過master_start_timeout 設定時間,進行主備切換 |
主庫 | 中止主庫Patroni | 中止主庫PG,並觸發failover |
主庫 | 強殺主庫Patroni(或Patroni crash) | 觸發failover,此時出現"雙主" |
主庫 | 主庫沒法鏈接etcd | 將主庫降級爲備庫,並觸發failover |
- | etcd集羣故障 | 將主庫降級爲備庫,此時集羣中所有都是備庫。 |
- | 同步模式下無可用同步備庫 | 臨時切換主庫爲異步複製,在恢復爲同步複製以前自動failover暫不生效 |
4.1 Patroni如何防止腦裂
部署在數據庫節點上的patroni進程會執行一些保護操做,確保不會出現多個「主庫」
- 非Leader節點的PG處於生產模式時,重啓PG並切換到恢復模式做爲備庫運行
- Leader節點的patroni沒法鏈接etcd時,不能確保本身仍然是Leader,將本機的PG降級爲備庫
- 正常中止patroni時,patroni會順便把本機的PG進程也停掉
然而,當patroni進程自身沒法正常工做時,以上的保護措施難以獲得貫徹。好比patroni進程異常終止或主機臨時hang等。
爲了更可靠的防止腦裂,Patroni支持經過Linux的watchdog監視patroni進程的運行,當patroni進程沒法正常往watchdog設備寫入心跳時,由watchdog觸發Linux重啓。具體的配置方法以下
設置Patroni的systemd service配置文件/etc/systemd/system/patroni.service
[Unit] Description=Runners to orchestrate a high-availability PostgreSQL After=syslog.target network.target [Service] Type=simple User=postgres Group=postgres #StandardOutput=syslog ExecStartPre=-/usr/bin/sudo /sbin/modprobe softdog ExecStartPre=-/usr/bin/sudo /bin/chown postgres /dev/watchdog ExecStart=/usr/bin/patroni /etc/patroni.yml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process TimeoutSec=30 Restart=no [Install] WantedBy=multi-user.target
設置Patroni自啓動
systemctl enable patroni
修改Patroni配置文件/etc/patroni.yml
,添加如下內容
watchdog: mode: automatic # Allowed values: off, automatic, required device: /dev/watchdog safety_margin: 5
safety_margin
指若是Patroni沒有及時更新watchdog,watchdog會在Leader key過時前多久觸發重啓。在本例的配置下(ttl=30,loop_wait=10,safety_margin=5)下,patroni進程每隔10秒(loop_wait
)都會更新Leader key和watchdog。若是Leader節點異常致使patroni進程沒法及時更新watchdog,會在Leader key過時的前5秒觸發重啓。重啓若是在5秒以內完成,Leader節點有機會再次得到Leader鎖,不然Leader key過時後,由備庫經過選舉選出新的Leader。
這套機制基本上能夠保證不會出現"雙主",可是這個保證是依賴於watchdog的可靠性的,從生產實踐上這個保證對絕大部分場景多是足夠的,可是從理論上難以證實它100%可靠。
另外一方面,自動重啓機器的方式會不會太暴力致使"誤殺"呢?好比因爲突發的業務訪問致使機器負載太高,進而致使patroni進程不能及時分配到CPU資源,此時自動重啓機器就未必是咱們指望的行爲。
那麼,有沒有其它更可靠的防止腦裂的手段呢?
4.2 利用PostgreSQL同步複製防止腦裂
防止腦裂的另外一個手段是把PostgreSQL集羣配置成同步複製模式。利用同步複製模式下的主庫在沒有同步備庫應答日誌時寫入會阻塞的特色,在數據庫內部確保即便出現「雙主」也不會發生"雙寫"。採用這種方式防止腦裂是最可靠最安全的,代價是同步複製相對異步複製會下降一點性能。具體設置方法以下
初始運行Patroni時,在Patroni配置文件/etc/patroni.yml
中設置同步模式
synchronous_mode:true
對於已部署的Patroni能夠經過patronictl命令修改配置
patronictl edit-config -s 'synchronous_mode=true'
此配置下,若是同步備庫臨時不可用,Patroni會把主庫的複製模式降級成了異步複製,確保服務不中斷。效果相似於MySQL的半同步複製,可是相比MySQL使用固定的超時時間控制複製降級,這種方式更加智能,同時還具備防腦裂的功效。
在同步模式下,只有同步備庫具備被提高爲主庫的資格。所以若是主庫被降級爲異步複製,因爲沒有同步備庫做爲候選主庫failover不會被觸發,也就不會出現「雙主」。若是主庫沒有被降級爲異步複製,那麼即便出現「雙主」,因爲舊主處於同步複製模式,數據沒法被寫入,也不會出現「雙寫」。
Patroni經過動態調整PostgreSQL參數synchronous_standby_names
控制同步異步複製的切換。而且Patroni會把同步的狀態記錄到etcd中,確保同步狀態在Patroni集羣中的一致性。
正常的同步模式的元數據示例以下:
[root@node4 ~]# etcdctl get /service/cn/sync {"leader":"pg1","sync_standby":"pg2"}
備庫故障致使主庫臨時降級爲異步複製的元數據以下:
[root@node4 ~]# etcdctl get /service/cn/sync {"leader":"pg1","sync_standby":null}
若是集羣中包含3個以上的節點,還能夠考慮採起更嚴格的同步策略,禁止Patroni把同步模式降級爲異步。這樣能夠確保任何寫入的數據至少存在於2個以上的節點。對數據安全要求極高的業務能夠採用這種方式。
synchronous_mode:true synchronous_mode_strict:true
若是集羣包含異地的災備節點,能夠根據須要配置該節點爲不參與選主,不參與負載均衡,也不做爲同步備庫。
tags: nofailover: true noloadbalance: true clonefrom: false nosync: true
4.2 etcd不可訪問的影響
當Patroni沒法訪問etcd時,將不能確認本身所處的角色。爲了防止這種狀態下產生腦裂,若是本機的PG是主庫,Patroni會把PG降級爲備庫。若是集羣中全部Patroni節點都沒法訪問etcd,集羣中將所有都是備庫,業務沒法寫入數據。這就要求etcd集羣具備很是高的可用性,特別是當咱們用一套中心的etcd集羣管理幾百幾千套PG集羣的時候。
當咱們使用集中式的一套etcd集羣管理不少套PG集羣時,爲了預防etcd集羣故障帶來的嚴重影響,能夠考慮設置超大的retry_timeout
參數,好比1萬天,同時經過同步複製模式防止腦裂。
retry_timeout:864000000 synchronous_mode:true
retry_timeout
用於控制操做DCS和PostgreSQL的重試超時。Patroni對須要重試的操做,除了時間上的限制還有重試次數的限制。對於PostgreSQL操做,目前彷佛只有調用GET /patroni
的REST API時會重試,並且最多隻重試1次,因此把retry_timeout
調大不會帶來其餘反作用。
5. 平常操做
平常維護時能夠經過patronictl
命令控制Patroni和PostgreSQL,好比修改PotgreSQL參數。
[postgres@node2 ~]$ patronictl --help Usage: patronictl [OPTIONS] COMMAND [ARGS]... Options: -c, --config-file TEXT Configuration file -d, --dcs TEXT Use this DCS -k, --insecure Allow connections to SSL sites without certs --help Show this message and exit. Commands: configure Create configuration file dsn Generate a dsn for the provided member, defaults to a dsn of... edit-config Edit cluster configuration failover Failover to a replica flush Discard scheduled events (restarts only currently) history Show the history of failovers/switchovers list List the Patroni members for a given Patroni pause Disable auto failover query Query a Patroni PostgreSQL member reinit Reinitialize cluster member reload Reload cluster member configuration remove Remove cluster from DCS restart Restart cluster member resume Resume auto failover scaffold Create a structure for the cluster in DCS show-config Show cluster configuration switchover Switchover to a replica version Output version of patronictl command or a running Patroni...
5.1 修改PostgreSQL參數
修改個別節點的參數,能夠執行ALTER SYSTEM SET ...
SQL命令,好比臨時打開某個節點的debug日誌。對於須要統一配置的參數應該經過patronictl edit-config
設置,確保全局一致,好比修改最大鏈接數。
patronictl edit-config -p 'max_connections=300'
修改最大鏈接數後須要重啓才能生效,所以Patroni會在相關的節點狀態中設置一個Pending restart
標誌。
[postgres@node2 ~]$ patronictl list + Cluster: pgsql (6868912301204081018) -------+----+-----------+-----------------+ | Member | Host | Role | State | TL | Lag in MB | Pending restart | +--------+-----------------+--------+---------+----+-----------+-----------------+ | pg1 | 192.168.234.201 | Leader | running | 25 | | * | | pg2 | 192.168.234.202 | | running | 25 | 0.0 | * | +--------+-----------------+--------+---------+----+-----------+-----------------+
重啓集羣中全部PG實例後,參數生效。
patronictl restart pgsql
5.2 查看Patroni節點狀態
一般咱們能夠同patronictl list
查看每一個節點的狀態。可是若是想要查看更詳細的節點狀態信息,須要調用REST API。好比在Leader鎖過時時存活節點卻沒法成爲Leader,查看詳細的節點狀態信息有助於調查緣由。
curl -s http://127.0.0.1:8008/patroni | jq
輸出示例以下:
[root@node2 ~]# curl -s http://127.0.0.1:8008/patroni | jq { "database_system_identifier": "6870146304839171063", "postmaster_start_time": "2020-09-13 09:56:06.359 CST", "timeline": 23, "cluster_unlocked": true, "watchdog_failed": true, "patroni": { "scope": "cn", "version": "1.6.5" }, "state": "running", "role": "replica", "xlog": { "received_location": 201326752, "replayed_timestamp": null, "paused": false, "replayed_location": 201326752 }, "server_version": 120004 }
上面的"watchdog_failed": true
,表明使用了watchdog可是卻沒法訪問watchdog設備,該節點沒法被提高爲Leader。
6. 客戶端訪問配置
HA集羣的主節點是動態的,主備發生切換時,客戶端對數據庫的訪問也須要可以動態鏈接到新主上。有下面幾種常見的實現方式,下面分別。
-
多主機URL
-
vip
-
haproxy
6.1 多主機URL
pgjdbc和libpq驅動能夠在鏈接字符串中配置多個IP,由驅動識別數據庫的主備角色,鏈接合適的節點。
JDBC
JDBC的多主機URL功能全面,支持failover,讀寫分離和負載均衡。能夠經過參數配置不一樣的鏈接策略。
-
鏈接主節點(實際是可寫的節點)。當出現"雙主"甚至"多主"時驅動鏈接第一個它發現的可用的主節點
-
優先鏈接備節點,無可用備節點時鏈接主節點,有多個可用備節點時隨機鏈接其中一個。
-
隨機鏈接任意一個可用的節點
libpq
libpq的多主機URL功能相對pgjdbc弱一點,只支持failover。
-
鏈接主節點(實際是可寫的節點)
-
鏈接任一可用節點
基於libpq實現的其餘語言的驅動相應地也能夠支持多主機URL,好比python和php。下面是python程序使用多主機URL建立鏈接的例子
import psycopg2 conn=psycopg2.connect("postgres://192.168.234.201:5432,192.168.234.202:5432/postgres?target_session_attrs=read-write&password=123456")
6.2 VIP(經過Patroni回調腳本實現VIP漂移)
多主機URL的方式部署簡單,可是不是每種語言的驅動都支持,並且若是數據庫出現意外的「雙主」,配置多主機URL的客戶端在多個主上同時寫入的機率比較高,而若是客戶端經過VIP的方式訪問則在VIP上又多了一層防禦(這種風險通常在數據庫的HA組件沒防禦好時發生,正如前面介紹的,若是咱們配置的是Patroni的同步模式,基本上沒有這個擔心)。
Patroni支持用戶配置在特定事件發生時觸發回調腳本。所以咱們能夠配置一個回調腳本,在主備切換後動態加載VIP。
準備加載VIP的回調腳本/pgsql/loadvip.sh
#!/bin/bash VIP=192.168.234.210 GATEWAY=192.168.234.2 DEV=ens33 action=$1 role=$2 cluster=$3 log() { echo "loadvip: $*"|logger } load_vip() { ip a|grep -w ${DEV}|grep -w ${VIP} >/dev/null if [ $? -eq 0 ] ;then log "vip exists, skip load vip" else sudo ip addr add ${VIP}/32 dev ${DEV} >/dev/null rc=$? if [ $rc -ne 0 ] ;then log "fail to add vip ${VIP} at dev ${DEV} rc=$rc" exit 1 fi log "added vip ${VIP} at dev ${DEV}" arping -U -I ${DEV} -s ${VIP} ${GATEWAY} -c 5 >/dev/null rc=$? if [ $rc -ne 0 ] ;then log "fail to call arping to gateway ${GATEWAY} rc=$rc" exit 1 fi log "called arping to gateway ${GATEWAY}" fi } unload_vip() { ip a|grep -w ${DEV}|grep -w ${VIP} >/dev/null if [ $? -eq 0 ] ;then sudo ip addr del ${VIP}/32 dev ${DEV} >/dev/null rc=$? if [ $rc -ne 0 ] ;then log "fail to delete vip ${VIP} at dev ${DEV} rc=$rc" exit 1 fi log "deleted vip ${VIP} at dev ${DEV}" else log "vip not exists, skip delete vip" fi } log "loadvip start args:'$*'" case $action in on_start|on_restart|on_role_change) case $role in master) load_vip ;; replica) unload_vip ;; *) log "wrong role '$role'" exit 1 ;; esac ;; *) log "wrong action '$action'" exit 1 ;; esac
修改Patroni配置文件/etc/patroni.yml
,配置回調函數
postgresql: ... callbacks: on_start: /bin/bash /pgsql/loadvip.sh on_restart: /bin/bash /pgsql/loadvip.sh on_role_change: /bin/bash /pgsql/loadvip.sh
全部節點的Patroni配置文件都修改後,從新加載Patroni配置文件
patronictl reload pgsql
執行switchover後,能夠看到VIP發生了漂移
/var/log/messages:
Sep 5 21:32:24 localvm postgres: loadvip: loadvip start args:'on_role_change master pgsql' Sep 5 21:32:24 localvm systemd: Started Session c7 of user root. Sep 5 21:32:24 localvm postgres: loadvip: added vip 192.168.234.210 at dev ens33 Sep 5 21:32:25 localvm patroni: 2020-09-05 21:32:25,415 INFO: Lock owner: pg1; I am pg1 Sep 5 21:32:25 localvm patroni: 2020-09-05 21:32:25,431 INFO: no action. i am the leader with the lock Sep 5 21:32:28 localvm postgres: loadvip: called arping to gateway 192.168.234.2
注意,若是直接中止主庫上的Patroni,上面的腳本不會摘除VIP。主庫上的Patroni被停掉後會觸發備庫failover成爲新主,此時新舊主2臺機器上都有VIP,可是因爲新主執行了arping,通常不會影響應用訪問。儘管如此,操做上仍是須要注意避免。
6.3 VIP(經過keepalived實現VIP漂移)
Patroni提供了用於健康檢查的REST API,能夠根據節點角色返回正常(200)和異常的HTTP狀態碼
-
GET /
或GET /leader
運行中且是leader節點
-
GET /replica
運行中且是replica角色,且沒有設置tag noloadbalance
-
GET /read-only
和
GET /replica
相似,可是包含leader節點
使用REST API,Patroni能夠和外部組件搭配使用。好比能夠配置keepalived動態在主庫或備庫上綁VIP。
關於Patroni的REST API接口詳細,參考Patroni REST API。
下面的例子在一主一備集羣(node1和node2)中動態在備節點上綁只讀VIP(192.168.234.211),當備節點故障時則將只讀VIP綁在主節點上。
安裝keepalived
yum install -y keepalived
準備keepalived配置文件/etc/keepalived/keepalived.conf
global_defs { router_id LVS_DEVEL } vrrp_script check_leader { script "/usr/bin/curl -s http://127.0.0.1:8008/leader -v 2>&1|grep '200 OK' >/dev/null" interval 2 weight 10 } vrrp_script check_replica { script "/usr/bin/curl -s http://127.0.0.1:8008/replica -v 2>&1|grep '200 OK' >/dev/null" interval 2 weight 5 } vrrp_script check_can_read { script "/usr/bin/curl -s http://127.0.0.1:8008/read-only -v 2>&1|grep '200 OK' >/dev/null" interval 2 weight 10 } vrrp_instance VI_1 { state BACKUP interface ens33 virtual_router_id 211 priority 100 advert_int 1 track_script { check_can_read check_replica } virtual_ipaddress { 192.168.234.211 } }
啓動keepalived
systemctl start keepalived
上面的配置方法也能夠用於讀寫vip的漂移,只要把track_script
中的腳本換成check_leader
便可。可是在網絡抖動或其它臨時故障時keepalived管理的VIP容易飄,所以我的更推薦使用Patroni回調腳本動態綁定讀寫VIP。若是有多個備庫,也能夠在keepalived中配置LVS對全部備庫進行負載均衡,過程就不展開了。
6.4 haproxy
haproxy做爲服務代理和Patroni配套使用能夠很方便地支持failover,讀寫分離和負載均衡,也是Patroni社區做爲Demo的方案。缺點是haproxy自己也會佔用資源,全部數據流量都通過haproxy,性能上會有必定損耗。
下面配置經過haproxy訪問一主兩備PG集羣的例子。
安裝haproxy
yum install -y haproxy
編輯haproxy配置文件/etc/haproxy/haproxy.cfg
global maxconn 100 log 127.0.0.1 local2 defaults log global mode tcp retries 2 timeout client 30m timeout connect 4s timeout server 30m timeout check 5s listen stats mode http bind *:7000 stats enable stats uri / listen pgsql bind *:5000 option httpchk http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server postgresql_192.168.234.201_5432 192.168.234.201:5432 maxconn 100 check port 8008 server postgresql_192.168.234.202_5432 192.168.234.202:5432 maxconn 100 check port 8008 server postgresql_192.168.234.203_5432 192.168.234.203:5432 maxconn 100 check port 8008 listen pgsql_read bind *:6000 option httpchk GET /replica http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server postgresql_192.168.234.201_5432 192.168.234.201:5432 maxconn 100 check port 8008 server postgresql_192.168.234.202_5432 192.168.234.202:5432 maxconn 100 check port 8008 server postgresql_192.168.234.203_5432 192.168.234.203:5432 maxconn 100 check port 8008
若是隻有2個節點,上面的GET /replica
須要改爲GET /read-only
,不然備庫故障時就沒法提供只讀訪問了,可是這樣配置主庫也會參與讀,不能徹底分離主庫的讀負載。
啓動haproxy
systemctl start haproxy
haproxy自身也須要高可用,能夠把haproxy部署在node1和node2 2臺機器上,經過keepalived控制VIP(192.168.234.210)在node1和node2上漂移。
準備keepalived配置文件/etc/keepalived/keepalived.conf
global_defs { router_id LVS_DEVEL } vrrp_script check_haproxy { script "pgrep -x haproxy" interval 2 weight 10 } vrrp_instance VI_1 { state BACKUP interface ens33 virtual_router_id 210 priority 100 advert_int 1 track_script { check_haproxy } virtual_ipaddress { 192.168.234.210 } }
啓動keepalived
systemctl start keepalived
下面作個簡單的測試。從node4上經過haproxy的5000端口訪問PG,會連到主庫上
[postgres@node4 ~]$ psql "host=192.168.234.210 port=5000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()' inet_server_addr | pg_is_in_recovery ------------------+------------------- 192.168.234.201 | f (1 row)
經過haproxy的6000端口訪問PG,會輪詢鏈接2個備庫
[postgres@node4 ~]$ psql "host=192.168.234.210 port=6000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()' inet_server_addr | pg_is_in_recovery ------------------+------------------- 192.168.234.202 | t (1 row) [postgres@node4 ~]$ psql "host=192.168.234.210 port=6000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()' inet_server_addr | pg_is_in_recovery ------------------+------------------- 192.168.234.203 | t (1 row)
haproxy部署後,能夠經過它的web接口 http://192.168.234.210:7000/查看統計數據
7. 級聯複製
一般集羣中全部的備庫都從主庫複製數據,可是特定的場景下咱們可能須要部署級聯複製。基於Patroni搭建的PG集羣支持2種形式的級聯複製。
7. 1 集羣內部的級聯複製
能夠指定某個備庫優先從指定成員而不是Leader節點複製數據。相應的配置示例以下:
tags: replicatefrom: pg2
replicatefrom
只對節點處於Replica角色時有效,並不影響該節點參與Leader選舉併成爲Leader。當replicatefrom
指定的複製源節點故障時,Patroni會自動修改PG切換到從Leader節點複製。
7.2 集羣間的級聯複製
咱們還能夠建立一個只讀的備集羣,從另外一個指定的PostgreSQL實例複製數據。這能夠用於建立跨數據中心的災備集羣。相應的配置示例以下:
初始建立一個備集羣,能夠在Patroni配置文件/etc/patroni.yml
中加入如下配置
bootstrap: dcs: standby_cluster: host: 192.168.234.210 port: 5432 primary_slot_name: slot1 create_replica_methods: - basebackup
上面的host
和port
是上游複製源的主機和端口號,若是上游數據庫是配置了讀寫VIP的PG集羣,能夠將讀寫VIP做爲host
避免主集羣主備切換時影響備集羣。
複製槽選項primary_slot_name
是可選的,若是配置了複製槽,須要同時在主集羣上配置持久slot,確保在新主上始終保持slot。
slots: slot1: type: physical
對於已配置好的級聯集羣,可使用patronictl edit-config
命令動態添加standby_cluster
設置把主集羣變成備集羣;以及刪除standby_cluster
設置把備集羣變成主集羣。
standby_cluster: host: 192.168.234.210 port: 5432 primary_slot_name: slot1 create_replica_methods: - basebackup
8. 參考
- https://patroni.readthedocs.io/en/latest/
- http://blogs.sungeek.net/unixwiz/2018/09/02/centos-7-postgresql-10-patroni/
- https://scalegrid.io/blog/managing-high-availability-in-postgresql-part-1/
- https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters
- https://www.percona.com/blog/2019/10/23/seamless-application-failover-using-libpq-features-in-postgresql/