基于Patroni的PostgreSQL高可用环境部署

基于 Patroni 的 PostgreSQL 高可用环境部署Patroni

前言

PostgreSQL 是一款功能,性能,可靠性都可以和高端的国外商业数据库相媲美的开源数据库。而且 PostgreSQL 的许可和生态完全开放,不被任何一个单一的公司或国家所操控,保证了使用者没有后顾之忧。国内越来越多的企业开始用 PostgreSQL 代替原来昂贵的国外商业数据库。

在部署 PostgreSQL 到生产环境中时,选择适合的高可用方案是一项必不可少的工作。本文介绍基于 Patroni 的 PostgreSQL 高可用的部署方法,供大家参考。

PostgreSQL 的开源 HA 工具有很多种,下面几种算是比较常用的

Patroni 优势

  • 支持自动 failover 和按需 switchover
  • 支持一个和多个备节点
  • 支持级联复制
  • 支持同步复制,异步复制
  • 支持同步复制下备库故障时自动降级为异步复制(功效类似于 MySQL 的半同步,但是更加智能)
  • 支持控制指定节点是否参与选主,是否参与负载均衡以及是否可以成为同步备机
  • 支持通过pg_rewind自动修复旧主
  • 支持多种方式初始化集群和重建备机,包括pg_basebackup和支持wal_epgBackRestbarman等备份工具的自定义脚本
  • 支持自定义外部 callback 脚本
  • 支持 REST API
  • 支持通过 watchdog 防止脑裂
  • 支持 k8s,docker 等容器化环境部署
  • 支持多种常见 DCS(Distributed Configuration Store)存储元数据,包括 etcd,ZooKeeper,Consul,Kubernetes

因此,除非只有 2 台机器没有多余机器部署 DCS 的情况,Patroni 是一款非常值得推荐的 PostgreSQL 高可用工具。

基于 Patroni 搭建 PostgreSQL 高可用环境

测试环境资源

  • 中标麒麟 7.4
  • PostgreSQL 12
  • Patroni 1.6.5
  • etcd 3.3.11
机器名IP角色资源
node1192.168.1.155PostgreSQL、Etcd6C 16G 200G
node2192.168.1.156PostgreSQL、Etcd6C 16G 200G
node3192.168.1.157PostgreSQL、Etcd6C 16G 200G
192.168.1.159VIP

环境准备

节点时间同步
yum install -y ntpdate
ntpdate time.windows.com && hwclock -w
防火墙开放端口
  • postgres:5432
  • patroni:8008
  • etcd:2379/2380
firewall-cmd --zone=public --add-port=5432/tcp --permanent (permanent永久生效,没有此参数重启后失效)
firewall-cmd --zone=public --add-port=8008/tcp --permanent (permanent永久生效,没有此参数重启后失效)
firewall-cmd --zone=public --add-port=2379/tcp --permanent (permanent永久生效,没有此参数重启后失效)
firewall-cmd --zone=public --add-port=2380/tcp --permanent (permanent永久生效,没有此参数重启后失效)
firewall-cmd --reload

# 或者关闭防火墙
关闭 selinux
setenforce 0
sed -i.bak "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

Etcd 安装部署

生产环境至少需要部署 3 个节点,可以使用独立的机器也可以和数据库部署在一起(以下内容,三节点都需要配置)。

安装依赖包
yum install -y gcc python-devel epel-release
安装 etcd
yum install -y etcd
编辑配置文件
# /etc/etcd/etcd.conf
ETCD_DATA_DIR="/home/etcd/data"				# 注意目录权限
ETCD_LISTEN_PEER_URLS="http://192.168.1.155:2380"  # 服务器各自IP地址
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.155:2379"
ETCD_NAME="etcd-1" #每个机器一样,与ETCD_INITIAL_CLUSTER对应匹配
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.155:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.155:2379"
ETCD_INITIAL_CLUSTER="etcd-1=http://192.168.1.155:2380,etcd-2=http://192.168.1.156:2380,etcd-3=http://192.168.1.157:2380" #集群总体IP地址
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"	#每个机器一样
ETCD_INITIAL_CLUSTER_STATE="new"	#新创建集群

