高可用
一、意义
- 显著提高系统整体可用性,提高RTO与RPO水平。
- 极大提高运维灵活性与可演化性,可以通过主动切换进行滚动升级,灰度停机维护。
- 极大提高系统可维护性,自动维护域名,服务,角色,机器,监控等系统间的一致性。显著减少运维工作量,降低管理成本
二、目标
当我们在说高可用时,究竟在说什么?Several nines ?
说到底,对于传统单领导者数据库来说,核心问题是就是故障切换,是领导权力交接的问题。
目标层次
- L0,手工操作,完全通过DBA人工介入,手工操作完成故障切换(十几分钟到小时级)
- L1,辅助操作,有一系列手工脚本,完成选主,拓扑切换,流量切换等操作(几分钟)
- L2,半自动化,自动检测,人工决策,自动操作。(1分钟)
- L3,全自动化:自动检测,自动决策,自动操作。(10s)
关键指标
- 允许进行日常Failover与Switchover操作,不允许出现脑裂。
- 无需客户端介入,提供代理切换机制,基于流复制,不依赖特殊硬件。
- 域名解析,VIP流量切换,服务发现,监控适配都需要与自动故障切换对接,做到自动化。
- 支持PG 10~12版本与CentOS 7,不会给云原生改造埋坑。
交付方式
- 沙盒模型,展示期待的部署架构与状态
- 调整方案,说明如何将现有环境调整至理想状态。
三、效果
场景演示
集群状况介绍
- 主库URL:
postgres://dbuser_test:dbuser_test@testdb:5555/testdb
- 从库URL:
postgres://dbuser_test:dbuser_test@testdb:5556/testdb
HA的两个核心场景:
- Switchover演示
- Failover演示
故障切换的四个核心问题:
- 故障检测(Lease, TTL,Patroni向DCS获取Leader Key)
- Fencing(Patroni demote,kill PG进程,或通过Watchdog直接重启)
- 拓扑调整(通过DCS选主,其他从库从DCS获取新主库信息,修改自身复制源并重启生效)
- 流量切换(监听选主事件,通知网络层修改解析)
Patroni原理:故障检测
- 基于DCS判定
- 心跳包保活
- Leader Key Lease
- 秦失其鹿,天下共逐之。
Patroni原理:Fencing
- 一山不容二虎,成王败寇,血腥的权力交接。
Patroni原理:选主
- The king is dead, long live the king
- 先入关者王
流量切换原理
- 回调事件,或监听DCS变化。
搭建环境
https://github.com/Vonng/pigsty
五、细节,问题,与风险
场景演示
- Switchover
- Standby Down
- Patroni Down
- Postgres Down
- Accidentally Promote
- Primary Down
- Failover
- DCS Down
- DCS Service Down
- DCS Primary Client Down
- DCS Standby Client Down
- Fencing And corner cases
- Standby Cluster
- Sync Standby
- Takeover existing cluster
问题探讨
关键问题:DCS的SLA如何保障?
==在自动切换模式下,如果DCS挂了,当前主库会在retry_timeout 后Demote成从库,导致所有集群不可写==。
作为分布式共识数据库,Consul/Etcd是相当稳健的,但仍必须确保DCS的SLA高于DB的SLA。
解决方法:配置一个足够大的retry_timeout
,并通过几种以下方式从管理上解决此问题。
- SLA确保DCS一年的不可用时间短于该时长
- 运维人员能确保在
retry_timeout
之内解决DCS Service Down的问题。 - DBA能确保在
retry_timeout
之内将关闭集群的自动切换功能(打开维护模式)。
可以优化的点? 添加绕开DCS的P2P检测,如果主库意识到自己所处的分区仍为Major分区,不触发操作。
关键问题:HA策略,RPO优先或RTO优先?
可用性与一致性谁优先?例如,普通库RTO优先,金融支付类RPO优先。
普通库允许紧急故障切换时丢失极少量数据(阈值可配置,例如最近1M写入)
与钱相关的库不允许丢数据,相应地在故障切换时需要更多更审慎的检查或人工介入。
关键问题:Fencing机制,是否允许关机?
在正常情况下,Patroni会在发生Leader Change时先执行Primary Fencing,通过杀掉PG进程的方式进行。
但在某些极端情况下,比如vm暂停,软件Bug,或者极高负载,有可能没法成功完成这一点。那么就需要通过重启机器的方式一了百了。是否可以接受?在极端环境下会有怎样的表现?
关键操作:选主之后
选主之后要记得存盘。手工做一次Checkpoint确保万无一失。
关键问题:流量切换怎样做,2层,4层,7层
- 2层:VIP漂移
- 4层:Haproxy分发
- 7层:DNS域名解析
关键问题:一主一从的特殊场景
- 2层:VIP漂移
- 4层:Haproxy分发
- 7层:DNS域名解析
切换流程细节
主动切换流程
假设集群包括一台主库P,n台从库S,所有从库直接挂载在主库上。
- 检测:主动切换不需要检测故障
- 选主:人工从集群中选择复制延迟最低的从库,将其作为候选主库(C)andidate。
- 拓扑调整
- 修改主库P配置,使得C成为同步从库,使切换RTO = 0。
- 重定向其他从库,将其
primary_conninfo
指向C,作为级连从库,滚动重启生效。
- 流量切换:需要快速自动化执行以下步骤
- Fencing P,停止当前主库P,视流量来源决定手段狠辣程度
- PAUSE Pgbouncer连接池
- 修改P的HBA文件并Reload
- 停止Postgres服务。
- 确认无法写入
- Promote C:提升候选主库C为新主库
- 移除standby.signal 或 recovery.conf。执行promote
- 如果Promote失败,重启P完成回滚。
- 如果Promote成功,执行以下任务:
- 自动生成候选主库C的新角色域名:
.primary.
- 调整集群主库域名/VIP解析:
primary.
,指向C - 调整集群从库域名/VIP解析:
standby.
,摘除C(一主一从除外) - 根据新的角色域名重置监控(修改Consul Node名称并重启)
- Rewind P:(可选)将旧主库Rewind后作为新从库
- 运行
pg_rewind
,如果成功则继续,如果失败则直接重做从库。 - 修改
recovery.conf(12-)|postgresql.auto.conf(12)
,将其primary_conninfo
指向C - 自动生成P的新角色域名:
< max(standby_sequence) + 1>.standby.
- 集群从库域名/VIP解析变更:
standby.
,向S中添加P,承接读流量 - 根据角色域名重置监控
- 运行
- Fencing P,停止当前主库P,视流量来源决定手段狠辣程度
自动切换流程
自动切换的核心区别在于主库不可用。如果主库可用,那么完全同主动切换一样即可。 自动切换相比之下要多了两个问题,即检测与选主的问题,同时拓扑调整也因为主库不可用而有所区别。
- 检测
(网络不可达,端口拒绝连接,进程消失,无法写入,多个从库上的WAL Receiver断开)
- 实现:检测可以使用主动/定时脚本,也可以直接访问
pg_exporter
,或者由Agent定期向DCS汇报。 - 触发:主动式检测触发,或监听DCS事件。触发结果可以是调用中控机上的HA脚本进行集中式调整,也可以由Agent进行本机操作。
- 实现:检测可以使用主动/定时脚本,也可以直接访问
- 选主
- Fencing P:同手动切换,因为自动切换中主库不可用,无法修改同步提交配置,因此存在RPO > 0 的可能性。
- 遍历所有可达从库,找出LSN最大者,选定为C,最小化RPO。
- 流量切换:需要快速自动化执行以下步骤
- Promote C:提升候选主库C为新主库
- 移除standby.signal 或 recovery.conf。执行promote
- 自动生成候选主库C的新角色域名:
.primary.
- 调整集群主库域名/VIP解析:
primary.
,指向C - 调整集群从库域名/VIP解析:
standby.
,摘除C(一主一从除外) - 根据新的角色域名重置监控(修改Consul Node名称并重启)
- Promote C:提升候选主库C为新主库
- 拓扑调整
- 重定向其他从库,将其
primary_conninfo
指向C,作为级连从库,滚动重启生效,并追赶新主库C。 - 如果使用一主一从,之前C仍然承接读流量,则拓扑调整完成后将C摘除。
- 重定向其他从库,将其
- 修复旧主库P(如果是一主一从配置且读写负载单台C撑不住,则需要立刻进行,否则这一步不紧急)
- 修复有以下两种方式:Rewind,Remake
- Rewind P:(可选)将旧主库Rewind后作为新从库(如果只有一主一从则是必选)
- 运行
pg_rewind
,如果成功则继续,如果失败则直接重做从库。 - 修改
recovery.conf(12-)|postgresql.auto.conf(12)
,将其primary_conninfo
指向C - 自动生成P的新角色域名:
< max(standby_sequence) + 1>.standby.
- 集群从库域名/VIP解析变更:
standby.
,向S中添加P,承接读流量 - 根据角色域名重置监控
- 运行
- Remake P:
- 以新角色域名
< max(standby_sequence) + 1>.standby.
向集群添加新从库。
- 以新角色域名