
点击蓝字关注我们
PGD是一个active/active或multi-master DBMS。如果采用异步模式,则在使用标准数据类型时,从多个不同节点写入相同或相关的行可能会导致数据冲突。
冲突不是错误,有效解决冲突是PGD保持一致性的核心,PGD就是为处理冲突而设计的。在大多数情况下,PGD可以在这些事件发生时检测并解决。解决方案取决于应用程序的性质和数据的含义,因此 PGD 为应用程序提供一系列关于如何解决这些问题的选择非常重要。
默认情况下,冲突在行级别解决。当两个节点的更改发生冲突时,将选取本地或远程元组,并丢弃另一个元组。例如,可以比较两个冲突更改的提交时间戳,并保留较新的更改。这种方法可确保所有节点收敛到相同的结果,并在整个集群上建立commit-order-like语义。PGD也提供列级冲突检测和解决方案,如果要避免冲突,可以使用group commit with eager conflict resolution或者无冲突数据类型(CRDTs)实现。
冲突处理是可配置的,PGD可以检测冲突,并使用冲突触发器对每个表进行不同的处理。
默认情况下,所有冲突都记录到 bdr.conflict_history。如果可能发生冲突,则表所有者必须监视它们并分析如何避免它们或制定计划以定期处理。LiveCompare工具也可用于定期扫描差异性。
有些群集系统使用分布式锁机制来防止对数据的并发访问。当服务器彼此非常接近时,这些可以合理地执行,但不能很好地支持地理位置分散的应用程序,在这些应用程序中,在可接受的性能情况下极低的延迟至关重要。
分布式锁定本质上是一种悲观的方法。PGD提倡一种乐观的方法,即尽可能避免冲突,但允许某些类型的冲突发生,并在冲突出现时解决它们。
PGD没有分布式事务管理器或锁管理器。这就是为什么它在延迟和网络分区方面表现良好的部分原因。因此,使用默认值(惰性复制-lazy replication)时,不同节点上的事务完全彼此独立执行。节点之间的独立性较低可以完全避免冲突,这就是为什么 PGD 在重要的时候也提供 Eager Replication。
一、 冲突类型及解决程序
理想情况下应该避免冲突,但重要的是要知道,当冲突发生时,它们会由PGD的内置和可配置的冲突解决程序进行记录和管理。
视图 bdr.node_conflict_resolvers 提供有关PGD当前如何为所有已知冲突类型配置冲突解决方案的信息。
bdrdb=# select * from bdr.node_conflict_resolvers ;conflict_type | conflict_resolver---------------------------+--------------------------insert_exists | update_if_newerupdate_differing | update_if_newerupdate_missing | insert_or_skipdelete_missing | skipupdate_origin_change | update_if_newertarget_table_missing | skip_if_recently_droppedtarget_column_missing | ignore_if_nullsource_column_missing | use_default_valueapply_error | errorapply_error_trigger | errorapply_error_ddl | errorapply_error_dml | errorupdate_recently_deleted | skipdelete_recently_updated | skipupdate_pkey_exists | update_if_newermultiple_unique_conflicts | error(16 rows)
PGD官方文档中列出了各种冲突类型及解决方法和建议,可以参考下面链接了解详细的相关内容介绍:
https://www.enterprisedb.com/docs/pgd/latest/consistency/ 。
1.1 PGD 冲突类型列表
PGD 可识别以下冲突类型,这些冲突类型可用作 conflict_type 参数:
insert_exists — 传入的插入通过主键或唯一键/索引与现有行冲突。
update_differing — 传入更新的键行与本地行不同。这只有在使用行版本冲突检测时才会发生。
update_origin_change — 传入的更新正在修改上次由其他节点更改的行。
update_missing — 传入更新正在尝试修改不存在的行。
update_recently_deleted — 传入的更新正在尝试修改最近删除的行。
update_pkey_exists — 传入的更新已将PRIMARY KEY修改为应用更改的节点上已存在的值。
multiple_unique_conflicts — 传入行与目标表中的多个 UNIQUE 约束/索引冲突。
delete_recently_updated — 提交时间戳早于当前节点上行的最新更新的传入删除,或者使用行版本冲突检测时。
delete_missing — 传入删除正在尝试删除不存在的行。
target_column_missing — 目标表缺少传入行中存在的一列或多列。
source_column_missing — 传入行缺少目标表中存在的一列或多列。
target_table_missing — 缺少目标表。
apply_error_ddl — Postgres 在应用复制的 DDL 命令时引发错误。
1.2 PGD 冲突解决方法列表
PGD 中提供了多种冲突解决方法,它们可以处理的冲突类型具有不同的覆盖范围:
error — 引发错误并停止复制。可用于任何冲突类型。
skip — 跳过处理远程更改,并在下一次更改时继续复制。可用于insert_exists、update_differing、update_origin_change、update_missing、update_recently_deleted、update_pkey_exists、delete_recently_updated、delete_missing、target_table_missing、target_column_missing和source_column_missing冲突类型。
skip_if_recently_dropped — 如果远程更改是针对下游不存在的表,则跳过该表,因为该表最近(一天内)被丢弃在下游。否则会引发错误。可用于target_table_missing冲突类型。如果删除同名表后不久重新创建同名表,则skip_if_recently_dropped冲突解决程序可能会带来挑战。在这种情况下,其中一个节点可能会在看到重新创建表的 DDL 之前看到重新创建的表上的 DML。然后,它会错误地跳过远程数据,假设该表最近被删除,并导致数据丢失。因此,我们建议您不要在与此冲突解决程序一起删除对象名称后立即重复使用它们。
skip_transaction — 跳过生成冲突的整个事务。可用于apply_error_ddl冲突。
update_if_newer — 如果远程行的提交时间晚于冲突的本地行(由原始节点的挂钟确定),则更新。如果时间戳相同,则节点 ID 用作决胜局,以确保在所有节点上选择同一行(节点 ID 越高)。可用于insert_exists、update_differing、update_origin_change和update_pkey_exists冲突类型。
update — 始终执行复制的操作。可用于insert_exists(将 INSERT 转换为 UPDATE)、update_differing、update_origin_change、update_pkey_exists 和 delete_recently_updated(执行删除)。
insert_or_skip — 尝试从源发送的可用信息构建新行并插入它。如果没有足够的可用信息来生成整行,请跳过更改。可用于update_missing和update_recently_deleted冲突类型。
insert_or_error — 尝试从源发送的可用信息构建新行并插入它。如果没有足够的可用信息来生成整行,则引发错误并停止复制。可用于update_missing和update_recently_deleted冲突类型。
ignore — 忽略任何缺失的目标列并继续处理。可用于target_column_missing冲突类型。
ignore_if_null — 如果远程行中的额外列包含 NULL 值,则忽略缺少的目标列。否则,将引发错误并停止复制。可用于target_column_missing冲突类型。
use_default_value — 使用默认值填充缺失的列值(如果这是列默认值,则包括 NULL)并继续处理。处理默认值或违反约束时出现的任何错误(即 NOT NULL 列的 NULL 默认值)都会停止复制。可用于source_column_missing冲突类型。
1.3 PGD 冲突方法设置
大多数冲突可以自动解决。PGD默认使用最后更新获胜机制,或更准确地说,使用 update_if_newer冲突解决程序。该机制根据用于冲突检测的相同提交时间戳保留两个冲突行中最近插入或更改的行。某些极端情况下的行为取决于 bdr.create_node_group 和 bdr.alter_node_group 使用的设置。
PGD 允许您使用以下函数覆盖冲突解决的默认行为。
bdr.alter_node_set_conflict_resolver
该函数设置给定节点上冲突解决的行为。
bdr.alter_node_set_conflict_resolver(node_name text,conflict_type text,conflict_resolver text)
node_name— 正在更改的节点的名称。
conflict_type - 要应用设置的冲突类型。
conflict_resolver - 用于给定冲突类型的解析器。
注意:
目前只能更改本地节点。函数调用不会被复制。如果要更改多个节点上的设置,则必须在每个节点上运行该函数。此函数所做的配置更改会覆盖bdr.create_node_group 或 bdr.alter_node_group 指定的冲突解决的任何默认行为。该功能是事务性的。您可以回滚更改,并且它们对当前事务可见。
二、 PGD 测试环境
处理冲突的最佳方法首先是避免冲突!使用带有PGD proxy代理的PGD Always-On 架构,确保应用程序写入群集中的是同一台节点服务器。
当冲突发生时,了解 PGD 如何解决冲突、如何控制冲突的解决以及如何发现冲突是非常有用的。行插入和行更新是两种可能导致冲突的操作。
这里我们进行一个简单的冲突检测demo。
我们还是采用之前安装和配置的环境,3个数据库节点(linuxhost-1,linuxhost-2,linuxhost-3)以及一个备份节点(linuxhost-4)。
我们仍然采用xpanes同时打开四个窗口分别连接到4个节点上。
[nuser@linuxhost-1 ~]$ sudo -iu enterprisedb
运行以下命令以连接到三个数据库服务器和一个代理服务器, 三个窗口将连接到端口 5444 上的数据库节点 linuxhost-1、linuxhost-2 和 linuxhost-3。一个窗口将连接到端口 6432 上在 linuxhost-1上运行的 pgd-proxy。每个都将作为 enterprisedb 用户登录到数据库中,按 Control-b,然后按 q 以简要显示每个窗口的数值。
enterprisedb@linuxhost-1:~ $ xpanes -d -c "psql postgresql://enterprisedb@{}/bdrdb?sslmode=require" "linuxhost-1:5444" "linuxhost-2:5444" "linuxhost-3:5444" "linuxhost-1:6432"

