PostgreSQL 10.0 preview sharding增强 - 支持分布式事务

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介:

标签

PostgreSQL , 10.0 , sharding , 分布式事务 , 2pc , 两阶段事务


背景

作为一个完整的分布式数据库(sharding),没有分布式事务支持是不行的。

什么是分布式事务呢?比如我们把一个数据库比作一个小朋友,每个小朋友都有100块钱,然后A小朋友给了B小朋友20块钱,这样的话应该是A剩余80块,B剩余120块。如果在交易过程中B小朋友不小心把20块弄丢了,那么会怎么样呢?

理论上应该是交易不成功,A和B都回到100块的状态。 不应该出现中间状态。

PostgreSQL 10.0内置了基于postgres_fdw的sharding功能,同时也对postgres_fdw新增了2PC模块,你可以设置foreign server是否支持2PC.

创建foreign server时,通过参数two_phase_commit 指定即可。

分布式事务实现的原理

什么时候会使用两阶段事务

当写事务涉及到>=2个two_phase_commit=on的shard节点的写操作时。

当事务仅仅涉及单个shard时(包括本地的写),不需要2PC,在本地提交时可以通过pre-commit of notify确保本地和异地的一致性。

如何处理crash后的未知两阶段状态

两阶段事务,如果在第二阶段(即prepare成功后,准备commit前)时,数据库CRASH了,如何处理呢?

PostgreSQL提供了两种处理方法

1. 调用 pg_fdw_xact_resolve() 函数手工处理.

2. 使用pg_fdw_xact_resolver 模块自动处理.

pg_fdw_xact_resolver 是一个worker process,会自动检测是否有未知状态的2PC事务,自动处理.

API

为了支持分布式事务,新增的API如下

两阶段事务,获取shard节点1st prepare阶段产生的事务ID  
GetPreparedID() is called to get transaction identifier on pre-commit phase.  
  
非两阶段事务,在shard节点执行提交或回滚  
EndForeignTransaction() is called on commit phase and executes either COMMIT or ROLLBACK on foreign servers.  
  
两阶段事务,1st,在shard节点执行预提交操作  
PrepareForeignTransaction() is called on pre-commit phase and executes PREPARE TRANSACTION on foreign servers.  
  
两阶段事务,2nd,在shard节点执行提交或回滚prepared xact  
ResolvePrepareForeignTransaction() is called on commit phase and execute either COMMIT PREPARED or ROLLBACK PREPARED with given transaction identifier on foreign servers.  
  
如果foreign server没有开启两阶段支持,则不需要使用后两个API  
If the foreign data wrapper is not capable of two-phase-commit protocol, last two APIs are not required.  

目前的限制

注意,目前只要事务中涉及到2个或以上开启了Two-phase-commit的shard,那么都会开启两阶段事务。

可能会影响一些性能,所以请酌情使用Two-phase-commit开关。

Two-phase-commit protocol is used even when the transaction involves with multiple servers but does not modify data.

分布式2PC事务处理过程原理剖析

为了实现分布式两阶段事务,coordinator节点需要跟踪shard节点的事务。PG通过开辟一块共享内存区域(KnownFDWXact list)来记录shard节点的事务状态,这块区域的改写需要记录REDO日志,在检查点时持久化到$PGDATA/fdw_xact 目录中,每个2PC事务对应这个目录中的一个文件,文件名为(xid, foreign server oid, user oid)。

分布式2PC事务处理过程如下:

1. two_phase_commit = on的foreign server,当开启一个事务时,通过RegisterXactForeignServer()注册连接到MyFDWConnection结构中

2. pre-commit阶段执行如下动作(涉及到事务中用到的shard节点)

2.1 获取two_phase_commit = on的foreign server的本事务的 xact id

2.2 在two_phase_commit = off的节点, 执行commit,需要一个notify,shard才会真正提交。

2.3 将xact id写入fdw_xact共享内存结构中,同时写redo日志XLOG_FDW_XACT_INSERT 。

2.4 在two_phase_commit = on的节点,执行PREPARE TRANSACTION .

2.5 本地调用RecordTransactionCommit(),提交。完成2PC的第一阶段。如果第一阶段执行失败,所有节点包括本地节点都可以回滚,(包括two_phase_commit = off的shard)。