验证 etcd 安装部署结果
# etcdctl --endpoints=http://192.168.1.155:2379,http://192.168.1.156:2379,http://192.168.1.157:2379 member list
4130f9ed59402db5: name=etcd-2 peerURLs=http://192.168.1.156:2380 clientURLs=http://192.168.1.156:2379 isLeader=false
4a98731394a09e4d: name=etcd-3 peerURLs=http://192.168.1.157:2380 clientURLs=http://192.168.1.157:2379 isLeader=false
b0af6b1d5bc0214d: name=etcd-1 peerURLs=http://192.168.1.155:2380 clientURLs=http://192.168.1.155:2379 isLeader=true
# etcdctl --endpoints=http://192.168.1.155:2379,http://192.168.1.156:2379,http://192.168.1.157:2379 cluster-health
member 4130f9ed59402db5 is healthy: got healthy result from http://192.168.1.156:2379
member 4a98731394a09e4d is healthy: got healthy result from http://192.168.1.157:2379
member b0af6b1d5bc0214d is healthy: got healthy result from http://192.168.1.155:2379

PostgreSQL + Patroni HA 部署

安装 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

# 由于中标麒麟非Redhat原版,需要修改yum的源文件pgdg-redhat-all.repo
sed -i 's/rhel-$releasever-$basearch/rhel-7.4-x86_64/g' /etc/yum.repos.d/pgdg-redhat-all.repo
安装 Patroni
yum install -y gcc epel-release
yum install -y python-pip python-psycopg2 python-devel

pip3 install --upgrade pip
pip3 install --upgrade setuptools
pip3 install psycopg2-binary
pip3 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
scope: pgsql
namespace: /service/
name: pa-pg-1
restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.155:8008
etcd:
  host: 192.168.1.155:2379
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10						# 循环更新领导者密钥过程中的休眠时间
    retry_timeout: 10					# etcd和PostgreSQL操作重试的超时时间(以秒为单位)
    maximum_lag_on_failover: 1048576	# 如果Master和Replicate之间的字节数延迟大于此值,那么Replicate将不参与新的领导者选举
    master_start_timeout: 300
    synchronous_mode: false				# 是否打开同步复制模式
    postgresql:							# PostgreSQL的配置,是否使用pg_rewind,是否使用复制插槽,还有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:								# 定义了在引导过程中要传递给initdb的选项
  - encoding: UTF8
  - locale: C
  - lc-ctype: zh_CN.UTF-8
  - data-checksums
  pg_hba:								# 定义了集群初始化后,pg_hba.conf中该设置的条目
  - 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					# 设置postgresql.conf参数listen_addresses和port
  connect_address: 192.168.1.155:5432	# 从其他节点和应用程序访问Postgres的地址和端口
  data_dir: /pgsql/data					# 集群的数据目录的存放路径
  bin_dir: /usr/pgsql-12/bin			# PostgreSQL二进制文件存放路径
  authentication:						# 定义用于复制的用户,超级用户
    replication:
      username: repl
      password: "eds1234*"
    superuser:
      username: postgres
      password: "eds1234*"
  basebackup:
    max-rate: 100M
    checkpoint: fast
tags:
    nofailover: false					# 不参与选主
    noloadbalance: false				# 不参与负载均衡
    clonefrom: false
    nosync: false						# 也不作为同步备库
验证安装结果
# postgres拥有免密的sudoer权限
echo 'postgres        ALL=(ALL)       NOPASSWD: ALL'> /etc/sudoers.d/postgres
# 启动patroni
systemctl start patroni
# 验证安装结果
[root@node1 ~]# patronictl -c /etc/patroni.yml list
+ Cluster: pgsql ---------+---------+---------+----+-----------+
| Member  | Host          | Role    | State   | TL | Lag in MB |
+---------+---------------+---------+---------+----+-----------+
| pa-pg-1 | 192.168.1.155 | Replica | running |  1 |         0 |
| pa-pg-2 | 192.168.1.156 | Leader  | running |  1 |           |
| pa-pg-3 | 192.168.1.157 | Replica | running |  1 |         0 |
+---------+---------------+---------+---------+----+-----------+
# 可配置环境变量
echo 'export PATRONICTL_CONFIG_FILE=/etc/patroni.yml' >/etc/profile.d/patroni.sh
source /etc/profile
[root@node1 ~]# patronictl list
+ Cluster: pgsql ---------+---------+---------+----+-----------+
| Member  | Host          | Role    | State   | TL | Lag in MB |
+---------+---------------+---------+---------+----+-----------+
| pa-pg-1 | 192.168.1.155 | Replica | running |  1 |         0 |
| pa-pg-2 | 192.168.1.156 | Leader  | running |  1 |           |
| pa-pg-3 | 192.168.1.157 | Replica | running |  1 |         0 |
+---------+---------------+---------+---------+----+-----------+

Patroni 高可用设定

