ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Patroni 复制模式完全指南:从异步复制到同步模式与 Quorum 提交

Patroni 复制模式完全指南:从异步复制到同步模式与 Quorum 提交 数据库高可用集群管理运维后端【免费下载链接】patroniA template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes项目地址https://gitcode.com/gh_mirrors/pa/patroni点击查看免费下载导读本文以 Patroni 官方文档 docs/replication_modes.rst 为骨架系统讲解 Patroni 基于 PostgreSQL 流复制的三种数据保护形态——异步模式、同步模式Synchronous Mode与 Quorum 提交模式Quorum Commit Mode并深入剖析synchronous_mode、synchronous_mode_strict、synchronous_node_count、maximum_lag_on_syncnode等关键参数背后的实现原理。读完本文你将能根据自己的业务对数据丢失容忍度RPO与写入延迟的权衡正确选择复制模式、配置同步副本数量并理解 DCS 中/sync键如何保障「已提交事务不丢失」这一核心不变量。一、概述Patroni 复制模式的决策框架Patroni 本身不实现复制协议而是使用PostgreSQL 原生的流复制streaming replication作为数据复制的基础。默认情况下Patroni 将 PostgreSQL 配置为异步复制。选择哪种复制方案取决于业务诉求异步复制换来的是高可用性和低写入延迟代价是可能丢失已提交事务同步复制则保证数据多节点持久化代价是写入延迟上升、吞吐下降且对网络稳定性高度敏感。在选型之前建议同时调研异步、同步以及各类 HA 方案结合自身业务做权衡。从配置层面看复制模式由以下动态配置参数协同决定参数默认值作用synchronous_modeoff可选on/off/quorum控制 Patroni 是否接管同步复制管理synchronous_mode_strictfalse强制主节点在任何时刻都要求至少一个同步副本否则阻塞写入synchronous_node_count1Patroni 管理的同步备库数量仅在synchronous_mode开启时生效maximum_lag_on_syncnode-1同步备库允许的最大滞后用于决定是否替换不健康的同步备库maximum_lag_on_failover10485761MB异步模式下允许的最大复制滞后超过则不作为故障切换候选check_timelinefalse选主时是否校验候选节点的 timeline 与前任主节点一致这些参数属于动态配置可以通过patronictl edit-config命令或 Patroni REST 接口在线修改无需重启集群详见 docs/dynamic_configuration.rst。二、异步模式Asynchronous Mode的持久性边界在异步模式下集群为了保证可用性允许丢失一部分已提交事务。当主节点故障或不可达时Patroni 会自动将一个足够健康的备库提升为新主节点。任何尚未复制到该备库的事务会残留在旧主节点的 forked timeline分叉时间线上这些数据实际上不可恢复 [1]。这里需要准确理解「会丢失多少数据」的边界。丢失量由maximum_lag_on_failover参数控制由于主节点的事务日志位置并不是实时采样的因此故障切换时数据丢失量的最坏情况是maximum_lag_on_failover字节的事务日志再加上最近ttl秒内写入的量平均情况为loop_wait/2 秒。不过在正常稳定运行状态下复制延迟通常远小于 1 秒。也就是说这个参数并非精确的丢失上限而是一个「采样间隔 参数值」共同决定的经验上界。从源码看maximum_lag_on_failover的默认值在 patroni/global_config.py 中被实现为property def maximum_lag_on_failover(self) - int: Currently configured value of maximum_lag_on_failover from the global configuration. Assume 1048576 if it is not set or invalid. return self.get_int(maximum_lag_on_failover, 1048576)另外还有一个容易忽略的细节默认情况下Patroni 在选主时不会考虑副本当前的 timeline。这在某些场景下可能是不希望出现的行为。如果你不希望「timeline 与前任主节点不一致的节点成为新主节点」可以把check_timeline参数设置为true。对应的实现在 patroni/ha.py 中def check_timeline(self) - bool: ... return global_config.check_mode(check_timeline)在 patroni/ha.py 与 (patroni/ha.py#L1542) 处HA 循环会在候选节点时间线落后于集群时间线时将其排除在提升范围之外。三、直接使用 PostgreSQL 原生同步复制Synchronous ReplicationPatroni 同样支持直接透传使用 PostgreSQL 原生的同步复制。同步复制通过「写操作必须先复制到备库并确认才向客户端返回成功」来保证集群内一致性。其代价是写入延迟升高、吞吐下降且吞吐完全取决于网络性能。在托管数据中心环境如 AWS、Rackspace 等不受你控制的网络中同步复制会显著增加写性能的波动一旦备库与主节点不可达主节点实际上会变成只读。如果需要做一个简单的同步复制测试可以在 YAML 配置文件的parameters段中加入以下两行synchronous_commit: on synchronous_standby_names: *使用 PostgreSQL 原生同步复制时至少需要三个 PostgreSQL 数据节点才能保证某个主机故障时写入仍然可用否则主备同时故障时没有第三个节点可提升或写路径完全阻塞。需要特别指出使用 PostgreSQL 同步复制并不能在所有情况下保证零事务丢失当主节点与当前充当同步副本的备库同时故障时第三个节点可能并未包含全部事务此时它会被提升从而产生丢失。原生同步复制与 Patroni 同步模式的区别这是理解本文的关键分水岭上面的synchronous_standby_names: *属于静态配置同步备库由 PostgreSQL 自行按优先级选择Patroni 不介入管理而接下来介绍的Synchronous Mode同步模式是 Patroni 主动接管synchronous_standby_names的动态管理由 Patroni 根据 DCS 中的/sync键和pg_stat_replication状态实时决定谁是同步备库。当synchronous_mode开启时Patroni 会动态生成synchronous_standby_names并写入 PostgreSQL 配置覆盖用户静态配置见下文第四节源码佐证。四、Synchronous ModePatroni 托管同步复制对于「已提交事务不允许丢失」的场景可以开启 Patroni 的synchronous_mode。当它开启时Patroni 只有在确定备库包含所有可能已向客户端返回成功提交状态的事务后才会提升该备库[2]。这意味着即使部分服务器可用系统也可能无法写入——可用性让位于一致性。4.1 基本行为与双节点集群的可用性开启synchronous_mode并不能在所有情况下保证多节点持久性当没有合适的备库可用时主节点仍会接受写入但不保证这些写入被复制此时如果主节点故障不会有任何备库被自动提升当旧主节点宿主机恢复时它会自动重新被提升为主节点除非管理员执行了手动故障切换。正是这种「无同步备库时不自动提升、原主回归时自动恢复」的行为使得同步模式在2 节点集群中也可用。另一个重要行为当synchronous_mode开启、某个备库崩溃时提交会阻塞直到 Patroni 下一轮 HA 循环运行并将主节点切换到 standalone 模式写入的最坏延迟为ttl秒平均为loop_wait/2 秒。但手动关闭或重启备库不会造成提交服务中断——备库会在 PostgreSQL 关闭启动前先向主节点发出信号将自己从同步备库职责中释放。4.2 synchronous_mode_strict强制最小复制因子当确实需要保证每次写入都持久存储在至少两个节点上时应在synchronous_mode之外再开启synchronous_mode_strict。该参数会阻止 Patroni 在没有同步备库候选时关闭主节点上的同步复制除非事务显式将synchronous_commit设为 off从而阻塞所有客户端写入请求直到至少一个同步副本上线。从源码看synchronous_mode_strict的核心影响体现在 patroni/global_config.py 的min_synchronous_nodes属性上——严格模式下最小同步节点数为 1非严格模式下为 0property def min_synchronous_nodes(self) - int: The minimum number of synchronous nodes based on whether synchronous_mode_strict is enabled or not. return 1 if self.is_synchronous_mode_strict else 0同时patroni/postgresql/config.py 展示了 Patroni 如何在下发服务器参数时处理synchronous_standby_names在同步模式开启且严格模式生效、角色为主节点时若用户未显式配置synchronous_standby_namesPatroni 会写入SYNC_STRICT_PLACEHOLDER哨兵值否则移除该参数交由运行时动态管理。4.3 严格模式下的 synchronous_standby_names 决策规则当synchronous_mode_strict开启且当前没有活跃复制连接满足最小复制因子时Patroni 会按以下三条规则决定synchronous_standby_names的值对应实现见 patroni/ha.py 的_handle_synchronous_strict_mode规则 1/sync 键中存在最后已知的同步节点Patroni 将或保持synchronous_standby_names指向 DCS/sync键中记录的最后已知同步节点。例如若/sync键内容为leadernode1, sync_standbynode2,node3且两个备库都停止流复制Patroni 仍会继续使用synchronous_standby_names node2,node3因为这些节点是最后已知收到最新提交的节点。提交将阻塞直到其中至少一个重新连接。规则 2手动故障切换到异步节点当某个不在/sync键中的节点被提升例如通过patronictl failover --forcePatroni 会将synchronous_standby_names设置为前任主节点因为前任主节点是唯一保证拥有最新已提交数据的节点。规则 3/sync 键为空例如严格模式刚启用、集群刚初始化且还没有任何副本时Patroni 会设置synchronous_standby_names __patroni_strict_sync_replica_placeholder__这是一个内置的哨兵值不匹配任何真实节点名从而使所有写入阻塞直到有合格副本开始从主节点流复制。该哨兵值取代了原先的*通配符——通配符可能让一个不合格的节点意外满足同步要求。这个哨兵值在源码中定义于 patroni/postgresql/sync.pySYNC_STRICT_PLACEHOLDER __patroni_strict_sync_replica_placeholder__哨兵值在解析时会被视为空成员集见 patroni/postgresql/sync.py 的 doctestparse_sync_standby_names(ANY 1(__patroni_strict_sync_replica_placeholder__)).members set()因此没有任何真实节点能「匹配」它。警告__patroni_strict_sync_replica_placeholder__是 Patroni 保留值绝不能用作patroni.yaml中节点的name。如果配置了该名称Patroni 将拒绝启动——这一校验实现在 patroni/validator.py 的validate_name中会直接抛出ConfigParseError。当严格模式生效时Patroni 会输出日志警告No active replication connections and synchronous_mode_strict is requested. Commits will be delayed.该警告每个激活事件只输出一次而不是每个 HA 循环迭代都输出源码中通过self._synchronous_strict_mode_activated标志实现见 patroni/ha.py。4.4 排除不合格节点nosync 与 nostream 标签可以通过将某个备库的nosync标签设为true确保它永远不会成为同步备库。对于位于慢速网络后面、成为同步备库会造成性能下降的备库强烈建议这样设置。设置nostream标签为true也会产生相同的效果。从源码看_ReplicaList构建候选列表时直接过滤掉了设置了nosync的成员patroni/postgresql/sync.py而replicatefrom标签指定的级联备库也不会被纳入同步候选。4.5 同步模式的开关方式Synchronous mode可以通过patronictl edit-config命令或 Patroni REST 接口动态开启/关闭具体操作方式参见 docs/dynamic_configuration.rst。4.6 严格模式的残余风险重要免责声明由于 PostgreSQL 同步复制的实现方式即使使用synchronous_mode_strict仍可能丢失事务当 PostgreSQL 后端在等待复制确认期间被取消例如因客户端超时导致的报文取消或后端故障事务变更对其他后端变得可见。这些尚未复制的变更在备库提升时可能丢失。也就是说严格模式消除的是「没有同步副本时继续接受写入」这一路径的风险但无法消除 PostgreSQL 同步复制协议本身在取消/故障窗口内的固有风险。五、Synchronous Replication Factorsynchronous_node_countPatroni使用synchronous_node_count参数管理同步备库的数量默认值为1。当synchronous_mode为off时该参数不生效。开启后Patroni 会根据synchronous_node_count精确管理同步备库数量并在成员加入/离开时同步调整 DCS 中的状态与 PostgreSQL 的synchronous_standby_names。如果该参数设置的值高于合格节点的数量Patroni 会自动将其降低到可用节点数。源码中的实现位于 patroni/global_config.pyproperty def synchronous_node_count(self) - int: Currently configured value of synchronous_node_count from the global configuration. Assume 1 if it is not set or invalid. return max(self.get_int(synchronous_node_count, 1), self.min_synchronous_nodes)注意synchronous_node_count会与min_synchronous_nodes严格模式下为 1取最大值这意味着开启synchronous_mode_strict时即使配置值为 1也至少要求一个同步节点。该参数在验证器中的约束为IntValidator(min1)patroni/validator.py即最小合法值为 1。多同步备库场景下Patroni 会为 PostgreSQL 生成类似FIRST 2 (node1,node2)的synchronous_standby_names见下节示例。六、Maximum Lag on Synchronous Nodemaximum_lag_on_syncnode默认情况下Patroni 倾向于保持那些根据pg_stat_replication视图被声明为synchronous的节点即使有其他节点复制进度超前也不会轻易切换。这样做的目的是尽量减少synchronous_standby_names的变动频率。要改变这一行为可以使用maximum_lag_on_syncnode参数。它控制备库允许的最大滞后超过该值则不再被视为「同步」候选。Patroni 在比较时若存在多个备库则使用最大备库 LSN作为基准否则使用主节点当前的 WAL LSN。默认值为-1当值设置为0或更小时Patroni 不会采取行动去替换不健康的同步备库。建议将该值设置得足够高避免在事务高峰期间频繁替换同步备库。源码实现在 patroni/global_config.py默认-1其选型逻辑在 patroni/postgresql/sync.py 的_ReplicaList中候选列表按sync_priority、sync_state、LSN 降序排序先保留已经是sync状态的节点即使它们不是最新只有滞后超过阈值时才会触发同步备库的替换。相关注释明确写道Values are reverse ordered by_Replica.sync_stateand_Replica.lsn. That is, first there will be replicas that havesync_statesync, even if they are not the most up-to-date... Such cases would trigger sync standby member swapping, but only if lag on this member is exceeding a threshold (maximum_lag_on_syncnode).同时该参数在验证器中的约束为IntValidator(min-1)patroni/validator.py合法值域为 -1。七、Synchronous Mode 的实现原理DCS /sync 键与不变量在同步模式下Patroni 将同步状态维护在 DCS 的/sync键中包含最新的主节点和当前同步备库列表。该状态通过严格的顺序约束更新以保证以下三个不变量只要节点能接受写事务就必须被标记为最新主节点leader。Patroni 崩溃或 PostgreSQL 未正常关闭可能导致该不变量被破坏只要节点被发布为 DCS/sync键中的同步备库就必须同时被设置为 PostgreSQL 中的同步备库不是主节点或当前同步备库的节点不允许自动提升自己。Patroni 只会根据synchronous_node_count参数分配一个或多个节点到synchronous_standby_names。在每个 HA 循环迭代中Patroni 都会重新评估同步备库的选择如果当前同步备库列表中的节点仍处于连接状态且未请求移除同步状态则保持原选择否则从可供同步的集群成员中挑选复制进度最靠前的节点。7.1 完整示例synchronous_modeonsynchronous_node_count2假设集群有三个节点node0主、node1、node2同步备库则三个位置的配置/状态如下/config键DCSsynchronous_mode: on synchronous_node_count: 2 .../sync键DCS{ leader: node0, sync_standby: node1,node2 }postgresql.confsynchronous_standby_names FIRST 2 (node1,node2)在上面的例子中只有node1和node2被认定是同步的并且当主节点node0故障时只有它们允许被自动提升。7.2 同步状态的源码级佐证/sync键的状态由 patroni/dcs 各实现维护同步备库的选择与synchronous_standby_names的生成由 patroni/postgresql/sync.py 的SyncHandler负责。其中值得注意的实现细节是当synchronous_standby_names发生变化时Patroni 会记下_primary_flush_lsn新加入的节点只有在追赶到该 LSN 且被pg_stat_replication报告为 sync之后才会被计为「确认同步」——这保证了新增同步备库不会出现「名义上是同步、实际落后」的窗口见 patroni/postgresql/sync.py。八、Quorum Commit Mode基于法定人数的同步复制从PostgreSQL v10开始Patroni 支持基于法定人数quorum的同步复制。8.1 工作原理在该模式下Patroni 在 DCS 中维护同步状态包含最新已知主节点、达成法定人数所需的节点数quorum、当前有资格参与法定人数投票的节点voters。在稳定状态下参与投票的节点是主节点 所有同步备库。该状态同样通过严格的顺序约束更新涉及节点提升与synchronous_standby_names的变更顺序以确保任何时候能达成法定人数的任何投票子集都至少包含一个拥有最新成功提交的节点。在每个 HA 循环迭代中Patroni 根据节点可用性和集群配置重新评估同步备库选择与法定人数。在 PostgreSQL 9.6 以上的版本中所有合格节点一旦复制进度追平主节点就会被加入同步备库列表。Quorum 提交模式的核心收益是降低最坏情况下的写延迟即使复制到某一个备库的延迟较高其他备库可以补偿从而避免单点延迟拖垮整体写入性能。8.2 启用方式通过patronictl edit-config命令或 Patroni REST 接口将synchronous_mode设置为quorumsynchronous_mode: quorum源码中对应的判定在 patroni/global_config.pyproperty def is_quorum_commit_mode(self) - bool: :returns: True if quorum commit replication is requested return str(self.get(synchronous_mode)).lower() quorum property def is_synchronous_mode(self) - bool: True if synchronous replication is requested and it is not a standby cluster config. return (self.check_mode(synchronous_mode) is True or self.is_quorum_commit_mode) \ and not self.is_standby_cluster可以看到synchronous_mode: quorum同样被视为同步模式的一种形态is_synchronous_mode为真因此synchronous_node_count、maximum_lag_on_syncnode、synchronous_mode_strict等参数在 quorum 模式下继续以相同方式工作。在 quorum 模式下启用synchronous_mode_strict时若没有活跃副本Patroni 将synchronous_standby_names设置为ANY N (最后已知投票者)保留/sync中最后已知的 quorum 投票者或在/sync键中无任何投票者时设置为ANY 1 (__patroni_strict_sync_replica_placeholder__)。8.3 完整示例quorum 模式synchronous_node_count2/config键DCSsynchronous_mode: quorum synchronous_node_count: 2 .../sync键DCS{ leader: node0, sync_standby: node1,node2,node3, quorum: 1 }postgresql.confsynchronous_standby_names ANY 2 (node1,node2,node3)解读如果主节点node0故障上例中的node1、node2、node3中有两个节点拥有最新事务但我们不知道具体是哪两个。要判断node1是否拥有最新事务需要将其 LSN 与node2、node3中至少一个节点/sync键中quorum1的 LSN 进行比较。只要node1不比它们中的至少一个落后就可以保证提升node1不会产生用户可见的数据丢失。8.4 Quorum 状态变更的顺序约束patroni/quorum.py 详细记录了状态迁移必须遵守的先后顺序以保证「quorum sync 投票者总数」这一不变量增加synchronous_node_count时先增加synchronous_standby_namesnumsync再减小/sync键中的quorum字段减少synchronous_node_count时先增加/sync键中的quorum字段再减小synchronous_standby_namesnumsync添加新节点时若synchronous_standby_names为空先加入sync再加入voters否则先加入voters再加入sync移除节点时若移除后synchronous_standby_names将为空先从voters移除再移除sync否则先从sync移除再移除voters。这套规则的实质是任何导致「可确认最新提交的节点集合」缩小的操作降低numsync或quorum都必须滞后执行从而避免在变更过程中出现「法定人数内没有节点拥有最新提交」的危险窗口。九、故障切换与数据恢复的补充说明异步模式下残留在分叉时间线上的数据并非物理消失但恢复它需要数据恢复专家的手动恢复工作。当 Patroni 被允许使用use_pg_rewind执行 rewind 时分叉时间线会被自动擦除以让故障主节点重新加入集群。不过use_pg_rewind要正常工作集群必须满足以下条件之一使用initdb的--data-checksums选项初始化即启用data page checksums和/或wal_log_hints设置为on。十、参数速查表与选型建议场景推荐配置说明允许少量事务丢失追求最高可用性与写入性能默认synchronous_mode关闭丢失上界约为maximum_lag_on_failover 最近ttl秒写入量不允许丢失已提交事务可接受写阻塞synchronous_mode: on配合 2 节点集群即可工作无同步备库时自动提升被禁止必须保证每次写入持久化在至少两个节点synchronous_mode: onsynchronous_mode_strict: true无同步副本时阻塞所有写入多备库且希望摊平单节点复制延迟synchronous_mode: quorum要求 PostgreSQL v10精确控制同步副本数量synchronous_node_count: N默认 1仅在同步模式开启时生效避免频繁更换同步备库maximum_lag_on_syncnode设置合理值默认 -1 表示不主动替换防止 timeline 不一致节点当选check_timeline: true选主时校验时间线排除慢速网络备库参与同步设置nosync: true或nostream: true标签见 docs/patroni_configuration.rst 中 tags 部分最后再强调两条贯穿全文的实践原则复制模式本质是可用性与数据持久性的权衡。异步模式优先可用性允许丢失、同步模式优先持久性可能阻塞写入、quorum 模式在两者之间提供更平滑的延迟体验不存在绝对零丢失。无论是 PostgreSQL 原生同步复制、Patronisynchronous_mode还是synchronous_mode_strict都存在由 PostgreSQL 复制协议固有缺陷如等待确认期间的后端取消带来的残余丢失风险。生产环境中应结合备份如 docs/backup.rst 相关方案、监控与灾难恢复演练共同保障数据安全。脚注[1] 数据仍然存在但恢复它需要数据恢复专家的人工介入。当 Patroni 允许使用use_pg_rewind执行 rewind 时分叉时间线会被自动擦除以让故障主节点重新加入集群但use_pg_rewind正常工作的前提是集群以data page checksums初始化initdb --data-checksums和/或wal_log_hintson。[2] 客户端可以通过 PostgreSQL 的synchronous_commit设置按事务改变行为。synchronous_commit为off或local的事务在故障切换时可能丢失但不会被复制延迟阻塞。延伸阅读docs/dynamic_configuration.rst动态配置与patronictl edit-config使用说明docs/patroni_configuration.rst全部配置参数详解含 tags 与use_pg_rewinddocs/standby_cluster.rst备集群standby cluster与同步模式的关系patroni/quorum.pyQuorum 状态迁移的核心实现patroni/postgresql/sync.py同步备库选择与synchronous_standby_names生成patroni/ha.pyHA 循环与严格模式处理_handle_synchronous_strict_modetests/test_sync.py同步模式的测试覆盖含哨兵值与 quorum 场景赞分享数据库高可用集群管理运维后端【免费下载链接】patroniA template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes项目地址https://gitcode.com/gh_mirrors/pa/patroni点击查看免费下载相关推荐一键VPS系统重装神器reinstall颠覆传统运维体验一键VPS系统重装神器reinstall颠覆传统运维体验 还在为VPS系统重装的复杂流程而烦恼吗面对不同Linux发行版和Windows系统的切换需求传统运维CLIKeyDB异步复制replication模块的并发同步机制KeyDB异步复制replication模块的并发同步机制 引言多线程数据库的复制挑战 在高并发场景下Redis单线程复制架构面临两大核心痛点 同步阻塞数据库KV存储缓存数据存储CloudNativePG 复制机制深度解析流式复制、同步复制与复制槽CloudNativePG 复制机制深度解析流式复制、同步复制与复制槽 本文以 CloudNativePG 的 Replication 官方文档为主线系统讲云原生数据库高可用灾备容器编排上一篇思源宋体完整使用教程免费开源中文字体的终极指南下一篇Vue3后台管理系统模板5分钟快速搭建企业级管理后台的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表