3. 如果第一阶段执行成功,那么表示two_phase_commit = off的节点事务已经成功提交了,本地事务也成功提交了,进入第二阶段。

3.1 从共享内存解析出shard(foreign)节点的xact id,Resolve foreign prepared transaction.

3.2 从共享内存移除 foreign transaction entry 同时写WAL日志 XLOG_FDW_XACT_REMOVE .

如果第一阶段成功了,但是第二阶段没有成功,或者还没有成功前,主备发生了切换,那么就需要recovery两阶段事务了。

分布式两阶段事务的恢复过程剖析

恢复过程可能出现在主库crash,或者备库上面。分布式两阶段事务恢复的原理与普通两阶段事务恢复原理类似。

恢复时,从wal日志中解析XLOG_FDW_XACT_INSERT和XLOG_FDW_XACT_REMOVE record,复原fdw_xact目录中的2PC状态记录文件。

redo恢复阶段结束后,PostgreSQL会扫描pg_fdw_xact目录中还有哪些文件,如果有,说明有未知状态的2PC事务。未提交的2PC事务包括事务ID,shard id, user id,数据库需要处理未知状态的2PC事务,提交或者回滚。

Crash recovery  
  
During crash recovery, the fdw_xact entry are inserted to KnownFDWXact List or removed from KnownFDWXact list when corresponding WAL records are replayed.   
  
After the redo is done fdw_xact file is re-created and then pg_fdw_xact directory is scanned for unresolved foreign prepared transactions.  
  
The files in this directory are named as triplet (xid, foreign server oid, user oid) to create a unique name for each file.   
  
This scan also emits the oldest transaction id with an unresolved prepared foreign transactions.   
  
This affects oldest active transaction id, since the status of this transaction id is required to decide the fate of unresolved prepared foreign transaction.   
  
On standby during WAL replay files are just inserted or removed.   
  
If the standby is required to finish recovery and take over the master, pg_fdw_xact is scanned to read unresolved foreign prepared transactions into the shared memory.  
  
Many of fdw_xact.c code is inspired by two_phase.c code. So recovery mechanism and process are almost same as two_phase.   
  
The patch incorporated recent optimization of two_phase.c.  

环境部署例子

1. coordinator库设置

为了支持2PC,在coordinator库上要保留一些2PC状态,所以coordinator库也有一个参数控制最大允许开多少个2PC事务,max_prepared_foreign_transactions 参数就是控制这个的。

公式如下