客户端通过vip的方式访问PostgreSQL数据库可以多一层防护,Patroni支持用户配置在特定事件发生时触发的回调脚本。因此可以配置一个回调,在主备切换后动态加载vip。

创建回调脚本

/pgsql/loadvip.sh

#!/bin/bash

VIP=192.168.1.159		# 设定VIP地址
GATEWAY=192.168.1.1		# VIP地址对应的网关
DEV=enp3s0				# VIP地址绑定的网卡名称

action=$1
role=$2
cluster=$3


log()
{
  echo "$*"
  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配置文件
patronictl reload pgsql

Patroni 进阶设定

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 暂不生效
Patroni通过watchdog防止脑裂

为了更可靠的防止脑裂,Patroni支持通过Linux的watchdog监视patroni进程的运行,当patroni进程无法正常往watchdog设备写入心跳时,由watchdog触发Linux重启。

# /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
# /etc/patroni.yml
watchdog:
  mode: automatic # Allowed values: off, automatic, required
  device: /dev/watchdog
  safety_margin: 5
利用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集群中的一致性。

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调大不会带来其他副作用。

常用操作

日常维护时可以通过patronictl命令控制Patroni和PostgreSQL

  • 修改PostgreSQL参数

修改个别节点的参数,可以执行ALTER SYSTEM SET ... SQL命令,比如临时打开某个节点的debug日志。对于需要统一配置的参数应该通过patronictl edit-config设置,确保全局一致,比如修改最大连接数。

patronictl edit-config -p 'max_connections=300'  # 重启集群中所有PG实例后,参数生效。
patronictl restart pgsql

修改最大连接数后需要重启才能生效,因此Patroni会在相关的节点状态中设置一个Pending restart标志。

  • 查看Patroni节点状态

通常我们可以同patronictl list查看每个节点的状态。但是如果想要查看更详细的节点状态信息,需要调用REST API。比如在Leader锁过期时存活节点却无法成为Leader,查看详细的节点状态信息有助于调查原因。

curl -s http://127.0.0.1:8008/patroni
{
    "state": "running",
    "postmaster_start_time": "2023-06-06 11:45:52.638301+08:00",
    "role": "replica",
    "server_version": 120015,
    "xlog": {
        "received_location": 83886408,
        "replayed_location": 83886408,
        "replayed_timestamp": null,
        "paused": false
    },
    "timeline": 1,
    "dcs_last_seen": 1686034676,
    "database_system_identifier": "7241411695739765672",
    "patroni": {
        "version": "3.0.2",
        "scope": "pgsql"
    }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
除了基本的关系型数据库功能外,PostgreSQL还提供了许多高级功能,让它成为一个非常强大和灵活的数据库管理系统。以下是一些PostgreSQL的高级功能: 1. 大型对象(Large Objects,LO):LO是PostgreSQL特有的一种数据类型,它可以保存任意长度的二进制数据,例如图像、音频、视频等等。LO支持随机访问和部分读写,可以方便地对大型数据进行处理和存储。 2. 全文搜索(Full Text Search):PostgreSQL提供了全文索引和全文搜索功能,可以快速地在文本数据中搜索关键字或短语。全文搜索支持多种语言和多种搜索方式,可以方便地应用于文档管理、知识库、搜索引擎等场景。 3. GIS和空间数据(GIS and Spatial Data):PostgreSQL提供了PostGIS扩展,支持GIS(地理信息系统)和空间数据的存储和查询。PostGIS支持各种空间数据类型和空间算法,可以方便地进行地理空间分析、地图制作、位置服务等操作。 4. 多版本并发控制(MVCC):PostgreSQL是一种基于MVCC的数据库管理系统,它可以支持多个事务并发执行,并且保证数据的一致性和隔离性。MVCC可以避免锁定和死锁等问题,提高了系统的并发性和可靠性。 5. 多种语言扩展(Multi-Language Extensions):PostgreSQL支持多种编程语言的扩展,包括PL/Python、PL/Java、PL/Perl、PL/R等等。这些扩展可以让开发人员在数据库中直接编写和执行自己的代码,提高了系统的灵活性和可扩展性。 6. 复制和高可用性(Replication and High Availability):PostgreSQL提供了复制和流复制功能,可以将数据复制到多个节点上,实现数据的备份、负载均衡、容错等功能。同时,PostgreSQL还支持各种高可用性方案,例如基于Pacemaker和Corosync的集群、基于Patroni的自动故障转移等等。 除了以上功能,PostgreSQL还支持各种高级查询和优化技术、安全性和权限管理、性能监控和调优等功能,为开发人员和系统管理员提供了丰富的工具和功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值