分布式事务

(!!!最后防线,对账!!!, 注意采用幂等性)

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);
}

最终人工对账,补偿,成本远低于完整的自动化分布式事务回滚!!!!

posted on 2018-10-26 16:54  陈峰  阅读(274)  评论(0编辑  收藏  举报