max_prepared_foreign_transactions =  (max_connections) * (# of foreign server with two_phase_commit = on)  

同时,coordinator库还要设置一个参数来支持2PC事务

max_prepared_transactions = 10  # 建议设置为max_connections相等即可。  

2. sharding库,也需要设置如下

max_prepared_transactions = 100 # same as max_connections  
log_statement = all  # 方便观察  
log_line_prefix = '[S1]' # on shard2 server, we can set '[S2]'  

3. coordinator库上面创建postgres_fdw插件

4. 创建foreign server,假设有两个shard库

$ psql  
=# CREATE SERVER shard_node1 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'shard1', dbname 'postgres', port '5432', two_phase_commit 'on');  
CREATE SERVER  
=# CREATE SERVER shard_node2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'shard2', dbname 'postgres', port '5342', two_phase_commit 'on')  
CREATE SERVER  
=# SELECT * FROM pg_foreign_server;  
   srvname   | srvowner | srvfdw | srvtype | srvversion | srvacl |                   srvoptions                      
-------------+----------+--------+---------+------------+--------+-------------------------------------------------  
 shard_node1 |       10 |  16387 |         |            |        | {host=shard1,dbname=postgres,port=5432,two_phase_commit=on}  
 shard_node2 |       10 |  16387 |         |            |        | {host=shard2,dbname=postgres,port=5432,two_phase_commit=on}  
(2 rows)  

5. 创建foreign server的user mapping

测试

1. 先来一个非两阶段事务的测试(只涉及到一个foreign server, 不会启用两阶段事务)

=# BEGIN;  
=# INSERT INTO ft1 VALUES(1);  
=# COMMIT;  

查看日志

[S1] LOG:  statement: SET search_path = pg_catalog  
[S1] LOG:  statement: SET timezone = 'UTC'  
[S1] LOG:  statement: SET datestyle = ISO  
[S1] LOG:  statement: SET intervalstyle = postgres  
[S1] LOG:  statement: SET extra_float_digits = 3  
[S1] LOG:  statement: START TRANSACTION ISOLATION LEVEL REPEATABLE READ  
[S1] LOG:  execute pgsql_fdw_prep_1: INSERT INTO public.ft1(c) VALUES ($1)  
[S1] DETAIL:  parameters: $1 = '1'  
[S1] LOG:  statement: DEALLOCATE pgsql_fdw_prep_1  
[S1] LOG:  statement: COMMIT TRANSACTION  

2. 涉及多个two_phase_commit is on 的foreign server,自动开启两阶段事务

$ psql  
=# BEGIN;  
=# INSERT INTO ft1 VALUES(2);  
=# INSERT INTO ft2 VALUES(2);  
=# COMMIT;  

日志如下,看到启动了两阶段事务

[S1] LOG:  statement: SET search_path = pg_catalog  
[S1] LOG:  statement: SET timezone = 'UTC'  
[S1] LOG:  statement: SET datestyle = ISO  
[S1] LOG:  statement: SET intervalstyle = postgres  
[S1] LOG:  statement: SET extra_float_digits = 3  
[S1] LOG:  statement: START TRANSACTION ISOLATION LEVEL REPEATABLE READ  
[S1] LOG:  execute pgsql_fdw_prep_1: INSERT INTO public.ft1(c) VALUES ($1)  
[S1] DETAIL:  parameters: $1 = '2'  
[S1] LOG:  statement: DEALLOCATE pgsql_fdw_prep_1  
[S2] LOG:  statement: SET search_path = pg_catalog  
[S2] LOG:  statement: SET timezone = 'UTC'  
[S2] LOG:  statement: SET datestyle = ISO  
[S2] LOG:  statement: SET intervalstyle = postgres  
[S2] LOG:  statement: SET extra_float_digits = 3  
[S2] LOG:  statement: START TRANSACTION ISOLATION LEVEL REPEATABLE READ  
[S2] LOG:  execute pgsql_fdw_prep_2: INSERT INTO public.ft2(c) VALUES ($1)  
[S2] DETAIL:  parameters: $1 = '2'  
[S2] LOG:  statement: DEALLOCATE pgsql_fdw_prep_2  
[S1] LOG:  statement: PREPARE TRANSACTION 'px_1389361800_16388_10'  
[S2] LOG:  statement: PREPARE TRANSACTION 'px_53866648_16389_10'  
[S1] LOG:  statement: COMMIT PREPARED 'px_1389361800_16388_10'  
[S2] LOG:  statement: COMMIT PREPARED 'px_53866648_16389_10'  

3. 在coordinator节点使用两阶段事务

=# BEGIN;  
=# INSERT INTO ft1 VALUES (3);  
=# INSERT INTO ft2 VALUES (3);  
=# PREPARE TRANSACTION 'gxid';  

此时,可以查看到2PC会下发到shard节点

=# SELECT * FROM pg_fdw_xacts;  
 dbid  | transaction | serverid | userid |  status  |      identifier         
-------+-------------+----------+--------+----------+-----------------------  
 13182 |         564 |    16389 |     10 | prepared | px_450388264_16389_10  
 13182 |         564 |    16388 |     10 | prepared | px_569713952_16388_10  
(2 rows)  

当执行COMMIT PREPARED 'gxid'时,会提交远程的两阶段事务。

4. 两阶段事务的回滚,保证分布式一致性

=# BEGIN;  
=# INSERT INTO lt VALUES(4);  
=# INSERT INTO ft1 VALUES(4);  
=# INSERT INTO ft2 VALUES(4);  

关闭一个shard,提交失败

=# COMMIT; -- error  

检查数据是否一致,所有节点均无数据

=# SELECT * FROM lt WHERE c = '4'; -- data on local server  
 c   
---  
(0 rows)  
=# SELECT * FROM ft2 WHERE c = '4'; -- data on shard2 server  
 c   
---  
(0 rows)  
  
当shard1恢复后,可以看到自动回滚掉了  
=# SELECT * FROM ft1 WHERE c = '4'; -- data on shard1 server  
 c   
---  
(0 rows)  

这个patch的讨论,详见邮件组,本文末尾URL。

PostgreSQL社区的作风非常严谨,一个patch可能在邮件组中讨论几个月甚至几年,根据大家的意见反复的修正,patch合并到master已经非常成熟,所以PostgreSQL的稳定性也是远近闻名的。

参考

https://wiki.postgresql.org/wiki/2PC_on_FDW

https://www.postgresql.org/message-id/flat/CAFjFpRfQaCTt1vD9E35J%2BXxfCnZC5HONqgJgGpUjfktJdoYZVw%40mail.gmail.com#CAFjFpRfQaCTt1vD9E35J+XxfCnZC5HONqgJgGpUjfktJdoYZVw@mail.gmail.com

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
关系型数据库 分布式数据库 PolarDB
电子书阅读分享《PolarDB开发者大会:分布式的PolarDB》
电子书阅读分享《PolarDB开发者大会:分布式的PolarDB》
33 6
|
2月前
|
存储 关系型数据库 分布式数据库
选300平米别墅还是90平米小平层?一文带你读懂PolarDB分布式版集分一体化
1月17日,在阿里云PolarDB开发者大会上,阿里云PolarDB分布式产品部负责人黄贵发表了《分布式的PolarDB:分布式的能力,一体化的体验》主题演讲。
|
3月前
|
存储 关系型数据库 分布式数据库
选300平米别墅还是90平米小平层?一文带你读懂PolarDB分布式版集分一体化
PolarDB分布式版内核上具备了集中式分布式一体化的技术融合,支持集中式和分布式两种形态无缝切换。
选300平米别墅还是90平米小平层?一文带你读懂PolarDB分布式版集分一体化
|
6月前
|
SQL 存储 Web App开发
PolarDB-X 分布式数据库中的外键
外键是关系型数据库中非常便利的一种功能,它通过一个或多个列为两张表建立连接,从而允许跨表交叉引用相关数据。外键通过约束来保持数据的一致性,通过级联来同步数据在多表间的更新和删除。在关系数据库系统中,大多数表都遵循外键的概念。
|
10天前
|
Docker 容器 关系型数据库
【PolarDB-X从入门到精通】 第四讲:PolarDB分布式版安装部署(源码编译部署)
本期课程将于4月11日19:00开始直播,内容包括源码编译基础知识和实践操作,课程目标是使学员掌握源码编译部署技能,为未来发展奠定基础,期待大家在课程中取得丰富的学习成果!
【PolarDB-X从入门到精通】 第四讲:PolarDB分布式版安装部署(源码编译部署)
|
4月前
|
关系型数据库 分布式数据库 数据库
开营啦|PolarDB分布式版训练营,参营享限定版新年好礼
有机会领取阿里云新年礼盒大礼包等价值千元好礼
开营啦|PolarDB分布式版训练营,参营享限定版新年好礼
|
4月前
|
存储 关系型数据库 MySQL
[重磅更新]PolarDB-X V2.3 集中式和分布式一体化开源发布
2023年云栖大会,PolarDB-X 正式发布 2.3.0版本,重点推出PolarDB-X标准版(集中式形态),将PolarDB-X分布式中的DN节点提供单独服务,支持paxos协议的多副本模式、lizard分布式事务引擎,可以100%兼容MySQL。同时在性能场景上,采用生产级部署和参数(开启双1 + Paxos多副本强同步),相比于开源MySQL 8.0.34,PolarDB-X在读写混合场景上有30~40%的性能提升,可以作为开源MySQL的最佳替代选择。
|
2月前
|
关系型数据库 分布式数据库 PolarDB
电子书阅读分享《PolarDB开发者大会:分布式的PolarDB》
电子书阅读分享《PolarDB开发者大会:分布式的PolarDB》
24 4
|
2月前
|
SQL 关系型数据库 分布式数据库
PolarDB分布式版2023年度干货合集
PolarDB 分布式版 (PolarDB for Xscale,简称“PolarDB-X”) 是阿里云自主设计研发的高性能云原生分布式数据库产品,为用户提供高吞吐、大存储、低延时、易扩展和超高可用的云时代数据库服务。本文整理了PolarDB-X干货合集内容,希望对你学习和深入了解PolarDB-X有很大帮助。
|
3月前
|
存储 SQL 关系型数据库
贝泰妮使用PolarDB-X构建OMS分布式订单系统
贝泰妮使用PolarDB-X构建OMS分布式订单系统