【DTM】PHP协程客户端v0.1 beta版本发布啦!

dtm/dtm-client 是分布式事务管理器 DTM 的 PHP 客户端,已支持 TCC模式、Saga、二阶段消息模式的分布式事务模式,并分别实现了与 DTM Server 以 HTTP 协议或 gRPC 协议通讯。

好消息!DTM 分布式事务管理器 PHP 协程客户端 v0.1 beta 版本发布!!!→ github.com/dtm-php/dtm-client

介绍

dtm/dtm-client 是分布式事务管理器 DTM 的 PHP 客户端,已支持 TCC模式、Saga、二阶段消息模式的分布式事务模式,并分别实现了与 DTM Server 以 HTTP 协议或 gRPC 协议通讯,该客户端可安全运行于 PHP-FPM 和 Swoole 协程环境中,更是对 Hyperf 做了更加易用的功能支持。

关于 DTM

DTM 是一款基于 Go 语言实现的开源分布式事务管理器,提供跨语言,跨存储引擎组合事务的强大功能。DTM 优雅的解决了幂等、空补偿、悬挂等分布式事务难题,也提供了简单易用、高性能、易水平扩展的分布式事务解决方案。

亮点

  • 极易上手
    • 零配置启动服务,提供非常简单的 HTTP 接口,极大降低上手分布式事务的难度
  • 跨语言
    • 可适合多语言栈的公司使用。方便 Go、Python、PHP、NodeJs、Ruby、C# 等各类语言使用。
  • 使用简单
    • 开发者不再担心悬挂、空补偿、幂等各类问题,首创子事务屏障技术代为处理
  • 易部署、易扩展
    • 仅依赖 MySQL/Redis,部署简单,易集群化,易水平扩展
  • 多种分布式事务协议支持
    • TCC、SAGA、XA、二阶段消息,一站式解决多种分布式事务问题

对比

在非 Java 语言下,暂未看到除 DTM 之外的成熟的分布式事务管理器,因此这里将 DTM 和 Java 中最成熟的开源项目 Seata 做对比:

特性DTMSEATA备注
支持语言 Go、C#、Java、Python、PHP… Java DTM 可轻松接入一门新语言
存储引擎 支持数据库、Redis、Mongo等 数据库
异常处理 子事务屏障自动处理 手动处理 DTM 解决了幂等、悬挂、空补偿
SAGA事务 极简易用 复杂状态机
二阶段消息 最简消息最终一致性架构
TCC事务
XA事务
AT事务 建议使用XA AT 与 XA类似,但有脏回滚
单服务多数据源
通信协议 HTTP、gRPC Dubbo等协议 DTM对云原生更加友好
star数量 【DTM】PHP协程客户端v0.1 beta版本发布啦! 【DTM】PHP协程客户端v0.1 beta版本发布啦! DTM 从 2021-06-04 发布 0.1版本,发展飞快

从上面对比的特性来看,DTM 在许多方面都具备很大的优势。如果考虑多语言支持、多存储引擎支持,那么 DTM 毫无疑问是您的首选.

php零基础到就业直播视频课:进入学习Apipost-比 postman 更好用的 API 文档、设计、调试、自动化测试一体化协作平台!点击使用

安装

通过 Composer 可以非常方便的安装 dtm-client

composer require dtm/dtm-client

  • 使用时别忘了启动 DTM Server 哦

配置配置文件

如果您是在 Hyperf 框架中使用,在安装组件后,可通过下面的 vendor:publish 命令一件发布配置文件于 ./config/autoload/dtm.php

php bin/hyperf.php vendor:publish dtm/dtm-client

如果您是在非 Hyperf 框架中使用,可复制 ./vendor/dtm/dtm-client/publish/dtm.php 文件到对应的配置目录中。

use DtmClient\Constants\Protocol;
use DtmClient\Constants\DbType;

return [
// 客户端与 DTM Server 通讯的协议,支持 Protocol::HTTP 和 Protocol::GRPC 两种
;protocol; => Protocol::HTTP,
// DTM Server 的地址
;server; => ;127.0.0.1;,
// DTM Server 的端口
;port; => [
;http; => 36789,
;grpc; => 36790,
],
// 子事务屏障配置
;barrier; => [
// DB 模式下的子事务屏障配置
;db; => [
;type; => DbType::MySQL
],
// Redis 模式下的子事务屏障配置
;redis; => [
// 子事务屏障记录的超时时间
;expire_seconds; => 7 * 86400,
],
// 非 Hyperf 框架下应用子事务屏障的类
;apply; => [],
],
// HTTP 协议下 Guzzle 客户端的通用配置
;guzzle; => [
;options; => [],
],
];配置中间件

