如果我告诉您可以在Kubernetes上创建现成的主动-主动,联合PostgreSQL集群怎么办?
自从PostgreSQL 9.4引入逻辑解码以来,我对它所具有的各种应用程序着迷。实际上,我已经使用此功能将更改数据捕获的概念在理论和实践上都应用到应用程序和用户的利益上。 Postgres 10中引入的逻辑复制和本机分区支持为分配应用程序负载提供了更多可能性,尤其是对于地理上分散的工作负载。
尽管我已经看到了对应用程序高可用性的日益增长的需求(并且由于云技术的改进,我已经接受了这种东西),但我看到了一个不那么普遍的用例,但更多应用程序可能需要多个写入节点的可用性。我已经看到了各种情况,从在其区域内有大量写操作的地理分布的应用程序到可能在一段时间内被Internet中断并需要稍后同步的嵌入式应用程序。
此外,虽然这些用例可能要分发写入,但他们希望确保单个节点可以查看所有数据。这是数据联合的经典示例,也是PostgreSQL通过外部数据包装器长期支持的概念。但是,通过使用现代PostgreSQL版本中的某些功能(例如逻辑复制和分区),我们可以使联合的PostgreSQL集群的读取效率更高!
让我们逐步介绍如何使用Crunchy Postgres Operator和Kubernetes建立三个主要节点的联合Postgres集群,从而避免冲突。
为了方便起见,我将使用PostgreSQL Operator创建环境。设置Postgres Operator超出了本博客的范围,但是如果您在家中跟随,建议您使用快速入门。默认情况下,所有PostgreSQL集群都将部署到pgo命名空间中。
让我们创建三个PostgreSQL集群,特别是三个正在运行PostgreSQL 13的实例。我已将这些实例分为“东部”,“中央”和“#34”。 ; west&#34 ;,在每个实例上,我都将创建一个名为hippo的用户,其密码为datalake,并创建一个数据库名称hippo。
pgo创建集群hippo-east \ --ccp-image-tag = centos8-13.1-4.6.0 \ --username = hippo \ --password = datalake \ --password-superuser = superdatalake \ --database = hippopgo创建集群hippo-central \ --ccp-image-tag = centos8-13.1-4.6.0 \ --username = hippo \ --password = datalake \ --password-superuser = superdatalake \ --database = hippopgo创建集群hippo-west \ --ccp-image-tag = centos8-13.1-4.6.0 \-用户名=河马\-密码=数据湖\-密码超级用户=超级数据湖\-数据库=河马
我们将需要一种方法来标识联合集群中的每个节点。 Postgres Operator附带的pgnodemx扩展名提供了一种方便的方法来查找节点的名称。虽然Postgres Operator默认情况下为pgnodemx启用了共享库,但是您仍然需要将扩展名添加到数据库中才能使用它。
我们需要将此扩展名添加为Postgres超级用户,该用户名为hipgre数据库的postgres。有多种连接到Kubernetes上运行的PostgreSQL数据库的方法,如Postgres Operator文档中所述。此外,我们将需要在所有三个Postgres实例上运行命令,因此请注意执行以下命令的位置!
在上面的示例中,在设置三个群集时,请回想一下,我们将超级用户密码设置为superdatalake。首先,让我们连接到东面的河马Postgres集群和河马数据库。如果您使用端口转发技术,则连接字符串可能如下所示:
登录后,创建以下函数,该函数将获取节点名称并将权限授予河马用户以执行该节点:
如果不存在,请创建扩展名pgnodemx;创建或替换功能hippo.get_node_name()返回文本从kdapi_setof_kv(' labels')中选择val,其中key =' pg-cluster&#39 ;; $$语言SQL安全性定义不可变;在功能上执行GRIP hippo.get_node_name()到hippo;
以河马用户身份测试功能!例如,尝试以与上述登录方式类似的步骤,以hippo-east集群中的hippo用户身份登录到hippo数据库。例如,使用port-forward方法:
成功!如果您不使用Kubernetes,则可以通过设置PostgreSQL自定义选项来实现类似的设置,例如node.node_name,并在本示例的后面部分中进行引用。
设置我们的三节点可写PostgreSQL集群的基本配置已完成。现在我们需要建立实际的数据结构!
如上所述,有多种方法可以连接到在Kubernetes上运行的PostgreSQL数据库,如Postgres Operator文档中所述。要设置我们的数据结构,我们将需要使用在先前步骤(datalake)中设置的密码以河马用户身份登录。此外,我们将需要在所有三个Postgres实例上运行命令,因此请注意执行以下命令的位置!
首先,让我们连接到东面的河马Postgres集群和河马数据库。如果您使用端口转发技术,则连接字符串可能如下所示:
我们的数据结构将收集观察值和观察时间。连接到" hippo-east"时,执行以下SQL命令:
CREATE TABLE hippos(id uuid DEFAULT gen_random_uuid()NOT NULL,node_name文本,数值数值,created_at timestamptz)PARTITION BY LIST(node_name); CREATE TABLE hippo_default分隔河马(PRIMARY KEY(id))DEFAULT; CREATE TABLE hippo_east PARTITION OF (PRIMARY KEY(ID))中的值(' hippo-east'); CREATE TABLE hippo_centralPartition of hippos(PRIMARY KEY(ID))中的值(' hippo-central' );为((#hippo-west')中的值创建河马表(primary key(id))的hippo_west分区; .get_node_name()WHERE node_name是NULL;返回NULL; END $$语言plpgsql;为每个语句执行功能创建触发add_node_nameAFTER在河马上插入add_node_name();
请注意,将用作主键的值使用了PostgreSQL 13中可用的gen_random_uuid()函数-较早版本的PostgreSQL需要运行CREATE EXTENSION pgcrypto;作为超级用户允许使用该功能。
首先,我们创建了一个分区表,该分区表的分区键对应于我们的三个节点名称:hippo-east,hippo-central和hippo-west。这些将用于分割来自不同节点的写入。还要注意,我们创建了一个默认分区:这对于将每个插入的行适当地路由到正确的分区中非常重要。
PostgreSQL 13引入了在分区表上使用BEFORE行触发器的功能,尽管它们不允许您修改分区键。但是,这正是我们需要做的!相反,在插入一行(并将其路由到默认分区)之后,我们运行语句触发器,将默认分区中的任何行移动到代表当前节点的分区中(这是从我们创建的get_node_name()函数获得的)。
换句话说,以上架构确保将所有插入的行都路由到表示该特定节点的分区!
下一步,我们需要在节点之间设置逻辑复制。为此,我们需要成为Postgres超级用户,在本例中为postgres。回想一下,当我们配置了三个集群时,我们将超级用户密码设置为superdatalake。要设置逻辑复制,我们将需要执行以下步骤:
向河马用户授予REPLICATION特权。请注意,这是接近超级用户的特权,但是为了方便演示,我们正在这样做。
在每个群集上创建发布者,以公布其特定分区收集的写操作。例如,在hippo-east上,这将是hippo_east表。
创建所有发布者之后,在每个群集上创建对其他群集的订阅以读取所有更改。
这次,我们将不得不在每个PostgreSQL集群上运行不同的命令,因此请注意!
如上所述,这为河马PostgreSQL用户提供了复制特权,并为hippo_east表定义了逻辑复制发布。
现在,我们准备设置逻辑复制预订。每个节点都需要订阅其他节点才能接收所有更改。例如,河马东方需要订阅河马中央和河马西部的发行商来接收更改。以PostgreSQL超级用户(" postgres")登录到hippo-east并执行以下命令:
创建订阅sub_hippo_east_hippo_central连接' dbname = hippo主机= hippo-central.pgo用户= hippo密码= datalake'出版物pub_hippo_central;创建订阅sub_hippo_east_hippo_west连接' dbname = hippo主机= hippo-west.pgo用户= hippo密码= datalake'出版物pub_hippo_west;
(请注意,我正在使用Postgres Operator创建的Kubernetes Services导出主机名。了解有关Kubernetes网络名称的更多信息)。
创建订阅sub_hippo_central_hippo_east连接' dbname = hippo主机= hippo-east.pgo用户= hippo密码= datalake'出版物pub_hippo_east;创建订阅sub_hippo_central_hippo_west连接' dbname = hippo主机= hippo-west.pgo用户= hippo密码= datalake'出版物pub_hippo_west;
创建订阅sub_hippo_west_hippo_east连接' dbname = hippo主机= hippo-east.pgo用户= hippo密码= datalake'出版物pub_hippo_east;创建订阅sub_hippo_west_hippo_central连接' dbname = hippo主机= hippo-central.pgo用户= hippo密码= datalake'出版物pub_hippo_central;
要测试集群,请尝试将一些数据写入所有三个节点。这是一个入门的示例:修改特定于您的环境的设置:
#hippo-eastPGPASSWORD = datalake psql -h hippo-east.pgo -U hippo hippo -c' INSERT INTO hippos(value,created_at)VALUES(random(),CURRENT_TIMESTAMP);#hippo-centralPGPASSWORD = datalake psql -h hippo-central.pgo -U hippo hippo -c' INSERT INTO hippos(value,created_at)VALUES(random(),CURRENT_TIMESTAMP);#hippo-westPGPASSWORD = datalake psql -h hippo-west.pgo -U hippo hippo -c'插入河马(value,created_at)VALUES(random(),CURRENT_TIMESTAMP);
现在,以河马用户身份登录河马东并检查河马表。这是我看到的:
河马=>表河马; id | node_name |价值| created_at -------------------------------------- + ---------- ----- + ------------------- + ------------------------ ------- 4608b3a8-0f34-4837-8456-5944a61d15de |河马中心| 0.484604634620151 | 2020-12-19 16:41:08.707359 + 00 eefa9a61-cc7e-44bc-a427-4b26c2564d24 |东河马| 0.270977731568895 | 2020-12-19 16:41:16.837468 + 00 37cf93dd-c7a1-44be-9eab-a58c73a14740 |西河马| 0.509173376067992 | 2020-12-19 16:40:59.949198 + 00(3行)
很酷,所以一切都复制了!为了确保这不是一种幻想,我检查了每个分区表以查看其中有哪些行:
河马=>表hippo_east; id | node_name |价值| created_at -------------------------------------- + ---------- -+ ------------------- + --------------------------- ---- eefa9a61-cc7e-44bc-a427-4b26c2564d24 |东河马| 0.270977731568895 | 2020-12-19 16:41:16.837468 + 00(1行)hippo =>表hippo_central; id | node_name |价值| created_at -------------------------------------- + ---------- ----- + ------------------- + ------------------------ ------- 4608b3a8-0f34-4837-8456-5944a61d15de |河马中心| 0.484604634620151 | 2020-12-19 16:41:08.707359 + 00(1行)hippo =>表hippo_west; id | node_name |价值| created_at -------------------------------------- + ---------- -+ ------------------- + --------------------------- ---- 37cf93dd-c7a1-44be-9eab-a58c73a14740 |西河马| 0.509173376067992 | 2020-12-19 16:40:59.949198 + 00(1行)
那其他节点呢?登录到河马中心或河马西部-您应该看到类似以下内容:
河马=>表河马; id | node_name |价值| created_at -------------------------------------- + ---------- ----- + ------------------- + ------------------------ ------- 4608b3a8-0f34-4837-8456-5944a61d15de |河马中心| 0.484604634620151 | 2020-12-19 16:41:08.707359 + 00 eefa9a61-cc7e-44bc-a427-4b26c2564d24 |东河马| 0.270977731568895 | 2020-12-19 16:41:16.837468 + 00 37cf93dd-c7a1-44be-9eab-a58c73a14740 |西河马| 0.509173376067992 | 2020-12-19 16:40:59.949198 + 00
成功-我们在Kubernetes上创建了一个三节点Postgres集群,每个节点都可以安全地接受写入!
看来我们的应用程序将需要在南部的一个节点,那么如何添加另一个节点?
如果遵循上述步骤,实际上并不太复杂。请注意,随着添加更多节点,可能需要增加PostgreSQL参数,例如max_wal_senders和max_replication_slots。
与示例开头类似,以postgres用户身份登录到hippo数据库,并创建pgnodemx扩展名和get_node_name函数。为了方便起见,以下是这些命令:
如果不存在,请创建扩展名pgnodemx;创建或替换功能hippo.get_node_name()返回文本从kdapi_setof_kv(' labels')中选择val,其中key =' pg-cluster&#39 ;; $$语言SQL安全性定义不可变;在功能上执行GRIP hippo.get_node_name()到hippo;
现在以河马用户身份登录到河马南,并添加架构,现在为河马南添加了一个额外的分区:
CREATE TABLE hippos(id uuid DEFAULT gen_random_uuid()NOT NULL,node_name文本,数值数值,created_at timestamptz)PARTITION BY LIST(node_name); CREATE TABLE hippo_default分隔河马(PRIMARY KEY(id))DEFAULT; CREATE TABLE分隔河马(PRIMARY KEY(ID))中的值(' hippo-east'); CREATE TABLE hippo_centralPartition of hippos(PRIMARY KEY(ID)) );在(' hippo-west')中创建河马(PRIMARY KEY(id))中的表hippo_west分区;在(&## 39; hippo-south');创建或替换功能add_node_name()返回触发器为$$ BEGIN UPDATE hippos SET node_name = hippo.get_node_name()WHERE node_name为NULL;返回NULL; END $$语言plpgsql;为每个语句执行功能创建触发add_node_nameAFTER在河马上插入add_node_name();
以Postgres超级用户身份登录hippo-south,并向hippo授予REPLICATION特权,为hippo_south分区创建发布者,并创建订阅者:
ALTER ROLE hippo复制;创建pub_hippo_south for table hippo.hippo_south;创建订阅sub_hippo_south_hippo_east连接' dbname = hippo host = hippo-east.pgo user = hippo password = datalake'出版物pub_hippo_east;创建订阅sub_hippo_south_hippo_central连接' dbname = hippo主机= hippo-central.pgo用户= hippo密码= datalake'出版物pub_hippo_central;创建订阅sub_hippo_south_hippo_west连接' dbname = hippo主机= hippo-west.pgo用户= hippo密码= datalake'出版物pub_hippo_west;
作为河马用户,登录到河马东,河马中央和河马西部,并添加hippo_south分区:
现在,以Postgres超级用户身份,登录到下面的每个节点,并从hippo-east开始执行以下操作:
这样,您就为联邦PostgreSQL集群添加了一个额外的节点!测试一下,看看结果。
具有多个写节点的系统提出的一个问题是如何处理冲突。在上面的示例中,冲突实际上是在每个单独的节点上处理的:在每个分区中都设置了主键,因此,如果生成冲突的UUID,则不会插入该UUID,因此不会对其进行复制。这不会阻止两个节点生成相同的UUID:我们可以利用UUIDv5等来帮助防止这种冲突,或者确保可以使用其他一些自然键来查找真相。
作为安全措施,我们可以进一步锁定模式,以防止节点无意中将数据添加到不允许写入的分区中。
如果我们不打算创建其他节点,则针对主键冲突的另一种解决方案是协调序列。例如,使用我们的三节点集群,我们可以设计一个类似于以下内容的架构:
#hippo-eastCREATE TABLE hippos(id bigint由DEFAULT AS IDENTITY生成(以1 INCREMENT BY 3开头)不为空,值是数值,在timestamptz上创建)PARTITION BY LIST((((id-1)%3)); CREATE TABLE hippo_east PARTITION (0)中的值的河马(PRIMARY KEY(id));(1)中的值创建河马(PRIMARY KEY(id))的表;河马(PRIMARY KEY(id))中创建表的hippo_west分区(2)中的值;#hippo-central创建表河马(id bigint由默认身份作为身份生成(以2 INCREMENT BY 3开头)不是NULL,值是数值,在timestamptz时created)按列表划分((((id-1)%3) );为(0)中的值创建河马表(主要键(id));为(1)中的值创建河马表(主要键(id));为河马(原始表)创建表hippo_west分区(2)中的值的键(id));#hippo-west创建表河马(id bigint由默认身份标识生成(以3递增3开头)不为空,值数值,created_at timestamptz)按列表划分((((id-1)%3));为(0)中的值创建河马表hippo_eastPartition(主键(id));为河马表创建hippo_centralPartition(PRIMARY KEY( id))(1)中的值;为(2)中的值创建河马表hippo_west分区(主键(id));
这将防止主键冲突并消除对语句触发器的需要,但是添加其他节点将花费大量的精力。
高可用性如何?每个节点都需要有自己的HA。 Postgres Operator可以简化此操作,因为您可以默认部署HA群集,并通过pgo scale命令将群集转换为使用HA。 PostgreSQL Operator还具有使用pgBackRest进行内置备份的附加好处,尽管请注意,在备份时每个节点可能对世界有不同的看法。
尽管此解决方案提供了一种使用PostgreSQL实现写扩展的方法,但请注意,它不是“设置后忘记”的设置。解决方案:您确实需要监视您的节点,尤其是当节点长时间无法重新加入群集时。 PostgreSQL 13还添加了一个配置参数,如果它未被确认太长时间,则允许删除复制插槽,不过请注意,这意味着您必须在受影响的节点上重新同步分区。
任何需要多个可写节点的系统都面临挑战。 PostgreSQL的最新进展使构建更复杂的体系结构成为可能,该体系结构解决了复杂的数据访问和分发问题,同时提供了强大的开发人员功能,使我深深地融入了Postgres!