V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tanszhe
V2EX  ›  PHP

PHP 微服务之 [分布式事物]

  •  
  •   tanszhe · 2019-05-18 18:50:45 +08:00 · 5296 次点击
    这是一个创建于 2021 天前的主题,其中的信息可能已经有所发展或是发生改变。

    分布式事物一直是微服务的一个难点。相关的解决方案和框架大部分是 java 的,那么 php 该如何解决呢?下面一步一步讲解如何用 php 解决分布式事物。

    单机单数据源事物

    首先从单机事物开始。

    大概逻辑如下 :

    
    try {  
      // 开始事物
      $db->beginTransaction();
      
      // 执行你的操作 
      // ...
      
      // 提交事物
      $db->commit();
     
    } catch (Exception $e) {
    
      // 执行失败 回滚
      $db->rollBack();
      
    }
    
    

    单机多个数据源事物

    如果你业务涉及到多个数据库,事物大概逻辑是这个样子:

    
    try {  
      // 开始事物
      $db1->beginTransaction();
      $db2->beginTransaction();
      
      // 执行你的操作 
      // ...
      
      // 提交事物
      $db1->commit();
      $db2->commit();
     
    } catch (Exception $e) {
    
      // 执行失败 回滚
      $db1->rollBack();
      $db2->rollBack();
      
    }
    
    

    多机多数据源事物(分布式事物)

    如果你的数据源和业务代码都是分开的(微服务)这就是我们今天的核心。 由前面两种情况来看,大概逻辑是差不多的,主要也分为 4 个步骤。

    1. 开始事物
    2. 执行逻辑代码
    3. 提交事物
    4. 回滚事物

    有些文章也称为tcc也就是 234 步骤。

    我们用一个常用的例子:下单。
    主要 3 个步骤:

    1. 创建订单
    2. 修改库存
    3. 修改用户积分

    假设订单,库存,用户都是独立的服务。

    按照前面的经验大概分为 4 个步骤,我们以用户为例 代码如下:

    class User
    {
    	// 开始事物
    	public function beginTransaction()
    	{
    		$db->beginTransaction();
    		return $this;
    	}
    	
    	// 执行代码
    	public function doTransaction()
    	{
    		// 执行你的操作 
      		// ...
      		return $this;
    	}
    	
    	public function commit()
    	{
    		$db->commit();
    	}
    	
    	public funtion rollBack()
    	{
    		$db->rollBack();
    	}
    
    }
    
    

    库存(stock),订单(order)和上面类似,也需要这 4 个方法,我就不写了。 难点在于我们没法直接操作数据源,只能通过 rpc 调用相应的服务来操作。依次执行上面的方法就好了。代码如下:

    try {  
      // 开始事物
      $user = new User();
      $stock = new Stock();
      $order = new Order();
      
      $user = $user->beginTransaction();
      $stock = $stock->beginTransaction();
      $order = $order->beginTransaction();
      
      
      // 执行你的操作 
      $user = $user->doTransaction();
      $stock = $stock->doTransaction();
      $order = $order->doTransaction();
      
      // 提交事物
      $user->commit();
      $stock->commit();
      $order->commit();
     
    } catch (Exception $e) {
    
      // 执行失败 回滚
      $user->rollBack();
      $stock->rollBack();
      $order->rollBack();
      
    }
    
    

    到这里可能有人看出问题来了,正常情况下这样肯定是不行的。要上面这段代码成立需要满足 1 个条件:User分别调用了 3 次,也就是 3 个请求。要保证这 3 个请求是调用的同一个实例化后的对象。StockOrder一样。

    User 调用逻辑如下:

    
    // 第一次请求调用
    $user = new User();
    $user = $user->beginTransaction();
    
    // 第二次请求调用 复用的第一次 $user
    $user = $user->doTransaction();
    
    // 第三次请求调用 复用的第一次 $user
    $user->commit();
    //或者 
    $user->rollBack();
    
    
    

    注意: 虽然调用了 3 次但是只new了一次, 第二次和第三次请求是复用的第一次的对象。要满足这个条件 服务供方必须 常驻内存 ,而且提供的rpc 服务必须支持链式调用的功能。

    one框架 https://github.com/lizhichao/one
    极简 . 高性能 . 松耦合 . 分布式 . 可运行于多种环境

    one框架完美支持上面的要求。只需要把上面的UserStockOrder添加为 rpc 服务即可。还需要注意beginTransactiondoTransaction方法必须返回$this提供给后面的方法调用。

    user 服务如下:

    RpcServer::add(User::class);
    
    

    其他两个类似。到此分布式事物问题就搞定了,可能觉得这么简单吗?这主要由于one 框架的 rpc 服务提供了链式调用(多个请求复用同一个对象)的功能。

    可能有人要问:如果因为网络问题或者其他问题导致最后一个服务的最后一次调用失败了怎么办? 解决方案就是 事物补偿,你可以把这类极端的情况下的错误,放到一个队列里 起一服务来专门处理这里问题。

    25 条回复    2019-05-22 20:27:38 +08:00
    huangzhe8263
        1
    huangzhe8263  
       2019-05-18 19:11:14 +08:00 via Android
    没了解过 PHP
    确定不是 分布式事务 么😂
    yunye
        2
    yunye  
       2019-05-18 19:11:27 +08:00
    分布式事 [务] 不是物。。
    tanszhe
        3
    tanszhe  
    OP
       2019-05-18 19:15:47 +08:00
    @huangzhe8263 @yunye 感觉纠正 , 没有编辑按钮了 ……
    tanszhe
        4
    tanszhe  
    OP
       2019-05-18 19:16:34 +08:00
    @huangzhe8263 @yunye 感谢纠正,没有编辑按钮了 ……, 还么有评论删除按钮……
    veike
        5
    veike  
       2019-05-18 20:29:16 +08:00 via Android
    一有错别字我就看不下去,因为让我觉得要么写文章的人不认真,要么就是基础不唠。而且通篇的,我的小心脏。我的天呐。
    crazypig14
        6
    crazypig14  
       2019-05-18 20:36:56 +08:00
    同意楼上
    zjsxwc
        7
    zjsxwc  
       2019-05-18 20:37:39 +08:00 via Android
    这是分布式吗?表示怀疑⊙∀⊙?
    tanszhe
        8
    tanszhe  
    OP
       2019-05-18 20:45:15 +08:00
    @veike @crazypig14 @zjsxwc 一个技术男,对文字不敏感 和 专业段子手不能比
    way2create
        9
    way2create  
       2019-05-18 20:55:53 +08:00
    很好奇怎么能写错那么多事物 一次两次还能理解
    crazypig14
        10
    crazypig14  
       2019-05-18 20:56:17 +08:00
    好吧技术男,你的 rpc 服务是把一个本地调用的类包装成了一个代理供远程调。
    队列作事务补偿以满足最终一致性没问题,是个方案,但没包括在你的框架内。
    因此我觉得你的框架不能号称解决了我们通常所理解的分布式事务问题。
    关于分布式事务网上相关的帖子很多 https://yq.aliyun.com/articles/582282
    传统的事务协调器,两阶段提交这些建议可以参考了解下,php 框架在这方面想做好并不容易。
    tanszhe
        11
    tanszhe  
    OP
       2019-05-18 21:01:28 +08:00
    @crazypig14 如果连队列操作都不会 ,不用看这篇文章了。
    crazypig14
        12
    crazypig14  
       2019-05-18 21:15:58 +08:00
    没错,操作队列很简单的。
    但这篇帖子看标题是要讲分布式事务,并且关键部分最终一致性是要用队列做解决方案,我建议前面框架部分可以少讲一点,然后把怎么用队列来解决分布式事务讲详细点,否则感觉有点文不对题。
    并且用队列的方案一样会有一些坑,队列本身的可用性,以及队列消息的状态转换,比简单操作队列要复杂。
    tanszhe
        13
    tanszhe  
    OP
       2019-05-18 21:28:03 +08:00
    @crazypig14 队列是解决极端情况,用大篇幅来描述一个极端情况,你这个就好比 杞人忧天
    crazypig14
        14
    crazypig14  
       2019-05-18 22:03:10 +08:00 via Android
    好吧 我觉得我们从分布式事务的定义开始就有分歧 话题到此结束了
    不管怎么说 lz 的框架本身从加速开发或者 rpc 规范的角度还是有优点
    xhinliang
        15
    xhinliang  
       2019-05-18 22:51:16 +08:00
    这跟微服务有半毛钱关系么...
    ben1024
        16
    ben1024  
       2019-05-18 22:58:14 +08:00
    这概念。。。有点歪,想说的是嵌套事务?
    ```
    /**
    * Commit the active database transaction.
    *
    * @return void
    */
    public function commit()
    {
    if ($this->transactions == 1) {
    $this->getPdo()->commit();
    }

    $this->transactions = max(0, $this->transactions - 1);

    $this->fireConnectionEvent('committed');
    }
    ```
    hst001
        17
    hst001  
       2019-05-18 23:05:45 +08:00
    “事务”没有一个写对的
    m939594960
        18
    m939594960  
       2019-05-18 23:13:49 +08:00
    我想问个问题,为啥你们用了 swoole 写日志的部分 还在用 file_put_content 或者 error_log 这种函数啊? 不会阻塞么?????
    Hanada
        19
    Hanada  
       2019-05-19 01:18:36 +08:00 via Android
    当我看到第一个“事物”,我觉得不是“事务”么,然后觉得可能只是楼主输入法有点毛病,然后……我发现通篇都是事物刷屏洗脑之后,搞得我真的相信这里说的就是“事物”了。然后再看评论,然后赶紧将我这种想法剔除掉了。
    eslizn
        20
    eslizn  
       2019-05-19 01:34:01 +08:00
    @m939594960 error_log 不清楚,但是 file_put_content 会用协程 io
    CoderGeek
        21
    CoderGeek  
       2019-05-19 02:42:06 +08:00
    上周刚自己玩了玩 lcn
    xuanbg
        22
    xuanbg  
       2019-05-19 08:34:41 +08:00
    根本就不存在“分布式事务”这种东西,不光 PHP 没有,Java 生态里面也没有。有的只是分布式系统数据一致性解决方案!注意,只有解决方案,没有现成可用的框架和代码,所有的一切都需要自己根据自己的业务去实现。
    m939594960
        23
    m939594960  
       2019-05-19 10:13:26 +08:00
    @eslizn #20 文档上说过么?我看文档有专门协程写文件的啊?
    AngryPanda
        24
    AngryPanda  
       2019-05-19 22:03:36 +08:00
    楼主你是来黑 PHP 的吧
    ChoateYao
        25
    ChoateYao  
       2019-05-22 20:27:38 +08:00
    技术果然是通用的,啰嗦了一大堆还不如在开头写一句参考 JTA 事物。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3711 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 10:24 · PVG 18:24 · LAX 02:24 · JFK 05:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.