在使用之前,需要配置 DtmClient\Middleware\DtmMiddleware 中间件作为 Server 的全局中间件,该中间件支持 PSR-15 规范,可适用于各个支持该规范的的框架。在 Hyperf 中的中间件配置可参考 Hyperf文档 – 中间件 一章。

使用

dtm-client 的使用非常简单,我们提供了一个示例项目 dtm-php/dtm-sample 来帮助大家更好的理解和调试。在使用该组件之前,也强烈建议您先阅读 DTM 官方文档,以做更详细的了解。

TCC 模式

TCC 模式是一种非常流行的柔性事务解决方案,由 Try-Confirm-Cancel 三个单词的首字母缩写分别组成 TCC 的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文中提出。

TCC 的 3 个阶段

Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)Confirm 阶段:如果所有分支的 Try 都成功了,则走到 Confirm 阶段。Confirm 真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源Cancel 阶段:如果所有分支的 Try 有一个失败了,则走到 Cancel 阶段。Cancel 释放 Try 阶段预留的业务资源。

如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的 TCC 事务典型的时序图如下:

【DTM】PHP协程客户端v0.1 beta版本发布啦!

代码示例

以下展示在 Hyperf 框架中的使用方法,其它框架类似

<?php
namespace App\Controller;

use DtmClient\TCC;
use DtmClient\TransContext;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Throwable;

#[Controller(prefix: ;/tcc;)]
class TccController
{

protected string $serviceUri = ;http://127.0.0.1:9501;;

#[Inject]
protected TCC $tcc;

#[GetMapping(path: ;successCase;)]
public function successCase()
{
try {

$this->tcc->globalTransaction(function (TCC $tcc) {
// 创建子事务 A 的调用数据
$tcc->callBranch(
// 调用 Try 方法的参数
[;amount; => 30],
// Try 方法的 URL
$this->serviceUri . ;/tcc/transA/try;,
// Confirm 方法的 URL
$this->serviceUri . ;/tcc/transA/confirm;,
// Cancel 方法的 URL
$this->serviceUri . ;/tcc/transA/cancel;
);
// 创建子事务 B 的调用数据,以此类推
$tcc->callBranch(
[;amount; => 30],
$this->serviceUri . ;/tcc/transB/try;,
$this->serviceUri . ;/tcc/transB/confirm;,
$this->serviceUri . ;/tcc/transB/cancel;
);
});
} catch (Throwable $e) {
var_dump($e->getMessage(), $e->getTraceAsString());
}
// 通过 TransContext::getGid() 获得 全局事务ID 并返回
return TransContext::getGid();
}
}Saga 模式

Saga 模式是分布式事务领域最有名气的解决方案之一,也非常流行于各大系统中,最初出现在 1987 年 由 Hector Garcaa-Molrna & Kenneth Salem 发表的论文 SAGAS 里。

Saga 是一种最终一致性事务,也是一种柔性事务,又被叫做 长时间运行的事务(Long-running-transaction),Saga 是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发 Saga 全局事务中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga 会执行在这个失败的事务之前成功提交的所有事务的补偿操作。所以 Saga 模式在对比 TCC 模式时,因缺少了资源预留的步骤,往往在实现回滚逻辑时会变得更麻烦。

Saga 子事务拆分

比如我们要进行一个类似于银行跨行转账的业务,将 A 账户中的 30 元转到 B 账户,根据 Saga 事务的原理,我们将整个全局事务,拆分为以下服务:

  • 转出(TransOut)服务,这里将会进行操作 A 账户扣减 30 元
  • 转出补偿(TransOutCompensate)服务,回滚上面的转出操作,即 A 账户增加 30 元
  • 转入(TransIn)服务,这里将会进行 B 账户增加 30 元
  • 转出补偿(TransInCompensate)服务,回滚上面的转入操作,即 B 账户减少 30 元

整个事务的逻辑是:

执行转出成功 => 执行转入成功 => 全局事务完成

如果在中间发生错误,例如转入 B 账户发生错误,则会调用已执行分支的补偿操作,即:

执行转出成功 => 执行转入失败 => 执行转入补偿成功 => 执行转出补偿成功 => 全局事务回滚完成

下面是一个成功完成的 SAGA 事务典型的时序图:

【DTM】PHP协程客户端v0.1 beta版本发布啦!

代码示例

以下展示在 Hyperf 框架中的使用方法,其它框架类似

namespace App\Controller;

use DtmClient\Saga;
use DtmClient\TransContext;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;

#[Controller(prefix: ;/saga;)]
class SagaController
{

protected string $serviceUri = ;http://127.0.0.1:9501;;

#[Inject]
protected Saga $saga;

#[GetMapping(path: ;successCase;)]
public function successCase(): string
{
$payload = [;amount; => 50];
// 初始化 Saga 事务
$this->saga->init();
// 增加转出子事务
$this->saga->add(
$this->serviceUri . ;/saga/transOut;,
$this->serviceUri . ;/saga/transOutCompensate;,
$payload
);
// 增加转入子事务
$this->saga->add(
$this->serviceUri . ;/saga/transIn;,
$this->serviceUri . ;/saga/transInCompensate;,
$payload
);
// 提交 Saga 事务
$this->saga->submit();
// 通过 TransContext::getGid() 获得 全局事务ID 并返回
return TransContext::getGid();
}
}

PHP yield 协程 生成器用法的了解

转载2020-07-02 18:02:151617 + php学习QQ群:609135716

【DTM】PHP协程客户端v0.1 beta版本发布啦!

php零基础到就业直播视频课:进入学习Apipost-比 postman 更好用的 API 文档、设计、调试、自动化测试一体化协作平台!点击使用

写在前面

这篇文章,要和大家探讨的是 PHP yield 在 生成器用法,不带 foreach,for, while 循环的那种。就讨论 yield 将一个函数变成为生成器的用法。

关于yield 特性,是在开发 PHP5 时被提上日程,PHP5.5 版本正式加入。

关于yield的使用,我看到大部分文章都停留在,使用yield如何在foreach中穿出数据,今天想给大家讲讲 生成器 所有语法。

官网讲解

生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

