Logo
事务管理

事务管理 #

事务原理 #

虽然 SphereEx-DBPlusEngine 希望能够完全兼容所有的分布式事务场景,并在性能上达到最优,但在 CAP 定理所指导下,分布式事务必然有所取舍。SphereEx-DBPlusEngine 希望能够将分布式事务的选择权交给使用者,在不同的场景用使用最适合的分布式事务解决方案。由于应用的场景不同,需要开发者能够合理的在性能与功能之间权衡各种分布式事务。强一致的事务与柔性事务的 API 和功能并不完全相同,在它们之间并不能做到自由的透明切换。在开发决策阶段,就不得不在强一致的事务和柔性事务之间抉择,使得设计和开发成本被大幅增加。基于 XA 的强一致事务使用相对简单,但是无法很好的应对互联网的高并发或复杂系统的长事务场景;柔性事务则需要开发者对应用进行改造,接入成本非常高,并且需要开发者自行实现资源锁定和反向补偿。

事务分类 #

SphereEx-DBPlusEngine 提供了三种事务模式:LOCAL,XA,BASE,以应对不同的场景。

  • LOCAL:适用于对数据一致性要求不高的场景。
  • XA:提供了原子性的保证,保证了数据不丢,不保证快照读。适用于对一致性要求相对高,没有快照读要求的场景。在对一致性要求较高的场景,较好的选择是使用 XA 的 Narayana 实现。如果底层存储节点的数据库隔离级是 Serializable ,可以达到强一致性的语义。
  • BASE:在一致性和性能之间做了权衡,具体参考 Seata 官网。

使用方法 #

SphereEx-DBPlusEngine 中可通过配置文件(global.yaml)完成配置。主要是完成事务处理方式和分布式事务处理机制的配置。具体如下:

  • defaultType

使用LOCAL事务

transaction:
  defaultType: LOCAL

使用XA事务

transaction:
  defaultType: XA

使用BASE事务

transaction:
  defaultType: BASE
  • providerType

仅对 defaultType=XA 情况下适用。SphereEx-DBPlusEngine 支持多种分布式事务处理器:Atomikos、Narayana。建议用户选择使用 Narayana。

transaction:
  defaultType: XA
  providerType: Narayana

注意事项 #

本地事务 #

  • 支持项

    • 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中;
    • 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能够回滚。
  • 不支持项

    不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交,且无法回滚。

XA 事务 #

  • 支持项

    • 支持数据分片后的跨库事务;
    • 两阶段提交保证操作的原子性和数据的强一致性;
    • 服务宕机重启后,提交/回滚中的事务可自动恢复;
    • 支持同时使用 XA 和非 XA 的连接池。
    • 支持通过数据库和文件方式存储事务恢复日志。
  • 不支持项

    • 服务宕机后,在其它机器上恢复提交/回滚中的数据;
    • MySQL 事务块内,SQL 执行出现异常,执行 Commit,数据保持一致;
    • 配置 XA 事务后,存储单元名称最大长度不超过45个字符。
    • 当使用 DB 方式存储 XA 事务时,若存储 DB 不可用,会影响事务的执行和恢复。
  • XA 事务所需的权限:

    • MySQL 8:MySQL 8 数据库需要授予用户 XA_RECOVER_ADMIN 权限,否则 XA 事务管理器执行 XA RECOVER 语句时会报错,通过执行 GRANT XA_RECOVER_ADMIN TO currentUser 进行授权;
    • Oracle:Oracle 数据库需要授予用户 SYS.DBA_PENDING_TRANSACTIONS 查询权限,通过执行 GRANT SELECT ON SYS.DBA_PENDING_TRANSACTIONS TO currentUser 进行授权。

柔性事务 #

  • 支持项

    • 支持数据分片后的跨库事务;
    • 支持 RC 隔离级别;
    • 通过 undo 快照进行事务回滚;
    • 支持服务宕机后的,自动恢复提交中的事务。
  • 不支持项

    不支持除 RC 之外的隔离级别。

  • 待优化项

    SphereEx-DBPlusEngine 和 SEATA 重复 SQL 解析。

分布式事务实践 #

Narayana 使用方法 #

针对常见的分布式事务需求,SphereEx-DBPlusEngine 推荐使用 Narayana 实现的分布式事务,具体使用方法如下:

由于 Narayana 配置比较繁琐,DBPlusEngine-Proxy 提供了自动化配置功能,现在用户无需手动配置 jbossts-properties.xml 配置文件,DBPlusEngine 会自动根据用户在 global.yaml 里指定的 Narayana 事务配置,生成对应的 jbossts-properties.xml 配置文件。

  • global.yaml 当使用 Narayana 作为 XA 事务管理器时,且没有在 props 里配置额外的属性。集群模式下,会自动配置为使用 DB 方式存储 XA Recovery 信息;单机模式下,会自动配置为使用文件方式存储 XA Recovery 信息。
transaction:
  defaultType: XA
  providerType: Narayana

当使用 Narayana 作为 XA 事务管理器时,支持在 props 里配置事务超时时间,单位秒,默认不配置为 180 秒。

transaction:
  defaultType: XA
  providerType: Narayana
  props:
    defaultTimeout: 300 # 配置事务超时时间 300 秒

当使用 Narayana 作为 XA 事务管理器时,并且配置了使用 DB 方式存储 XA Recovery 信息,则 DBPlusEngine-Proxy 支持将故障的 Proxy 实例上尚未恢复的事务转移到其他 Proxy 上进行恢复。

配置如下:

transaction:
  defaultType: XA
  providerType: Narayana
  props:
    recoveryStoreUrl: jdbc:mysql://127.0.0.1:3306/jbossts?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true# mysql8 使用 com.mysql.cj.jdbc.MysqlDataSourcerecoveryStoreDataSource: com.mysql.jdbc.jdbc2.optional.MysqlDataSource
    recoveryStoreUser: databaseUser
    recoveryStorePassword: databasePwd

当使用 DB 方式存储 XA Recovery 信息时,需要提前创建好对应的存储事务恢复日志的数据库,比如上面的 jbossts 数据库。Narayana 会自动在该库下创建所需的表,包括 CommunicationJBossTSTxTableActionJBossTSTxTable 表。 在 XA 事务执行期间,Narayana 会向 ActionJBossTSTxTable 表记录事务日志,在事务执行完成后删除事务日志记录。期间如果 RM 或 TM 发生故障,会依赖 ActionJBossTSTxTable 表存储的事务日志进行事务恢复。所以需要保证事务恢复数据库的可用性,如果发生可用性问题,会导致事务执行失败,影响业务。

使用文件方式存储 XA Recovery 信息的配置如下:

transaction:
  defaultType: XA
  providerType: Narayana
  props:
    recoveryStoreType: File

注意,在集群模式下,使用文件方式存储 XA Recovery 信息会导致 Proxy 实例之间无法共享事务恢复日志,因此不推荐使用文件方式存储 XA Recovery 信息。

通过 XA 语句控制的分布式事务 #

  • 通过 XA START 可以手动开启 XA 事务,注意该事务完全由用户管理,DBPlusEngine 只负责将语句转发至后端数据库;

  • 服务宕机后,需要通过 XA RECOVER 获取未提交或回滚的事务,也可以在 COMMIT 时使用 ONE PHASE 跳过 PERPARE。

MySQL [(none)]> use test1                                                                               MySQL [(none)]> use test2
Reading table information for completion of table and column names                                      Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A                                          You can turn off this feature to get a quicker startup with -A
                                                                                                        
Database changed                                                                                        Database changed
MySQL [test1]> XA START '61c052438d3eb';                                                                MySQL [test2]> XA START '61c0524390927';
Query OK, 0 rows affected (0.030 sec)                                                                   Query OK, 0 rows affected (0.009 sec)
                                                                                                        
MySQL [test1]> update test set val = 'xatest1' where id = 1;                                            MySQL [test2]> update test set val = 'xatest2' where id = 1;
Query OK, 1 row affected (0.077 sec)                                                                    Query OK, 1 row affected (0.010 sec)
                                                                                                        
MySQL [test1]> XA END '61c052438d3eb';                                                                  MySQL [test2]> XA END '61c0524390927';
Query OK, 0 rows affected (0.006 sec)                                                                   Query OK, 0 rows affected (0.008 sec)
                                                                                                        
