以下为个人观点,如有纰漏敬请指正。
如何设计分布式系统-CAP和BASE理论?_技术分子的博客-CSDN博客
什么是事务?
处理问题整个过程中同时具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),称为事务。
什么分布式事务?
分布式事务是涉及两个或多个网络主机的数据库事务。
XA规范
XA 是由 X/Open 组织提出的分布式事务规范,XA 规范主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。
DTP(Distributed Transaction Processing) 模型图
Xa主要规定了RM与TM之间的交互,下面来看下XA规范中定义的RM 和 TM交互的接口:
XA协议二阶段提交的一个流程示意图:
MySQL 从5.0.3开始支持XA规范,且只有InnoDB存储引擎支持。
MySQL支持XA规范后,分为内部XA事务、外部XA事务
内部XA事务
MySQL整体架构分为三层: 网络连接层, 服务层, 存储引擎层。
在向存储引擎提交数据时(存储引擎层),同时需要将提交的信息写入二进制日志(服务层),这就是一个分布式事务被称为内部XA事务。
外部XA事务
MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,可以提供跨库的分布式事务。当然也就成了外部XA事务的协调者角色。在crash recover时控制悬挂事务是全局commit,或者rollback。
java 使用 MySQL XA 外部事务 demo
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
public class XaDemo {
public static MysqlXADataSource getDataSource(String connStr, String user, String pwd) {
try {
MysqlXADataSource ds = new MysqlXADataSource();
ds.setUrl(connStr);
ds.setUser(user);
ds.setPassword(pwd);
return ds;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] arg) {
String connStr1 = "jdbc:mysql://192.168.0.1:3306/test";
String connStr2 = "jdbc:mysql://192.168.0.2:3306/test";
try {
//从不同数据库获取数据库数据源
MysqlXADataSource ds1 = getDataSource(connStr1, "root", "123456");
MysqlXADataSource ds2 = getDataSource(connStr2, "root", "123456");
//数据库1获取连接
XAConnection xaConnection1 = ds1.getXAConnection();
XAResource xaResource1 = xaConnection1.getXAResource();
Connection connection1 = xaConnection1.getConnection();
Statement statement1 = connection1.createStatement();
//数据库2获取连接
XAConnection xaConnection2 = ds2.getXAConnection();
XAResource xaResource2 = xaConnection2.getXAResource();
Connection connection2 = xaConnection2.getConnection();
Statement statement2 = connection2.createStatement();
//创建事务分支的xid
Xid xid1 = new MysqlXid(new byte[] { 0x01 }, new byte[] { 0x02 }, 100);
Xid xid2 = new MysqlXid(new byte[] { 0x011 }, new byte[] { 0x012 }, 100);
try {
//事务分支1关联分支事务sql语句
xaResource1.start(xid1, XAResource.TMNOFLAGS);
int update1Result = statement1.executeUpdate("update account_from set money=money - 50 where id=1");
xaResource1.end(xid1, XAResource.TMSUCCESS);
//事务分支2关联分支事务sql语句
xaResource2.start(xid2, XAResource.TMNOFLAGS);
int update2Result = statement2.executeUpdate("update account_to set money= money + 50 where id=1");
xaResource2.end(xid2, XAResource.TMSUCCESS);
// 两阶段提交协议第一阶段
int ret1 = xaResource1.prepare(xid1);
int ret2 = xaResource2.prepare(xid2);
// 两阶段提交协议第二阶段
if (XAResource.XA_OK == ret1 && XAResource.XA_OK == ret2) {
xaResource1.commit(xid1, false);
xaResource2.commit(xid2, false);
System.out.println("reslut1:" + update1Result + ", result2:" + update2Result);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过程序配合MySQL的XA事务功能,可能遇到的问题:
问题一、主备数据可以可能不一致性
MySQL数据库的主备数据库的同步,通过Binlog的复制完成。而Binlog是MySQL数据库内部XA事务的协调者,并且MySQL数据库为binlog做了优化——binlog不写prepare日志,只写commit日志。
所有的参与节点prepare完成,在进行xa commit前crash,crash recover如果选择commit此事务。
由于binlog在prepare阶段未写,因此主库中看来,此分布式事务最终提交了,但是此事务的操作并未写到binlog中,因此也就未能成功复制到备库,从而导致主备库数据不一致的情况出现。
在MySQL 5.5.16版本中做过测试,这个问题实际存在。crash recover之后,对xa recover返回的事务运行xa commit,对应事务提交,但是操作并未写入binlog,因此无法复制到备库。
那么是否回滚所有prepare的事务,就可以避免此问题呢?结论是仍旧不行,不仅不能解决问题一,甚至可能引起问题二。
问题二:同一事务,在各参与节点,最终状态不一致(部分提交,部分回滚)。
所有节点完成prepare,commit阶段部分已经提交 部分机器crash。crash recove 过程选择了 回滚。最终导致同一分布式事务,在各参与节点,最终状态不一致。
结论:
XA无法彻底解决分布式一致问题。
如何设计分布式系统-分布式事务-2PC、3PC?_技术分子的博客-CSDN博客
参考:
分布式事务
XA规范
XA模型