看了下官网对他讲解:php.net 生成器语法 . 每个字都认识,但似乎还是体会到它讲的内涵。官网我们主要看两部分内容:

  • yield 的语法。

  • 代码例子。

  • 先说语法, yield 的左边是一个赋值语句,右边可以是值(也可是表达式) 。而yield 会先执行右边的表达式,并把值$value送到生成器外面。当生成器收到值后,会执行yield左边的语句,赋值给$data.

    <?phpfunction func(){
    $data = (yield [$express]);}

    语法就这样,估计大家还是有些懵,那就看看官网下面代码例子吧,我看里面例子参差不齐。

    注意yield 外面包的这一层括号,如果是在php5.5,右侧$express的优先级是判断,可能会比左侧$data的赋值语句低的。所以在php5用yield,yield 右边是可运行表达式,左侧需要接受返回并赋值,那么这个括号是有必要的。在php7不会有这个问题。

    通过例子来了解它

    不论是学 人类语言,计算机语言,都是模仿开始

    对于一个用人类语言来描述,都不那么明晰时,所以那就通过例子告诉你它能做什么,不能做什么。

    相关代码,我放到gitee了,希望你能复制到你本地运行下,亲自运行感受下,有助于了理解接下来的内容。

    git clone gitee.com/xupaul/PHP-generator-yie…

    怎样才能产生 Generator

    先定义一个函数,在函数内 写个 yield 关键词,将这个函数调用赋值给一个变量。一个生成器就产生了。

    代码 /php-yield-test/yieldFunctions.php 是生成器按照不同语法组合定义了多个生成器。

    测试代码 /php-yield-test/whatIsGenerator.php,用来检查哪些函数能构成生成器,哪些不能。运行结果如下

    【DTM】PHP协程客户端v0.1 beta版本发布啦!

  • 函数内必须有 yield 关键词,函数可以是全剧函数,或者类的方法。
  • 哪怕 yield 肯定不会被执行,也会产生生成器。见:yield_func4
  • 光秃秃 的 yield 关键词就行(不向外送出,不处理外面的输入)。见: yield_func2
  • 函数内使用 生成器 并不能让自己也成为生成器,见:yield_func5
  • eval函数中直接运行 yield 会报错, 见:yield_func11
  • 是的,函数内有没有foreach,while,for 语句都不是关键,关键是 yield. 生成器的类型判断用 $gen instanceof Generator

    生成器的函数

    Generator 对象是从 generators返回的.

    Generator 对象不能通过 new 实例化.

    • Generator::current — 返回当前产生的值
    • Generator::key — 返回当前产生的键
    • Generator::next — 生成器继续执行
    • Generator::rewind — 重置迭代器
    • Generator::send — 向生成器中传入一个值
    • Generator::throw — 向生成器中抛入一个异常
    • Generator::valid — 检查迭代器是否被关闭
    • Generator::__wakeup — 序列化回调
    • Gengerator::getReturn – Get the return value of a generator

    摘自 php.net generator

    看着以上方法,是不想起了Iterator, 他们的确很像。同时注意,官网zh语言版本的文档没有索引方法getReturn,访问也是404。文档以en版为准,ch做参考。

    以上就是生成器所有的方法,我们一个个来看。

    测试方法代码 /php-yield-test/generatorMothod.php, 这里面对每个方法都有使用举例,运行结果如下。

    【DTM】PHP协程客户端v0.1 beta版本发布啦!

    【DTM】PHP协程客户端v0.1 beta版本发布啦!

    好接下来对举例做个一一讲解。

    Generator::current

    • 返回当前产生的值

    <?phpfunction yield_func(){
    yield 12;
    return 'a';}$gen = yield_func();$re = $gen->current();echo 'current return : ' . $re;

    输出:

    current return : 12

    看到 php-yield-test/generatorMothod.php 代码。

    通过第一个代码事例,可得,对一个generator调用current方法,才算真正开始执行。执行到yield为止。如果不能命中yield,则执行到函数结束。

    非generoator会立马执行并得到结果,而非一个生成器对象。

    通过例子2,调用current一次,两次呢,第一次可以看到代码执行日志,第二次,只是把上一次的结果返回给我们而已,并不是让该生成器重新执行。

    通过例子1,调用该函数还会获取到返回值,返回的内容就是 yield 表达式左边的内容。如果表达式无内容,则是NULL.

    Generator::send

    • 向生成器yield点中传入一个值,并返回下一次current值。

    <?phpfunction yield_func(){
    $data = yield 12;
    echo 'get yield data: ' . $data;
    return 'a';}$gen = yield_func();$re = $gen->current();$gen->send(32);

    输出:

    get yield data: 32

    例子3,是一个current,send的常规调用。调用current代码运行yield等到用户send输入参数。接收到输入后,继续运行。current能够接收到yield弹出的值,send返回值为空。

    例子4,直接调用send,相当于调用current,send。不过current的返回值,并不会通过send传给用户。

    例子21中,可以看到直接调用send(1),会运行生成器,并向第一个yield处输入1,继续运行至下一个yield的返回值value。所以,$gen->send(2),和 $gen->current() 结果都是同一个值。

    也就是说:跳过current,直接调用send,会丢失第一次yield的弹出值。

    Generator::next

    • 跳过中断,并让生成器继续执行

    <?phpfunction yield_func(){
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    yield;
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    return $result;}$gen = yield_func();$gen->current();echo 'current called' . PHP_EOL;$gen->next();

    输出:

    run to code line: 4current called
    run to code line: 6

    例子5,这是一个较为常规的调用,调用current代码运行yield等到用户输入,这是调用next跳过,让代码继续运行。

    例子6,直接调用next,相当于调用current,next。而且通过最后打印$result, 我们发现怎么有点像在调用 $gen->send(NULL);。

    Generator::rewind

    • 重置迭代器

    <?phpfunction yield_func(){
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    $result = yield 12;
    echo 'run to code line: ' . __LINE__ . PHP_EOL;}$gen = yield_func();echo 'call yield_func rewind ' . PHP_EOL;$gen->rewind();

    输出:

    call yield_func rewind
    run to code line: 4

    例子7,8 中,发现调用该方法,会导致隐式调用current。

    例子9 中,发现在执行过一个yield代码段后,再次调用该方法,会导致报错(哪怕该 生成器已结束)。

    Generator::throw

    • 向生成器中抛入一个异常

    <?phpfunction yield_func(){
    try {
    $re = yield 'exception';
    } catch (Exception $e) {
    echo 'catched exception msg: ' .$e->getMessage();
    }}$gen = yield_func();$gen->throw(new \Exception('new yield exception'));

    输出:

    catched exception msg: new yield exception

    通过以上简单的例子可得,throw 就是让yield这行代码产生异常,让外面的try catch 捕获我们生成的那个异常。

    例子11中,构造生成器,并调用current方法,运行到yield处,再调用throw,就能捕获到异常。

    例子12中,当调用send方法,跳过函数内yield代码时,再调用throw传入异常,就没法捕获了。

    Generator::valid

    • 检查迭代器是否被关闭

    <?phpfunction yield_func(){
    yield 12;
    return 'a';}$gen = yield_func();$gen->send(1);$check = $gen->valid();echo 'the generator valid ? ' . intval($check);

    输出:

    the generator valid ? 0

    例子12中,发现current被隐式调用。

    例子13中,可得,当生成器运行到yield代码段时,用valid函数检查,都会返回true。

    所以,别问我是否已运行,问就是运行。该方法用来获取是否关闭状态,不是 是否运行状态!运行到底,运行到return就是 关闭状态。

    Generator::key

    • 返回当前产生的键

    <?phpfunction yield_func(){
    yield 1 => 'abc';}$gen = yield_func();echo 'value is :' . $gen->current() . PHP_EOL;echo 'key is: ' . $gen->key() . PHP_EOL;

    输出:

    value is :abc
    key is: 1

    从以上例子中,可得yield可显示设置返回的key.

    例子15 中,发现key的分发规律和PHP数组键值发放策略是差不多的,默认从0开始,未指定则是以上一个数字key+1作为当前的key.

    例子16 中,我们又发现current被隐式调用。

    Generator::__wakeup

    • Generator::__wakeup — 序列化回调

    <?phpfunction yield_func(){
    yield 1 => 'abc';}$gen = yield_func();try {$ser = serialize($gen);} catch (\Exception $e) {
    print_r($e->getMessage());}

    输出:

    Serialization of 'Generator' is not allowed

    这是一个魔术方法,见 PHP 魔术方法,也就是说 生成器 不能被序列化成一个字符串。

    例子17就不用说了,看下例子18,看样子序列化成功了。也就是说一个生成器做为一个方法可以被序列化,当函数变成生成器时,就不能被序列化了。

    Generator::getReturn

    <?phpfunction yield_func(){
    yield 1 => 'abc';
    return 32;}$gen = yield_func();$gen->send(0);echo 'call yield_func return, and get: ' . $gen->getReturn();

    输出:

    call yield_func return, and get: 32

    该函数就是获取生成器最后的返回值。如果没有return语句,或者没有执行到return语句,调用该函数得到的就是NULL。

    例子19 可得,getReturn 能够获取到生成器最后的返回值。

    例子19、20 可得,当生成器没有执行到return语句,或者没有执行到最后时,调用getReturn是会导致报错。

    综上所述

    到这里,我们就发现rewind,next 和 __wakeup 这两个函数感觉没啥叼用呢,为啥还存在呢,因为Generator继承Iterator,自然就有了rewind, next方法,PHP 虽然支持方法覆盖,但子类的访问修饰符 不能缩紧,所以Generator只能重写这两个方法。 __wakeup 继承自 stdClass。

    状态转换

    看图:
    【DTM】PHP协程客户端v0.1 beta版本发布啦!PHP yield 生命周期图

    画了两个状态转换图,上面的要细致,繁复一点。下面的精简版,便于快速理解。

    总结

    以上就是关于 PHP 生成器的基础内容,希望你看了后对它有更进一步认识。下一讲,我们手把手一起来做一个任务调度器,实战一下。

    有问题欢迎提问,谢谢大家!

    【DTM】PHP协程客户端v0.1 beta版本发布啦!

    以上就是PHP yield 协程 生成器用法的了解的详细内容,更多请关注钦钦技术栈其它相关文章!

    转载至:php中文网【www.php.cn】

    版权声明:本文(即:原文链接:https://www.qin1qin.com/catagory/24867/)内容由互联网用户自发投稿贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 630367839@qq.com 举报,一经查实,本站将立刻删除。

    (0)
    上一篇 2022年9月22日 上午11:57
    下一篇 2022年9月22日 下午1:03
    软件定制开发公司

    相关阅读

    发表回复

    登录后才能评论
    通知:禁止投稿所有关于虚拟货币,币圈类相关文章,发现立即永久封锁账户ID!