MySQL [test1]> XA PREPARE '61c052438d3eb';                                                              MySQL [test2]> XA PREPARE '61c0524390927';
Query OK, 0 rows affected (0.018 sec)                                                                   Query OK, 0 rows affected (0.011 sec)
                                                                                                        
MySQL [test1]> XA COMMIT '61c052438d3eb';                                                               MySQL [test2]> XA COMMIT '61c0524390927';
Query OK, 0 rows affected (0.011 sec)                                                                   Query OK, 0 rows affected (0.018 sec)
                                                                                                        
MySQL [test1]> select * from test where id = 1;                                                         MySQL [test2]> select * from test where id = 1;
+----+---------+                                                                                        │+----+---------+
| id | val     |                                                                                        | id | val     |
+----+---------+                                                                                        │+----+---------+
|  1 | xatest1 |                                                                                        |  1 | xatest2 |
+----+---------+                                                                                        │+----+---------+
1 row in set (0.016 sec)                                                                                1 row in set (0.129 sec)

MySQL [test1]> XA START '61c05243994c3';                                                                MySQL [test2]> XA START '61c052439bd7b';
Query OK, 0 rows affected (0.047 sec)                                                                   Query OK, 0 rows affected (0.006 sec)
                                                                                                        
MySQL [test1]> update test set val = 'xarollback' where id = 1;                                         MySQL [test2]> update test set val = 'xarollback' where id = 1;
Query OK, 1 row affected (0.175 sec)                                                                    Query OK, 1 row affected (0.008 sec)
                                                                                                        
MySQL [test1]> XA END '61c05243994c3';                                                                  MySQL [test2]> XA END '61c052439bd7b';
Query OK, 0 rows affected (0.007 sec)                                                                   Query OK, 0 rows affected (0.014 sec)
                                                                                                        
MySQL [test1]> XA PREPARE '61c05243994c3';                                                              MySQL [test2]> XA PREPARE '61c052439bd7b';
Query OK, 0 rows affected (0.013 sec)                                                                   Query OK, 0 rows affected (0.019 sec)
                                                                                                        
MySQL [test1]> XA ROLLBACK '61c05243994c3';                                                             MySQL [test2]> XA ROLLBACK '61c052439bd7b';
Query OK, 0 rows affected (0.010 sec)                                                                   Query OK, 0 rows affected (0.010 sec)
                                                                                                        
MySQL [test1]> select * from test where id = 1;                                                         MySQL [test2]> select * from test where id = 1;
+----+---------+                                                                                        │+----+---------+
| id | val     |                                                                                        | id | val     |
+----+---------+                                                                                        │+----+---------+
|  1 | xatest1 |                                                                                        |  1 | xatest2 |
+----+---------+                                                                                        │+----+---------+
1 row in set (0.009 sec)                                                                                1 row in set (0.083 sec)

MySQL [test1]>  XA START '61c052438d3eb';
Query OK, 0 rows affected (0.030 sec)

MySQL [test1]> update test set val = 'recover' where id = 1;
Query OK, 1 row affected (0.072 sec)

MySQL [test1]> select * from test where id = 1;
+----+---------+
| id | val     |
+----+---------+
|  1 | recover |
+----+---------+
1 row in set (0.039 sec)

MySQL [test1]>  XA END '61c052438d3eb';
Query OK, 0 rows affected (0.005 sec)

MySQL [test1]> XA PREPARE '61c052438d3eb';
Query OK, 0 rows affected (0.020 sec)

MySQL [test1]> XA RECOVER;
+----------+--------------+--------------+---------------+
| formatID | gtrid_length | bqual_length | data          |
+----------+--------------+--------------+---------------+
|        1 |           13 |            0 | 61c052438d3eb |
+----------+--------------+--------------+---------------+
1 row in set (0.010 sec)

MySQL [test1]> XA RECOVER CONVERT XID;
+----------+--------------+--------------+------------------------------+
| formatID | gtrid_length | bqual_length | data                         |
+----------+--------------+--------------+------------------------------+
|        1 |           13 |            0 | 0x36316330353234333864336562 |
+----------+--------------+--------------+------------------------------+
1 row in set (0.011 sec)

MySQL [test1]> XA COMMIT 0x36316330353234333864336562;
Query OK, 0 rows affected (0.029 sec)

MySQL [test1]> XA RECOVER;
Empty set (0.011 sec)