分布式事务
(!!!最后防线,对账!!!, 注意采用幂等性)
1、两阶段提交协议 2PC (prepare, commit)《MYSQL支持》
一个协调者、多个参与者
- 准备阶段:协调者通知参考者,执行准备提交;有失败则统一通知回滚
- 提交阶段:协调者通知参考者,执行提交;有失败则统一通知回滚
缺点:
1、同步阻塞、执行过程中所有资源都处理同步阻塞
2、单点故障、由于协调者是单点,出现故障参与者都无法执行
3、数据不一致、当协调者在通知提交时,它与某个参与者网络断开,导致某个参与者无法联系,出现分布式系统数据不一致
<?PHP
$dbtest1 = new mysqli("172.20.101.17","public","public","dbtest1")or die("dbtest1 连接失败");
$dbtest2 = new mysqli("172.20.101.18","public","public","dbtest2")or die("dbtest2 连接失败");
//为XA事务指定一个id,xid 必须是一个唯一值。
$xid = uniqid("");
//两个库指定同一个事务id,表明这两个库的操作处于同一事务中
$dbtest1->query("XA START '$xid'");//准备事务1
$dbtest2->query("XA START '$xid'");//准备事务2
try {
//$dbtest1
$return = $dbtest1->query("UPDATE member SET name='唐大麦' WHERE id=1") ;
if($return == false) {
throw new Exception("库dbtest1@172.20.101.17执行update member操作失败!");
}
//$dbtest2
$return = $dbtest2->query("UPDATE memberpoints SET point=point+10 WHERE memberid=1") ;
if($return == false) {
throw new Exception("库dbtest1@172.20.101.18执行update memberpoints操作失败!");
}
//阶段1:$dbtest1提交准备就绪
$dbtest1->query("XA END '$xid'");
$dbtest1->query("XA PREPARE '$xid'");
//阶段1:$dbtest2提交准备就绪
$dbtest2->query("XA END '$xid'");
$dbtest2->query("XA PREPARE '$xid'");
//阶段2:提交两个库
$dbtest1->query("XA COMMIT '$xid'");
$dbtest2->query("XA COMMIT '$xid'");
}
catch (Exception $e) {
//阶段2:回滚
$dbtest1->query("XA ROLLBACK '$xid'");
$dbtest2->query("XA ROLLBACK '$xid'");
die($e->getMessage());
}
$dbtest1->close();
$dbtest2->close();
?>
2、三段提交 3PC (CanCommit、PreCommit、DoCommit)
就是把二阶段提交中的第一阶段(准备阶段)分为2阶段,这样就有了CanCommit、PreCommit、DoCommit三个阶段.(没有解决啥问题,加点东西就改进了?)
3、补偿事务TCC(Try, Commit,Cancel)
以转账为例。
-
Try, 准备或者预处理, 冻结资金
-
Commit, 提交, 扣除资金
-
Cancel, 补偿处理, 出现异常时, 反向加一笔资金平账。
优点:比二段式简单
缺点:2、3两步都可以出现失败,出现数据不一致
4、本地消息表
将分布式事分拆分成多个本地事务, 生产者和消费都都有业务表,都额外创建一个消息表,都有一个定时检查器。生产者与消费者通过MQ连接。
- 生产者,处理本地事务,<处理业务,添加对应消息>, 通知消息到消费者,失败则重试
- 消费者, 处理消息成功,则完成;失败则重试;如果业务失败,则通知生产者反向补平。
- 双方定时扫描消息表, 重发未完成或者失败的消息。
优点:避免了分布式事务所,实现最终一致性
缺点:消息表会业务耦合到系统中,各种耦合表;还有定时器扫描消耗
5、Rocket MQ事务消息
为了解决本地消息表中的缺陷,消除消息表。RocketMQ提出了”事务消息“的概念
1、发送Prepare消息
2、Update DB
3、根据update db结果成功或者失败,Confirm或者取消消息
// ==============发送事务消息的一系列准备工作=======
// 未决事务,MQ服务器回查客户端
// 也就是上文所说的,当RocketMQ发现`Prepared消息`时,会根据这个Listener实现的策略来决断事务
TransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl();
// 构造事务消息的生产者
TransactionMQProducer producer = new TransactionMQProducer("groupName");
// 设置事务决断处理类
producer.setTransactionCheckListener(transactionCheckListener);
// 本地事务的处理逻辑,相当于示例中检查Bob账户并扣钱的逻辑
TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();
producer.start()
// 构造MSG,省略构造参数
Message msg = new Message(......);
// 发送消息
SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null);
producer.shutdown();
// ==================事务消息的发送过程========
public TransactionSendResult sendMessageInTransaction(.....) {
// 逻辑代码,非实际代码
// 1.发送消息
sendResult = this.send(msg);
// sendResult.getSendStatus() == SEND_OK
// 2.如果消息发送成功,处理与消息关联的本地事务单元
LocalTransactionState localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
// 3.结束事务
this.endTransaction(sendResult, localTransactionState, localException);
}