我们首先把窗口3做为活动窗口,Control-b q 3 移动到左下窗口。
三、 设计冲突表并监视冲突
首先建个测试冲突表test_confilict
drop table if exists test_conflict;create table test_conflict (id integer primary key ,value_1 text);
然后,我们在该窗口监视冲突表,通过\watch 1滚动显示冲突信息。
select * from bdr.conflict_history_summary\watch 1
四、 创建 insert 冲突
冲突的最基本形式是,当插入发生在两个不同数据节点上的相同表上,并且两个数据节点都具有相同的主键。我们接下来准备制造冲突。
Control-b q 0 移动到左上角的窗口。此窗口是 linuxhost-1节点。在那里开始一个交易,并插入一行:
start transaction;insert into test_conflict values (1, 'from linuxhost-1);
Control-b q 1 移动到右上窗口。此窗口是 linuxhost-2 节点。在这里,也启动一个事务,并将不同的数据插入到同一行中。
start transaction;insert into test_conflict values (1, 'from linuxhost-2');
现在,在不同的服务器上打开了两个事务,并且已成功执行插入操作。此时,需要提交两个事务:
Control-b q 0,然后输入 commit;Control-b q 1,然后输入 commit;
我们将看到两个提交都有效。但是,在右下窗格中,您可以看到检测到的冲突。

冲突历史记录中的一行现在记录了表中的冲突,insert_exists。其中,它还指出,此冲突的解决方案是保留基于提交时间的较新记录。此冲突称为 INSERT/INSERT 冲突。您可以在 INSERT/INSERT 冲突中阅读有关此类冲突的详细信息。
五、 创建 update 冲突
当在不同的节点上对同一记录进行不同的更新时,也会发生冲突。
Control-b q 0 移动到左上窗口。此窗格是 linuxhost-1节点。在这里,开始一个事务并更新一行:
start transaction;update test_conflict set value_1 = 'update from linuxhost-1' where id=1;
同样,Control-b q 1 移动到右上窗口。此窗格是 linuxhost-2节点。在这里,也开始一个事务,并使用不同的数据更新同一行:
start transaction;update test_conflict set value_1 = 'update from linuxhost-2' where id = 1;
同上,两个窗口分别提交事务:
Control-b q 0,然后输入 commit;Control-b q 1,然后输入 commit;
同样,我们看到两个提交都有效。同样,在右下窗口中,可以看到检测到更新的冲突。

冲突历史记录中的附加行显示发生了update_origin_change冲突,并且解决方案为 apply_remote。此解决方案意味着已应用远程更改,更新了记录。此冲突称为 UPDATE/UPDATE 冲突,在官网文档 UPDATE/UPDATE 冲突中有更详细的解释。
我们查看现在记录是在linuxhost-2上最新提交一条更新有效。
bdrdb=# select * from test_conflict ;id | value_1----+-------------------------1 | update from linuxhost-2(1 row)

发现“分享”和“赞”了吗,戳我看看吧




