├── README.md ├── docs ├── activities.md ├── configure-temporal-server.md ├── events.md ├── filter-workflows.md ├── get-started.md ├── go-activities.md ├── go-activity-async-completion.md ├── go-child-workflows.md ├── go-continue-as-new.md ├── go-create-workflows.md ├── go-distributed-cron.md ├── go-error-handling.md ├── go-execute-activity.md ├── go-hello-world.md ├── go-quick-start.md ├── go-retries.md ├── go-run-your-first-app.md ├── go-sdk-video-tutorial.md ├── go-sessions.md ├── go-side-effect.md ├── go-signals.md ├── go-tracing.md ├── go-versioning.md ├── go-workers.md ├── install-temporal-server.md ├── learn-glossary.md ├── namespaces.md ├── overview.md ├── queries.md ├── samples.md ├── system-architecture.md ├── task-queues.md ├── tctl.md └── workflows.md └── img ├── docs ├── apps.png ├── block.png ├── boost.png ├── check.png ├── confetti.png ├── harbor-crane.png ├── hello.png ├── one.png ├── repair-tools.png ├── running.png ├── system-architecture-2.png ├── system-architecture.png ├── temporal-high-level-application-design.png ├── temporal-server-and-sdk-icons.png ├── three.png ├── two.png ├── use-this-template.png ├── warning.png ├── web-ui-activity-error-info.png ├── wisdom.png └── workflow.png ├── favicon.ico ├── favicon.png ├── logo.svg ├── readme ├── forkrepo.png └── netlifypreview.png ├── temporal-logo.svg ├── undraw_docusaurus_mountain.svg ├── undraw_docusaurus_react.svg ├── undraw_docusaurus_tree.svg ├── v1-pipeline.png └── workflow-meme.png /README.md: -------------------------------------------------------------------------------- 1 | # temporal-doc-CN 2 | Chinese version of Temporal Documentation 3 | 4 | 这是中文版本的 [Temproal 文档](https://github.com/temporalio/documentation),仅做了介绍、使用和 Go SDK 相关部分的翻译,方式为机翻+人工校正,其中有一些超链接可能有问题,还望谅解。 5 | 如发现错误欢迎提PR,同时也欢迎参与贡献 6 | 7 | 基于 V1.0.0 版本 8 | 9 | ## 目录 10 | 11 | ### 开始 12 | 13 | - [开始](docs/get-started.md) 14 | 15 | ### 概念 16 | 17 | - [总览](docs/overview.md) 18 | - [工作流](docs/workflows.md) 19 | - [活动](docs/activities.md) 20 | - [事件](docs/events.md) 21 | - [查询](docs/queries.md) 22 | - [任务队列](docs/task-queues.md) 23 | - [命名空间](docs/namespaces.md) 24 | - [系统架构](docs/system-architecture.md) 25 | - [名词表](docs/learn-glossary.md) 26 | 27 | ### 使用 28 | 29 | - [安装 Temporal](docs/install-temporal-server.md) 30 | - [配置 Temporal](docs/configure-temporal-server.md) 31 | - [筛选工作流程](docs/filter-workflows.md) 32 | - [tctl(CLI)](docs/tctl.md) 33 | 34 | ### Go SDK 35 | 36 | - [运行您的第一个应用](docs/go-run-your-first-app.md) 37 | - [构建"Hello World!" 应用程序](docs/go-hello-world.md) 38 | - [SDK视频教程](docs/go-sdk-video-tutorial.md) 39 | - [Worker](docs/go-workers.md) 40 | - [创建工作流](docs/go-create-workflows.md) 41 | - [活动](docs/go-activities.md) 42 | - [执行活动](docs/go-execute-activity.md) 43 | - [子工作流](docs/go-child-workflows.md) 44 | - [活动和工作流重试](docs/go-retries.md) 45 | - [错误处理](docs/go-error-handling.md) 46 | - [信号](docs/go-signals.md) 47 | - [Continue As New](docs/go-continue-as-new.md) 48 | - [Side Effect](docs/go-side-effect.md) 49 | - [异步活动完成](docs/go-activity-async-completion.md) 50 | - [版本](docs/go-versioning.md) 51 | - [会话](docs/go-sessions.md) 52 | - [分布式 CRON](docs/go-distributed-cron.md) 53 | - [跟踪和 context 传输](docs/go-tracing.md) 54 | - [样例描述](docs/samples.md) 55 | 56 | -------------------------------------------------------------------------------- /docs/activities.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: activities 3 | title: Activities 4 | --- 5 | 6 | Temporal 的核心抽象是故障无感知的有状态的工作流。但是由于确定性的执行要求,它们不允许直接调用任何外部 API。取而代之的是他们编排活动的执行。在最简单的形式中,Temporal 活动是一种受支持的语言中的函数或对象方法。如果发生故障Temporal 并不会恢复活动的状态。因此活动可以包含任何代码而不受限制。 7 | 8 | 活动通过任务队列异步调用。任务队列本质上是用于存储活动任务的队列,直到一个活跃的 Worker 将其消费为止。 Worker 通过调用活动实现的函数来处理活动。当函数返回时 Worker 将结果报告回 Temporal 服务,该服务随后将通知工作流此活动的完成。通过从一个不同的进程来完成活动,可以完全异步地实现该活动。 9 | 10 | ## 超时 11 | 12 | Temporal 不对活动持续时间施加任何系统限制,而是由应用程序决定其执行超时时间。这些是可配置的活动超时选项: 13 | 14 | - `ScheduleToStart`是从工作流请求活动执行到 Worker 开始执行活动的最长时间。导致此超时的原因通常是所有 Worker 都在忙碌或无法跟上请求频率。我们建议将此超时设置为在所有可能的 Worker 宕机的情况下工作流愿意等待活动执行的最长时间。 15 | - `StartToClose` 是活动被 Worker 消费后可以执行的最长时间。 16 | - `ScheduleToClose` 是从工作流请求活动执行到完成的最长时间。 17 | - `Heartbeat`是两次心跳请求之间的最长时间。请参阅[长期运行活动](https://docs.temporal.io/docs/activities#long-running-activities)。 18 | 19 | `ScheduleToClose`或者`ScheduleToStart`和`StartToClose`超时设置是必需的。 20 | 21 | ## 重试 22 | 23 | 由于 Temporal 无法恢复活动的状态,并且它们可以与任何外部系统进行通信,失败便是预料之内的事情。因此 Temporal 支持自动活动重试。任何活动在被引用时都可以伴随关联的重试策略。这是重试策略参数: 24 | 25 | - `InitialInterval` 是第一次重试的退避间隔。 26 | - `BackoffCoefficient`重试策略是指数的。该系数指定重试间隔的增长速度。系数1表示重试间隔始终等于`InitialInterval`。 27 | - `MaximumInterval`指定重试之间的最大间隔。对于大于1的系数有用。 28 | - `MaximumAttempts`指定在出现故障时尝试执行活动的次数。如果超出此限制,则错误将返回到调用活动的工作流。 29 | - `NonRetryableErrorReasons`允许您指定不应重试的错误。例如,在某些情况下对无效参数错误进行重试没有任何意义。 30 | 31 | 在某些情况下,应该在失败时重试一个工作流的全部部分而不是一个活动。例如媒体编码工作流将文件下载到主机进行处理,然后将结果上传回存储。在此工作流中如果拥有 Worker 的主机死亡,则应在其他主机上重试所有三个活动。这些重试应该由工作流代码处理,因为它们的用例非常特定化。 32 | 33 | ## 长期活动 34 | 35 | 对于长时间运行的活动,我们建议您指定相对较短的心跳超时并持续心跳。这样即使是很长时间运行的活动, Worker 的故障也可以得到及时处理。指定心跳超时的活动应在其实现中*定期*调用心跳方法。 36 | 37 | 心跳请求可以包含进应用程序的特定部分。这对于保存活动执行进度很有用。如果活动由于缺少心跳而超时,则下一次执行该活动的尝试可以访问该进度并从该断点继续执行。 38 | 39 | 长期活动可以用作领导选举算法的一种特例。Temporal 超时用作第二种解决方案。因此这不是实时应用程序的解决方案。但是如果可以在几秒钟之内对过程的失败做出响应,那么 Temporal 心跳活动就非常合适。 40 | 41 | 此类领导选举的一种常见用例是监控。一个活动执行一个内部循环,该循环定期轮询一些 API 并检查某些条件。每次迭代也都会进行心跳。如果满足条件,那么活动将完成,从而使其工作流可以处理它。如果活动 Worker 死亡,则在超过心跳间隔后活动将超时,并在其他 Worker 上重试。同样该模式也适用于轮询 Amazon S3 存储桶中的新文件或 REST 和其他同步 API 中的响应。 42 | 43 | ## 取消 44 | 45 | 工作流可以请求取消活动。当前一项活动得知其被取消的唯一方法是通过心跳。当心跳请求失败并返回一个特殊错误时,表示活动已取消。然后由活动的实现来执行所有必要的清理并报告已完成清理。由工作流的实现来决定是要等待活动取消确认还是不等待直接继续执行。 46 | 47 | 活动心跳失败的另一种常见情况是,调用它的工作流处于完成状态。在这种情况下活动也将执行清除。 48 | 49 | ## 通过任务队列路由活动任务 50 | 51 | 活动通过任务队列分派给 Worker 。任务队列是 Worker 监听的队列。任务队列是高度动态且轻量级的。他们不需要显式注册。每个 Worker 进程都有一个任务队列也是可以的。通常情况下会有不止一种活动类型通过单个任务队列被调用。在某些情况下(例如主机路由),在多个任务队列上调用相同的活动类型也是正常的。 52 | 53 | 以下是在单个工作流中使用多个活动任务队列的一些用例: 54 | 55 | - *流量控制*。从任务队列消费的 Worker 仅在性能足够时才请求活动任务。因此 Worker 永远不会因请求高峰而超负荷工作。如果请求活动执行的速度快于 Worker 的处理速度,则会将其积压在任务队列中。 56 | - *节流*。每个活动 Worker 可以指定允许其处理任务队列上的活动的最大速率。即使有剩余性能也不会超过此限制。同时还支持全局任务队列速率限制。对于给定的任务队列,此限制适用于所有 Worker 。它通常用于限制活动调用的下游服务的负载。 57 | - *独立部署一系列活动*。设想一种托管活动的服务,该服务可以独立于其他活动和工作流进行部署。要将活动任务发送到此服务,需要一个单独的任务队列。 58 | - *具有不同性能的 Worker*。例如,有 GPU 的机器上的 Worker 与没有 GPU 机器上的 Worker 。在这种情况下,具有两个单独的任务队列使工作流可以选择将执行请求发送到哪个任务队列。 59 | - *将活动路由到特定主机*。例如在媒体编码的情况下,转换和上传活动必须与下载主机在同一主机上运行。 60 | - *将活动路由到特定进程*。例如某些活动加载大型数据集并将其缓存在其进程中。依赖此数据集的活动应路由到同一进程。 61 | - *多重优先权*。每个优先级一个任务队列,每个优先级有一个 Worker 池。 62 | - *版本控制*。活动的新的向后不兼容实现可能使用不同的任务队列。 63 | 64 | ## 异步活动完成 65 | 66 | 默认情况下,活动是取决于客户端库语言的函数或方法。函数返回后,活动即告完成。但是在某些情况下,活动实现是异步的。例如,它通过消息队列转发到外部系统,答复来自另一个队列。 67 | 68 | 为了支持此类用例,Temporal 允许在活动的函数完成时并不完成活动的实现。在这种情况下应使用单独的 API 来完成活动。这个 API 可以从原始活动 Worker 使用的任何进程(甚至使用不同的编程语言)中调用。 69 | 70 | ## 本地活动 71 | 72 | 有一些活动生存周期很短,不需要排队语义、流量控制、速率限制和路由功能。对于这些 Temporal 同样支持,叫做*本地活动*功能。本地活动与调用它们的工作流在同一工作进程中执行。考虑将本地活动用于具有以下特性的功能: 73 | 74 | - 不超过几秒钟 75 | - 不需要全局速率限制 76 | - 不需要路由到特定的 Worker 或 Worker 池 77 | - 可以与调用它们的工作流在同一二进制文件中实现 78 | 79 | 本地活动的主要好处是,与常规活动调用相比它们在利用 Temporal 服务资源方面效率更高,并且延迟开销要低得多。 -------------------------------------------------------------------------------- /docs/configure-temporal-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: configure-temporal-server 3 | title: Configure Temporal 4 | --- 5 | 6 | 在 `development.yaml`可以找到 Temporal Server 的配置,其中包含以下可能的部分: 7 | 8 | - [**global**](#global) 9 | - [**persistence**](#persistence) 10 | - [**log**](#log) 11 | - [**clusterMetadata**](#clustermetadata) 12 | - [**services**](#services) 13 | - [**kafka**](#kafka) 14 | - [**publicClient**](#publicclient) 15 | - archival 16 | - dcRedirectionPolicy 17 | - dynamicConfigClient 18 | - namespaceDefaults 19 | 20 | **注意:**更改`development.yaml`文件中的任何属性都需要重新启动进程才能使更改生效。 21 | 22 | **注意:**如果您想更深入地了解我们如何实际解析此文件,请[在此处](https://github.com/temporalio/temporal/blob/master/common/service/config/config.go)查看我们的源代码。 23 | 24 | ## global 25 | 26 | 本`global`部分包含全局的配置。请参阅下面的最小配置(注释掉了可选参数)。 27 | 28 | ```yaml 29 | global: 30 | membership: 31 | name: temporal 32 | #maxJoinDuration: 30s 33 | #broadcastAddress: "127.0.0.1" 34 | #pprof: 35 | #port: 7936 36 | #tls: 37 | #... 38 | 39 | ``` 40 | 41 | ### membership - *必填* 42 | 43 | `membership`节控制以下成员关系层参数: 44 | 45 | - `name` - *必填* - 用于标识 gossip 环中的其他的集群成。所有成员节点都必须相同。 46 | - `maxJoinDuration` - 服务尝试加入 gossip 环的超时时间 47 | - `broadcastAddress` - 用作与远程节点进行连接的地址. 48 | - 当BindOnIP在多个节点相同(如0.0.0.0)且用于 nat 穿透场景时,通常使用此项。`net.ParseIP`来控制其所支持的语法。注意:仅支持IPV4。 49 | 50 | ### pprof 51 | 52 | - `port` - 如果指定,则将在指定的端口上启动进程时初始化 pprof。 53 | 54 | ### tls 55 | 56 | `tls`部分控制网络通信的 SSL / TLS 设置,包含两个子部分`internode`和`frontend`。`internode`部分管理角色之间的内部服务通信,`frontend`管理 SDK 客户端与 frontend 服务角色之间的通信。 57 | 58 | 这些小节中的每个小节都包含一个`server`小节和一个`client`小节。`server`包含以下参数: 59 | 60 | - `certFile` -包含要使用的证书的PEM编码公钥文件的路径。 61 | - `keyFile` -包含要使用的证书的PEM编码私钥文件的路径。 62 | - `requireClientAuth`-*布尔值*-要求客户端在连接时使用证书进行身份验证,否则默认为双向 TLS。 63 | - `clientCaFiles`-包含您希望信任的用于客户端身份验证的证书颁发机构的PEM编码公共密钥的文件的路径列表。如果`requireClientAuth`未启用,则忽略此值。 64 | 65 | 以下是在 SDK 和 frontend API 之间启用服务器 TLS(https)的示例: 66 | 67 | ```yaml 68 | global: 69 | tls: 70 | frontend: 71 | server: 72 | certFile: /path/to/public/cert 73 | keyFile: /path/to/private/cert 74 | client: 75 | serverName: dnsSanInFrontendCertificate 76 | ``` 77 | 78 | 注意,`client`通常是需要配置的,通过`serverName`字段指定所提供的服务器证书中包含的 DNS SubjectName;这是必需的,因为Temporal 使用 IP 到 IP 的通信。如果服务器证书包含适当的 IP 使用者备用名称,则可以忽略此选项。 79 | 80 | 此外,当客户端的主机不信任服务器使用的根CA时,需要配置`rootCaFiles`字段。下面的示例扩展了上面的示例,手动指定了 frontend 服务使用的根CA: 81 | 82 | ```yaml 83 | global: 84 | tls: 85 | frontend: 86 | server: 87 | certFile: /path/to/public/cert 88 | keyFile: /path/to/private/cert 89 | client: 90 | serverName: dnsSanInFrontendCertificate 91 | rootCaFiles: 92 | - /path/to/frontend/server/ca 93 | ``` 94 | 95 | 下面是一个完全安全的集群的另一个示例,它使用双向 TLS 与手动指定的 Cas 进行前端和节点间通信: 96 | 97 | ```yaml 98 | global: 99 | tls: 100 | internode: 101 | server: 102 | certFile: /path/to/internode/publicCert 103 | keyFile: /path/to/internode/privCert 104 | requireClientAuth: true 105 | clientCaFiles: 106 | - /path/to/internode/serverCa 107 | client: 108 | serverName: dnsSanInInternodeCertificate 109 | rootCaFiles: 110 | - /path/to/internode/serverCa 111 | frontend: 112 | server: 113 | certFile: /path/to/frontend/publicCert 114 | keyFile: /path/to/frontend/privCert 115 | requireClientAuth: true 116 | clientCaFiles: 117 | - /path/to/internode/serverCa 118 | - /path/to/sdkClientPool1/ca 119 | - /path/to/sdkClientPool2/ca 120 | client: 121 | serverName: dnsSanInFrontendCertificate 122 | rootCaFiles: 123 | - /path/to/frontend/serverCa 124 | 125 | ``` 126 | **注意:**如果启用了客户端身份验证,则`internode.server`证书将用作服务中的客户端证书。这增加了以下要求: 127 | 128 | - 该`internode.server`证书必须在所有角色中指定,即使是只有 frontend 配置。 129 | - 内部节点间服务器证书必须用 **no** Extended Key Usages 或 ServerAuth and ClientAuth EKUs 生成。 130 | - 如果您的证书颁发机构不受信任,例如上一个示例,则需要在以下位置指定节点间服务器 Ca: 131 | - `internode.server.clientCaFiles` 132 | - `internode.client.rootCaFiles` 133 | - `frontend.server.clientCaFiles` 134 | 135 | ## persistence 136 | `persistence`节包含数据存储/持久层的配置。以下是受密码保护的 Cassandra 群集的最小配置示例。 137 | 138 | ```yaml 139 | persistence: 140 | defaultStore: default 141 | visibilityStore: visibility 142 | numHistoryShards: 512 143 | datastores: 144 | default: 145 | cassandra: 146 | hosts: "127.0.0.1" 147 | keyspace: "temporal" 148 | user: "username" 149 | password: "password" 150 | visibility: 151 | cassandra: 152 | hosts: "127.0.0.1" 153 | keyspace: "temporal_visibility" 154 | ``` 155 | 156 | 以下顶级配置项是必需的: 157 | 158 | - `numHistoryShards` - *必需* - 初始化集群时要创建的历史分片数。 159 | - **警告**:此值是不可变的,在第一次运行后将被忽略。请确保将该值设置得足够高,以适应该群集的最坏情况峰值负载。 160 | - `defaultStore` - *必需* - Temporal 服务使用的数据存储定义的名称。 161 | - `visibilityStore` - *必需* - Temporal 可视化服务使用的数据存储定义的名称。 162 | - `datastores` - *必需* - 包含要引用的命名数据存储配置。 163 | - 每个定义都用一个声明名称的标题定义(即上述的:`default:`及`visibility:`),其中包含一个数据存储定义。 164 | - 数据存储区定义必须为`cassandra`或`sql`。 165 | 166 | `cassandra`数据存储定义可以包含下列值: 167 | 168 | - `hosts` - *必需* - Cassandra endpoints. 169 | - `port` - 默认值: 9042 - 用于`gocql`客户端连接的 Cassandra 端口。 170 | - `user` - 用于`gocql`客户端身份验证的 Cassandra 用户。 171 | - `password` - 用于`gocql`客户端身份验证的 Cassandra 密码。 172 | - `keyspace` - *必需* - Cassandra 键空间. 173 | - `datacenter` - Cassandra 的数据中心过滤器参数. 174 | - `maxConns` - 单个TLS配置允许的到此数据存储的最大连接数。 175 | - `tls` - 请参阅下面的TLS。 176 | 177 | `sql`数据存储定义可以包含下列值: 178 | 179 | - `user` - 用于身份验证的用户。 180 | - `password` - 用于身份验证的密码。 181 | - `pluginName` - *必需* - SQL数据库类型。 182 | - *有效值*: `mysql` 或 `postgres`. 183 | - `databaseName` - *必需* - 要连接的SQL数据库的名称。 184 | - `connectAddr` - *必需* - 数据库的远程地址。 185 | - `connectProtocol` - *必需* - 连接远程数据库的协议 186 | - *有效值*: `tcp` 或 `unix` 187 | - `connectAttributes` - 一个 K/V 映射的 Map,将作为连接`data_source_name`url 的一部分发送。 188 | - `maxConns` - 与此数据存储区的最大连接数。 189 | - `maxIdleConns` - 与此数据存储的最大空闲连接数。 190 | - `maxConnLifetime` - 是连接可以存活的最长时间。 191 | - `numShards` - 用于分片的sql数据库中的表的存储分片的数量(*默认值:* 1)。 192 | - `tls` - 见下文。 193 | 194 | `tls` 部分可能包含: 195 | 196 | - `enabled` - *boolean*. 197 | - `serverName` - 托管数据存储的服务器的名称。 198 | - `certFile` - 证书文件的路径。 199 | - `keyFile` - 密钥文件的路径。 200 | - `caFile` - ca文件的路径。 201 | - `enableHostVerification` - *boolean* -`true`验证主机名和服务器证书(就像 Cassandra 集群的通配符)。此选项基本上与`InSecureSkipVerify`相反。 见 `InSecureSkipVerify` 在 http://golang.org/pkg/crypto/tls/ 获取更多信息。 202 | 203 | 注意:`certFile`和`keyFile`是可选的,具体取决于服务器配置,但是必须同时省略两个字段,以避免使用客户端证书。 204 | 205 | ## log 206 | `log`部分是可选的,包含以下可能的值: 207 | 208 | - `stdout` - *boolean* - `true` 输出到标准输出 209 | - `level` - 设置日志记录级别。 210 | - *有效值* - debug, info, warn, error 或 fatal. 211 | - `outputFile` - 输出日志文件的路径。 212 | 213 | ## clusterMetadata 214 | 215 | `clusterMetadata` 包含所有群集定义,包括那些参与跨 DC 的定义。 216 | 217 | 一个 `clusterMetadata` 小节的示例: 218 | ```yaml 219 | clusterMetadata: 220 | enableGlobalNamespace: false 221 | failoverVersionIncrement: 10 222 | masterClusterName: "active" 223 | currentClusterName: "active" 224 | clusterInformation: 225 | active: 226 | enabled: true 227 | initialFailoverVersion: 0 228 | rpcAddress: "127.0.0.1:7233" 229 | #replicationConsumer: 230 | #type: kafka 231 | ``` 232 | - `currentClusterName` - *必需* - 当前群集的名称。**警告**:此值是不可变的,在第一次运行后将被忽略。 233 | - `enableGlobalNamespace` - *默认值:* `false`. 234 | - `replicationConsumerConfig` - 确定使用哪种方法来使用复制任务。类型可以是`kafka`或`rpc`。 235 | - `failoverVersionIncrement` - 发生故障转移时每个集群版本的增量。 236 | - `masterClusterName` - 主群集名称,只有主群集可以注册/更新命名空间。所有群集都可以进行命名空间故障转移。 237 | - `clusterInformation` - 包含集群名称到`ClusterInformation`定义的映射。`ClusterInformation`部分包括: 238 | - `enabled` - *boolean* 239 | - `initialFailoverVersion` - 初始的故障转移版本 240 | - `rpcAddress` - 指明远程服务地址(主机:端口)。主机可以是DNS名称。使用`dns:///`前缀启用 DNS 的 IP 地址之间的轮询。 241 | 242 | ## services 243 | `services` 节包含按服务角色类型分配的配置。当前支持四种服务角色: 244 | 245 | - `frontend` 246 | - `matching` 247 | - `worker` 248 | - `history` 249 | 250 | 以下是`services`下 `frontend` 服务定义的最小示例 : 251 | ```yaml 252 | services: 253 | frontend: 254 | rpc: 255 | grpcPort: 8233 256 | membershipPort: 8933 257 | bindOnLocalHost: true 258 | metrics: 259 | statsd: 260 | hostPort: "127.0.0.1:8125" 261 | prefix: "temporal_standby" 262 | 263 | ``` 264 | 265 | 每个服务标题下定义了两个部分: 266 | 267 | ### rpc - *必需* 268 | `rpc` 包含服务与其他服务交互方式有关的设置。支持以下值: 269 | 270 | - `grpcPort` 是gRPC侦听的端口。 271 | - `membershipPort` - 由成员关系侦听器使用。 272 | - `bindOnLocalHost` - 使用 `localhost` 作为侦听地址。 273 | - `bindOnIP` - 用于在特定IP上绑定服务(例如`0.0.0.0`)- 参考`net.ParseIP`获取支持的语法,仅支持IPv4,与`BindOnLocalHost`选项互斥。 274 | - `disableLogging` - 禁用 rpc 的所有日志记录。 275 | - `logLevel` - 所需的日志级别。 276 | 277 | **注意**:当前所有主机的角色类型之间的端口值均应保持一致。 278 | 279 | ### metrics 280 | `metrics` 包含按键值进行指标监控子系统的配置。支持三种程序: 281 | 282 | - `statsd` 283 | - `prometheus` 284 | - `m3` 285 | 286 | `statsd` 部分支持以下设置: 287 | 288 | - `hostPort` - statsd 服务的 host:port. 289 | - `prefix` - 向`statsd上报的特定前缀 . 290 | - `flushInterval` - 发送数据包的最大间隔。(*默认为*1秒)。 291 | - `flushBytes` - 指定您希望发送的最大UDP数据包大小。(*默认为*1432个字节)。 292 | 293 | 此外,指标支持以下非提供的特定设置: 294 | 295 | - `tags` - 要上报的 KV 键值对集,对每一个指标都生效。 296 | - `prefix` - 对每一个上报的指标设置前缀 297 | 298 | ## kafka 299 | `kafka` 节描述了所有连接到 Kafka 集群所需的配置,并支持以下值: 300 | 301 | - `tls` - 使用 TLS , 同 `persistence` 章节. 302 | - `clusters` - 命名的 `ClusterConfig` 定义的映射,描述每个 Kafka 集群的配置。一个`ClusterConfig`定义包含使用`brokers`配置值的brokers 列表。 303 | - `topics` - 卡夫卡集群主题 map。 304 | - `temporal-cluster-topics`- 命名的 `TopicList` 定义 map. 305 | - `applications` - 命名的 `TopicList` 定义 map. 306 | 307 | `TopicList` 定义描述每个集群的主题名称,并包含以下必需值: 308 | - `topic` 309 | - `retryTopic` 310 | - `dlqTopic` 311 | 312 | 以下是 `kafka` 部分示例: 313 | 314 | ```yaml 315 | kafka: 316 | tls: 317 | enabled: false 318 | certFile: "" 319 | keyFile: "" 320 | caFile: "" 321 | clusters: 322 | test: 323 | brokers: 324 | - 127.0.0.1:9092 325 | topics: 326 | active: 327 | cluster: test 328 | active-dlq: 329 | cluster: test 330 | standby: 331 | cluster: test 332 | standby-dlq: 333 | cluster: test 334 | other: 335 | cluster: test 336 | other-dlq: 337 | cluster: test 338 | temporal-cluster-topics: 339 | active: 340 | topic: active 341 | dlq-topic: active-dlq 342 | standby: 343 | topic: standby 344 | dlq-topic: standby-dlq 345 | other: 346 | topic: other 347 | dlq-topic: other-dlq 348 | ``` 349 | 350 | ## publicClient 351 | `publicClient`是必填部分,其中包含一个值`hostPort`,该值用于指定访问 frontend 端的 IPv4 地址或 DNS 名称和端口。 352 | 353 | 例: 354 | ```yaml 355 | publicClient: 356 | hostPort: "localhost:8933" 357 | ``` 358 | 359 | 使用 `dns:///` 前缀启用 DNS 的 IP 地址之间的轮询。 360 | -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: events 3 | title: Events 4 | --- 5 | 6 | ## 事件处理 7 | 8 | 故障无感知的有状态的工作流可以被接收外部事件的信号。信号总是点对点地指向特定工作流实例。信号始终按照接收顺序进行处理。 9 | 10 | 信号可以用于很多种场景。 11 | 12 | ## 事件汇总和关联 13 | 14 | Temporal 不能替代 Apache Flink 或 Apache Spark 等通用流处理引擎。但是在某些情况下它更合适,例如当所有应该聚合和关联的事件始终应用于具有清晰 ID 的某个业务实体时。此时只要特定条件满足时操作就会被执行。 15 | 16 | 一个主要限制是单个 Temporal 工作流的吞吐量非常有限,而工作流的数量实际上是无限的。因此如果您需要对每个客户汇总事件,并且您的应用程序拥有1亿客户,每个客户每秒生成的事件不超过20个,那么 Temporal 可以正常工作。但是如果您想汇总美国客户的所有事件,那么这些事件的产生频率将超出单个工作流的能力。 17 | 18 | 例如,物联网设备生成事件并且用有一定顺序的事件来表明应重新配置该设备。每个设备都将创建工作流实例,并且每个实例将管理设备的状态机并在必要时执行重新配置活动。 19 | 20 | 另一个用例是客户忠诚度计划。每次客户购买商品时都会生成一个事件进入 Apache Kafka 供下游系统处理。忠诚度服务的 Kafka 消费者接收该事件,并使用 Temporal `signalWorkflowExecution`API 向客户工作流发出有关购买的信号。工作流会累计购买次数。如果达到了指定的阈值,则工作流将执行一项活动,以通知某些外部服务客户已达到下一级别的忠诚度计划。工作流还执行活动以定期向客户发送有关其当前状态的消息。 21 | 22 | ## 人工任务 23 | 24 | 许多业务流程都涉及人工参与者。Temporal 用于实现外部交互的标准模式是执行在外部系统中创建人工任务的活动。它可以是带有表单的电子邮件,也可以是某些外部数据库中的记录,也可以是移动应用通知。当用户更改任务状态时,信号将发送到相应的工作流。比如表单提交或移动应用通知被确认。一些任务具有多种可能的动作,例如索赔,退货,完成,拒绝。这中情况下就可以发送多个信号。 25 | 26 | ## 流程执行变更 27 | 28 | 某些业务流程在发生了某些外部事件时应更改其行为。例如在执行订单运送工作流时,任何物品数量的变化都可以以信号的形式传递。 29 | 30 | 另一个示例是服务部署工作流。在向 Kubernetes 集群推出新软件版本时发现了一些问题。在调查问题时,可以使用信号来要求工作流暂停。然后可以使用继续信号或回滚信号来执行适当的操作。 31 | 32 | ## 同步 33 | 34 | Temporal 工作流是高度一致的,因此它们可以用作执行中活动的同步点。例如要求顺序处理单个用户的所有消息,但是底层消息传递基础结构可以并行传递它们。Temporal 解决方案将是为每个用户提供一个工作流,并在收到事件时向其发出信号。然后,工作流将所有信号缓存在内部数据结构中,然后为收到的每个信号调用一个活动。有关示例,请参见下面的[Stack Overflow答案](https://stackoverflow.com/a/56615120/1664318)。 35 | 36 | -------------------------------------------------------------------------------- /docs/filter-workflows.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: filter-workflows 3 | title: Filter Workflows 4 | --- 5 | 6 | Temporal支持使用自定义键值对创建工作流,更新工作流代码中的信息,然后使用类似SQL的查询列出/搜索工作流。例如,您可以使用键`city`和`age`创建工作流,然后使用`city = seattle and age > 22`搜索所有工作流。 7 | 8 | 另外常规的工作流属性,例如开始时间和工作流类型也可以查询。 例如,[从 CLI 列出工作流](https://docs.temporal.io/docs/tctl/#list-closed-or-open-workflow-executions)或使用 List API([Go](https://pkg.go.dev/go.temporal.io/sdk/client#Client))时,可以指定以下查询 9 | 10 | ```sql 11 | WorkflowType = "main.Workflow" and Status != 0 and (StartTime > "2019-06-07T16:46:34-08:00" or CloseTime > "2019-06-07T16:46:34-08:00" order by StartTime desc) 12 | ``` 13 | 14 | ## 备注vs搜索属性 15 | 16 | Temporal 提供了两种使用键值对创建工作流的方法:备注和搜索属性。备注只能在工作流开始时提供。另外,备注数据没有索引,因此不可搜索。使用 List API 列出工作流时,备注数据也是能看到的。搜索属性数据会建立索引,因此您可以通过查询这些属性来搜索工作流。但是,搜索属性需要使用Elasticsearch。 17 | 18 | 备注和搜索属性在 Go 客户端的 [StartWorkflowOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#StartWorkflowOptions) 中可用。 19 | 20 | ```go 21 | type StartWorkflowOptions struct { 22 | // ... 23 | 24 | // Memo - Optional non-indexed info that will be shown in list workflow. 25 | Memo map[string]interface{} 26 | 27 | // SearchAttributes - Optional indexed info that can be used in query of List/Scan/Count workflow APIs (only 28 | // supported when Temporal server is using Elasticsearch). The key and value type must be registered on Temporal server side. 29 | // Use GetSearchAttributes API to get valid key and corresponding value type. 30 | SearchAttributes map[string]interface{} 31 | } 32 | ``` 33 | 34 | 在Java客户端中,*WorkflowOptions.Builder* 具有用于[备忘](https://javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/client/WorkflowOptions.Builder.html#setMemo-java.util.Map-)和[搜索属性](https://www.javadoc.io/doc/com.uber.cadence/cadence-client/2.6.0/com/uber/cadence/client/WorkflowOptions.Builder.html#setSearchAttributes-java.util.Map-)的类似方法。 35 | 36 | 备注和搜索属性之间的一些重要区别: 37 | 38 | - 备注可以支持所有数据类型,因为它没有索引。搜索属性仅支持基本数据类型(包括String,Int,Float,Bool,Datetime),因为它由 Elasticsearch 索引。 39 | - 备注不限制键名。搜索属性要求在使用键之前将其列出来,因为 Elasticsearch 对索引键有限制。 40 | - 备注不需要 Temporal 集群依赖 Elasticsearch,而搜索属性仅适用于 Elasticsearch。 41 | 42 | ## 搜索属性 (Go 客户端用例) 43 | 44 | 使用 Temporal Go 客户端时,请在 [StartWorkflowOptions中 ](https://pkg.go.dev/go.temporal.io/sdk/internal#StartWorkflowOptions)提供键值对作为 SearchAttributes 。 45 | 46 | SearchAttributes是`map[string]interface{}`,需要在其中列出键,以便 Temporal 知道属性键的名称和值的类型。映射中提供的值必须与注册的类型相同。 47 | 48 | ### 列出搜索属性 49 | 50 | 首先使用 CLI 查询搜索属性列表: 51 | 52 | ```bash 53 | $ tctl --namespace samples-namespace cl get-search-attr 54 | +---------------------+------------+ 55 | | KEY | VALUE TYPE | 56 | +---------------------+------------+ 57 | | Status | INT | 58 | | CloseTime | INT | 59 | | CustomBoolField | DOUBLE | 60 | | CustomDatetimeField | DATETIME | 61 | | CustomNamespace | KEYWORD | 62 | | CustomDoubleField | BOOL | 63 | | CustomIntField | INT | 64 | | CustomKeywordField | KEYWORD | 65 | | CustomStringField | STRING | 66 | | NamespaceId | KEYWORD | 67 | | ExecutionTime | INT | 68 | | HistoryLength | INT | 69 | | RunId | KEYWORD | 70 | | StartTime | INT | 71 | | WorkflowId | KEYWORD | 72 | | WorkflowType | KEYWORD | 73 | +---------------------+------------+ 74 | ``` 75 | 76 | 使用 admin CLI 添加新的搜索属性: 77 | 78 | ```bash 79 | tctl --namespace samples-namespace adm cl asa --search_attr_key NewKey --search_attr_type string 80 | ``` 81 | 82 | **_search_attr_type_** 的值可为: 83 | 84 | - string 85 | - keyword 86 | - int 87 | - double 88 | - bool 89 | - datetime 90 | 91 | #### 关键字与字符串 92 | 93 | 请注意,**关键字**和**字符串**是从 Elasticsearch 获取的概念。**字符串**中的每个单词都被视为可搜索的关键字。对于 UUID,这可能会出现问题,因为 Elasticsearch 会分别索引 UUID 的每个部分。要将整个字符串视为可搜索关键字,请使用**关键字**类型。 94 | 95 | 例如,键 RunId 的值为“ 2dd29ab7-2dd8-4668-83e0-89cae261cfb1” 96 | 97 | - 作为**关键字,**只能与RunId =“ 2dd29ab7-2dd8-4668-83e0-89cae261cfb1”匹配(或在将来使用[正则表达式](https://github.com/uber/cadence/issues/1137)) 98 | - 因为**字符串**将由RunId =“ 2dd8”进行匹配,这可能会导致不必要的匹配 99 | 100 | **注意:**字符串类型不能在 Order By 查询中使用。 101 | 102 | 有一些预先设定的搜索属性可以方便地进行测试: 103 | 104 | - CustomKeywordField 105 | - CustomIntField 106 | - CustomDoubleField 107 | - CustomBoolField 108 | - CustomDatetimeField 109 | - CustomStringField 110 | 111 | 其类型在其名称中已指明。 112 | 113 | ### 值类型 114 | 115 | 以下是搜索属性值类型及其对应的 Golang 类型: 116 | 117 | - Keyword = string 118 | - Int = int64 119 | - Double = float64 120 | - Bool = bool 121 | - Datetime = time.Time 122 | - String = string 123 | 124 | ### 限制 125 | 126 | 我们建议通过对以下各项实施限制来限制 Elasticsearch 索引的数量: 127 | 128 | - 密钥数:每个工作流100 129 | - 值大小:每个值 2kb 130 | - 键和值的总大小:每个工作流 40kb 131 | 132 | Temporal 保留键,例如 NamespaceId,WorkflowId 和 RunId。这些只能在 List 查询中使用。该值不可更新。 133 | 134 | ### Upsert 工作流搜索属性 135 | 136 | [UpsertSearchAttributes](https://pkg.go.dev/go.temporal.io/sdk/workflow#UpsertSearchAttributes) 用于在工作流代码中添加或更新搜索属性。 137 | 138 | 可以在[github.com/temporalio/temporal-go-samples中](https://github.com/temporalio/temporal-go-samples/tree/master/searchattributes)找到用于搜索属性的Go样例。 139 | 140 | UpsertSearchAttributes 会将属性合并到工作流中的现有 map。例如以下示例工作流代码: 141 | 142 | ```go 143 | func MyWorkflow(ctx workflow.Context, input string) error { 144 | 145 | attr1 := map[string]interface{}{ 146 | "CustomIntField": 1, 147 | "CustomBoolField": true, 148 | } 149 | workflow.UpsertSearchAttributes(ctx, attr1) 150 | 151 | attr2 := map[string]interface{}{ 152 | "CustomIntField": 2, 153 | "CustomKeywordField": "seattle", 154 | } 155 | workflow.UpsertSearchAttributes(ctx, attr2) 156 | } 157 | ``` 158 | 159 | 在第二次调用UpsertSearchAttributes之后,map 将包含: 160 | 161 | ```go 162 | map[string]interface{}{ 163 | "CustomIntField": 2, 164 | "CustomBoolField": true, 165 | "CustomKeywordField": "seattle", 166 | } 167 | ``` 168 | 169 | 当前不支持删除字段。为了获得类似的效果,请将字段设置为前哨值。例如,要删除“ CustomKeywordField”,请将其更新为“ impossibleVal”。然后搜索`CustomKeywordField != ‘impossibleVal’`将匹配CustomKeywordField不等于“ impossibleVal”的工作流,其中**包括**未设置 CustomKeywordField 的工作流。 170 | 171 | 使用`workflow.GetInfo`来获得当前的搜索属性。 172 | 173 | ### ContinueAsNew 和 Cron 174 | 175 | 当执行 [ContinueAsNew](https://docs.temporal.io/docs/go-continue-as-new/) 或使用 [Cron](https://docs.temporal.io/docs/go-distributed-cron/) 时,默认情况下,搜索属性(和备注)将被带到新的运行中。 176 | 177 | ## 查询功能 178 | 179 | 通过[CLI列出工作流](https://docs.temporal.io/docs/tctl/#list-closed-or-open-workflow-executions)时,可以使用类似 SQL 的 where 子句查询工作流,也可以使用列表API([Go](https://pkg.go.dev/go.temporal.io/sdk/client#Client),[Java](https://static.javadoc.io/com.uber.cadence/cadence-client/2.6.0/com/uber/cadence/WorkflowService.Iface.html#ListWorkflowExecutions-com.uber.cadence.ListWorkflowExecutionsRequest-))来查询工作流。 180 | 181 | 请注意,查询时您只会看到来自一个命名空间的工作流。 182 | 183 | ### 支持的运算符 184 | 185 | - AND, OR, () 186 | - =, !=, >, >=, <, <= 187 | - IN 188 | - BETWEEN ... AND 189 | - ORDER BY 190 | 191 | ### 默认属性 192 | 193 | 这些可以通过使用 CLI get-search-attr 命令或 GetSearchAttributes API 来找到。名称和类型如下: 194 | 195 | | KEY | VALUE TYPE | 196 | | ------------------- | ---------- | 197 | | Status | INT | 198 | | CloseTime | INT | 199 | | CustomBoolField | DOUBLE | 200 | | CustomDatetimeField | DATETIME | 201 | | CustomNamespace | KEYWORD | 202 | | CustomDoubleField | BOOL | 203 | | CustomIntField | INT | 204 | | CustomKeywordField | KEYWORD | 205 | | CustomStringField | STRING | 206 | | NamespaceId | KEYWORD | 207 | | ExecutionTime | INT | 208 | | HistoryLength | INT | 209 | | RunId | KEYWORD | 210 | | StartTime | INT | 211 | | WorkflowId | KEYWORD | 212 | | WorkflowType | KEYWORD | 213 | 214 | 这些属性有一些特殊的注意事项: 215 | 216 | - Status, CloseTime, NamespaceId, ExecutionTime, HistoryLength, RunId, StartTime, WorkflowId, WorkflowType 是由 Temporal 保留的,并且是只读的 217 | - Status 是 int 到状态的映射: 218 | - 0 = unknown 219 | - 1 = running 220 | - 2 = completed 221 | - 3 = failed 222 | - 4 = canceled 223 | - 5 = terminated 224 | - 6 = continuedasnew 225 | - 7 = timedout 226 | - StartTime, CloseTime 和 ExecutionTime 存储为INT, 但支持使用EpochTime(以纳秒为单位)和RFC3339格式的字符串的查询(例如“ 2006-01-02T15:04:05 + 07:00”) 227 | - CloseTime, HistoryLength 仅存在于关闭的工作流中 228 | - ExecutionTime 供 Retry / Cron 用户查询将来将要运行的工作流 229 | 230 | 要仅列出打开的工作流,请添加`CloseTime = missing`到查询末尾。 231 | 232 | 如果使用重试或 cron 功能查询将在特定时间范围内开始执行的工作流,则可以在 ExecutionTime 上添加限制。例如:`ExecutionTime > 2019-01-01T10:00:00-07:00`。请注意,如果包含有 ExecutionTime 的限制,则仅返回 cron 或需要重试的工作流。 233 | 234 | If you use retry or the cron feature to query workflows that will start execution in a certain time range, you can add predicates on ExecutionTime. For example: `ExecutionTime > 2019-01-01T10:00:00-07:00`. Note that if predicates on ExecutionTime are included, only cron or a workflow that needs to retry will be returned. 235 | 236 | ### 查询的注意事项 237 | 238 | - Pagesize 的默认值为 1000,并且不能大于 10k 239 | - Temporal 时间戳的范围查询(StartTime,CloseTime,ExecutionTime)不能大于9223372036854754775807(maxInt64-1001) 240 | - 按时间范围查询将具有 1ms 的分辨率 241 | - 查询列名称区分大小写 242 | - 检索大量工作流(10M +)时,ListWorkflow 可能需要更长的时间 243 | - 要检索大量工作流而不关心顺序,请使用 ScanWorkflow API 244 | - 为了有效地计算工作流的数量,请使用 CountWorkflow API 245 | 246 | ## 工具支持 247 | 248 | ### CLI 249 | 250 | 从 Temporal 服务的 0.6.0 版本开始支持搜索属性。您还可以从最新的 [CLI Docker 映像](https://hub.docker.com/r/temporalio/tctl)(在 0.6.4 或更高版本上受支持)使用CLI 。 251 | 252 | #### 使用搜索属性启动工作流 253 | 254 | ```bash 255 | tctl --ns samples-namespace workflow start --tq helloWorldGroup --wt main.Workflow --et 60 --dt 10 -i '"vancexu"' -search_attr_key 'CustomIntField | CustomKeywordField | CustomStringField | CustomBoolField | CustomDatetimeField' -search_attr_value '5 | keyword1 | vancexu test | true | 2019-06-07T16:16:36-08:00' 256 | ``` 257 | 258 | #### 使用 List API 搜索工作流 259 | 260 | ```bash 261 | tctl --ns samples-namespace wf list -q '(CustomKeywordField = "keyword1" and CustomIntField >= 5) or CustomKeywordField = "keyword2"' -psa 262 | ``` 263 | 264 | ```bash 265 | tctl --ns samples-namespace wf list -q 'CustomKeywordField in ("keyword2", "keyword1") and CustomIntField >= 5 and CloseTime between "2018-06-07T16:16:36-08:00" and "2019-06-07T16:46:34-08:00" order by CustomDatetimeField desc' -psa 266 | ``` 267 | 268 | 要仅列出打开的工作流,请添加`CloseTime = missing`到查询末尾。 269 | 270 | 请注意,查询可以支持多种类型的过滤器: 271 | 272 | ```bash 273 | tctl --ns samples-namespace wf list -q 'WorkflowType = "main.Workflow" and (WorkflowId = "1645a588-4772-4dab-b276-5f9db108b3a8" or RunId = "be66519b-5f09-40cd-b2e8-20e4106244dc")' 274 | ``` 275 | 276 | ```bash 277 | tctl --ns samples-namespace wf list -q 'WorkflowType = "main.Workflow" StartTime > "2019-06-07T16:46:34-08:00" and CloseTime = missing' 278 | ``` 279 | 280 | ### Web UI 支持 281 | 282 | 从0.20.0版本开始,[Temporal Web](https://github.com/temporalio/temporal-web) 中支持查询。使用“基本/高级”按钮切换到“高级”模式,然后在搜索框中键入查询。 283 | 284 | ## 本地测试 285 | 286 | 1. 将 Docker 内存增加到 6GB 以上。进入到Docker-> Preferences-> Advanced-> Memory 287 | 2. 获取临时 Docker 撰写文件。运行`curl -L https://github.com/temporalio/temporal/releases/latest/download/docker.tar.gz | tar -xz --strip-components 1 docker/docker-compose-es.yml` 288 | 3. 使用以下命令启动 Temporal Docker(其中包含 Apache Kafka,Apache Zookeeper 和 Elasticsearch) `docker-compose -f docker-compose-es.yml up` 289 | 4. 从 Docker 输出日志中,确保 Elasticsearch 和 Temporal 正确启动。如果遇到磁盘空间不足错误,请尝试`docker system prune -a --volumes` 290 | 5. 注册一个本地命名空间并开始使用它。 `tctl --ns samples-namespace n re` 291 | 292 | 注意:启动具有搜索属性但没有 Elasticsearch 的工作流将照常成功,但是将不可搜索,也不会显示在列表结果中。 293 | 294 | -------------------------------------------------------------------------------- /docs/get-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: get-started 3 | title: Temporal documentation 4 | sidebar_label: Get started 5 | --- 6 | 7 | Temporal 重新构想了具有状态依赖和服务编排等特性的应用程序开发。 8 | 9 | ## Get started 10 | 11 | 准备好构建了吗? [安装 Temporal 服务](https://docs.temporal.io/docs/install-temporal-server/) 并选择你的 SDK. 12 | 13 | - [Go SDK](https://docs.temporal.io/docs/go-quick-start/) 14 | - [Java SDK](https://docs.temporal.io/docs/java-quick-start/) 15 | 16 | ## 了解 Temporal 17 | 18 | Temporal 使开发人员可以编写高度可靠的应用程序而不必担心任何极端情况,但是对于部分人来说,开始使用 Temporal 可能会是一种在认知模式上的转变。如果您不熟悉 Temporal 或常用的工作流应用,可以从其[核心概念](https://docs.temporal.io/docs/overview/)进行了解。 19 | 20 | 想知道 Temporal 是否适合您的用例?查看我们的[用例章节](https://docs.temporal.io/docs/use-cases-orchestration/),或访问[社区论坛](https://community.temporal.io/tag/use-case-validation)。 21 | 22 | 想直接开始吗?尝试一些我们的示例吧。 23 | 24 | - [Go SDK samples](https://github.com/temporalio/go-samples) 25 | - [Java SDK samples](https://github.com/temporalio/java-samples) 26 | 27 | ## 最佳实践 28 | 29 | 想掌握 Temporal 工作流吗?我们的指南将带您快速了解 Temporal 开发的最佳实践。 30 | 31 | - [如何安装 Temporal](https://docs.temporal.io/docs/install-temporal-server/) 32 | - [如何配置 Temporal](https://docs.temporal.io/docs/configure-temporal-server/) 33 | - [如何筛选工作流](https://docs.temporal.io/docs/filter-workflows/) 34 | - [如何使用CLI(tctl)](https://docs.temporal.io/docs/tctl/) 35 | 36 | ## 贡献 37 | 38 | 想要贡献?请遵循以下仓库中提供的贡献说明。 39 | 40 | - [Temporal server](https://github.com/temporalio/temporal/blob/master/CONTRIBUTING.md) 41 | - [Documentation](https://github.com/temporalio/documentation-legacy/blob/master/README.md). 42 | - [Go SDK](https://github.com/temporalio/go-sdk/blob/master/CONTRIBUTING.md) 43 | - [Java SDK](https://github.com/temporalio/java-sdk/blob/master/CONTRIBUTING.md) 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/go-activities.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-activities 3 | title: Activities 4 | --- 5 | 6 | 活动是业务逻辑中特定任务的实现。 7 | 8 | 活动是通过函数实现的。数据可以通过函数参数直接传递给活动。参数可以是基本类型或结构体,唯一的要求是参数必须可序列化。尽管不是必需的,但我们建议活动函数的第一个参数的类型为`context.Context`,以便允许活动与其他框架方法进行交互。该函数必须返回一个`error`值,并且可以选择返回一个结果值。结果值一样可以是基本类型或结构体,唯一的要求是可序列化。 9 | 10 | 通过调用参数传递到活动或通过结果值返回的值记录在执行历史记录中。整个执行历史记录从 Temporal 服务转移到工作流 Worker ,其中包含工作流逻辑需要处理的每个事件。因此,大量的执行历史记录可能会对工作流的性能产生不利影响。因此,请注意通过活动调用参数或返回值传输的数据量。除此之外,活动实现不存在其他限制。 11 | 12 | ## 概述 13 | 14 | 下面的示例演示一个简单的活动,该活动接受一个字符串参数,向其附加一个单词,然后返回结果。 15 | 16 | ```go 17 | package sample 18 | 19 | import ( 20 | "context" 21 | 22 | "go.uber.org/zap" 23 | 24 | "go.temporal.io/sdk/activity" 25 | ) 26 | 27 | // SimpleActivity is a sample Temporal activity function that takes one parameter and 28 | // returns a string containing the parameter value. 29 | func SimpleActivity(ctx context.Context, value string) (string, error) { 30 | activity.GetLogger(ctx).Info("SimpleActivity called.", zap.String("Value", value)) 31 | return "Processed: " + value, nil 32 | } 33 | ``` 34 | 让我们看一下该活动的每个组成部分。 35 | 36 | ### 声明 37 | 38 | 在 Temporal 编程模型中,活动是通过函数实现的。函数声明指定活动接受的参数以及活动可能返回的任何值。活动函数可以采用零个或多个活动特定的参数,并且可以返回一个或两个值。它必须始终至少返回一个错误值。活动函数可以将任何可序列化类型作为参数接受,并作为结果返回。 39 | 40 | `func SimpleActivity(ctx context.Context, value string) (string, error)` 41 | 42 | 该函数的第一个参数是 context.Context。这是一个可选参数,可以省略。此参数是标准的 Go context。第二个字符串参数是定制的特定活动参数,可用于在启动时将数据传递到活动中。一个活动可以具有一个或多个这样的参数。活动函数的所有参数必须可序列化,这实际上意味着参数不能是 channels、函数、可变参数或不安全的指针。该活动声明两个返回值:字符串和 error。字符串返回值用于返回活动结果。error 返回值用于指示在执行过程中遇到错误。 43 | 44 | ### 实现 45 | 46 | 您可以像其他任何 Go 服务代码一样编写活动实现代码。此外,您可以使用常规的 loggers 和 metrics controllers 以及标准的 Go 并发结构。 47 | 48 | #### 心跳 49 | 50 | 对于长时间运行的活动,Temporal 为活动代码提供了一个 API,可将生存情况和进度报告给 Temporal 托管服务。 51 | 52 | ```go 53 | progress := 0 54 | for hasWork { 55 | // Send heartbeat message to the server. 56 | activity.RecordHeartbeat(ctx, progress) 57 | // Do some work. 58 | ... 59 | progress++ 60 | } 61 | ``` 62 | 当一个活动由于丢失心跳而超时,函数实现的最后的运算值(上面例子中的`progress`)会作为`TimeoutError`的 details 字段从`workflow.ExecuteActivity`函数返回,`TimeoutType` 会被设置为 `Heartbeat`。 63 | 64 | 您还可以从外部源对活动进行心跳: 65 | 66 | ```go 67 | // The client is a heavyweight object that should be created once per process. 68 | serviceClient, err := client.NewClient(client.Options{ 69 | HostPort: HostPort, 70 | Namespace: Namespace, 71 | MetricsScope: scope, 72 | }) 73 | 74 | // Record heartbeat. 75 | err := serviceClient.RecordActivityHeartbeat(ctx, taskToken, details) 76 | ``` 77 | 该`RecordActivityHeartbeat`函数的参数为: 78 | 79 | - `taskToken`:在活动内部检索`TaskToken`的`ActivityInfo`结构的二进制字段的值。 80 | - `details`:包含进度信息的可序列化有效负载。 81 | 82 | #### 取消 83 | 84 | 当活动取消或其工作流执行完成或失败后,传递给其函数的 context 将被取消,这会将其 channel 的关闭状态设置为`Done`。活动可以使用这种方式执行任何必要的清理并中止其执行。取消仅传递给调用了`RecordActivityHeartbeat`的活动。 85 | 86 | ### 注册 87 | 88 | 为了使活动对承载它的工作进程可见,必须通过调用`activity.Register`来注册活动。 89 | 90 | ```go 91 | activity.Register(SimpleActivity) 92 | ``` 93 | 此调用在完全限定了函数名称和实现之间的 Worker 进程中创建内存映射。如果 Worker 收到针对其未知活动类型的活动执行请求,该请求将失败。 94 | 95 | ## 标记活动为失败 96 | 97 | 要将活动标记为失败,活动函数必须通过`error`返回值返回错误。 98 | -------------------------------------------------------------------------------- /docs/go-activity-async-completion.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-activity-async-completion 3 | title: Asynchronous Activity Completion 4 | --- 5 | 6 | 在某些情况下,我们不能或不希望在完成函数后立即完成活动。例如,您可能有一个需要用户输入才能完成活动的应用程序。您可以使用轮询机制来实现活动,但是更简单且耗费资源较少的实现是异步完成 Temporal 活动。 7 | 8 | 实现异步完成的活动分为两部分: 9 | 10 | 1. 该活动从外部系统提供完成所需的信息,并通知 Temporal 服务正在等待该外部回调。 11 | 2. 外部服务调用 Temporal 服务以完成活动。 12 | 13 | 以下示例演示了第一部分: 14 | 15 | ```go 16 | // Retrieve the activity information needed to asynchronously complete the activity. 17 | //检索异步完成活动所需的活动信息。 18 | activityInfo := activity.GetInfo(ctx) 19 | taskToken := activityInfo.TaskToken 20 | 21 | // Send the taskToken to the external service that will complete the activity. 22 | //将taskToken发送到将完成活动的外部服务。 23 | ... 24 | 25 | // Return from the activity a function indicating that Temporal should wait for an async completion message. 26 | //从活动中返回一个函数,该函数指示Temporal应该等待异步完成信息。 27 | return "", activity.ErrResultPending 28 | ``` 29 | 30 | 以下代码演示了如何成功完成活动: 31 | 32 | ```go 33 | // Instantiate a Temporal service client. 34 | // The same client can be used to complete or fail any number of activities. 35 | // The client is a heavyweight object that should be created once per process. 36 | //实例化临时服务客户端。 37 | //可以使用同一客户端完成或失败任何数量的活动。 38 | //客户端是一个重量级对象,每个进程应创建一次。 39 | serviceClient, err := client.NewClient(client.Options{}) 40 | 41 | // Complete the activity. 42 | //完成活动。 43 | client.CompleteActivity(taskToken, result, nil) 44 | ``` 45 | 46 | 要使活动失败,您可以执行以下操作: 47 | 48 | ```go 49 | // Fail the activity. 50 | client.CompleteActivity(taskToken, nil, err) 51 | ``` 52 | 53 | 以下是该`CompleteActivity`函数的参数: 54 | 55 | * `taskToken`:在活动内部检索`ActivityInfo`结构体的`TaskToken`的字段的二进制值。 56 | * `result`:要为活动记录的返回值。此值的类型必须与活动函数声明的返回值的类型匹配。 57 | * `err`:如果活动因错误而终止,则返回的错误代码。 58 | 59 | 如果`error`不为null,则忽略该`result`字段的值。 60 | 61 | -------------------------------------------------------------------------------- /docs/go-child-workflows.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-child-workflows 3 | title: Child Workflows 4 | --- 5 | 6 | `workflow.ExecuteChildWorkflow` 支持在工作流的实现中编排其他工作流。父工作流具有监视和影响子工作流的生命周期的能力,类似于其对活动调用的方式。 7 | 8 | ```go 9 | cwo := workflow.ChildWorkflowOptions{ 10 | // Do not specify WorkflowId if you want Temporal to generate a unique Id for the child execution. 11 | WorkflowId: "BID-SIMPLE-CHILD-WORKFLOW", 12 | ExecutionStartToCloseTimeout: time.Minute * 30, 13 | } 14 | ctx = workflow.WithChildWorkflowOptions(ctx, cwo) 15 | 16 | var result string 17 | future := workflow.ExecuteChildWorkflow(ctx, SimpleChildWorkflow, value) 18 | if err := future.Get(ctx, &result); err != nil { 19 | workflow.GetLogger(ctx).Error("SimpleChildWorkflow failed.", zap.Error(err)) 20 | return err 21 | } 22 | ``` 23 | 让我们看一下此调用的每个组成。 24 | 25 | 在调用`workflow.ExecuteChildworkflow()`之前,必须配置调用的`ChildWorkflowOptions`。这些选项可自定义各种执行超时,并通过从初始 context 创建子 context 并覆盖所需的值来传递。然后将子 context 传递到`workflow.ExecuteChildWorkflow()`调用中。如果多个活动共享相同的选项值,则在调用`workflow.ExecuteChildworkflow()`时可以使用相同的 context 实例。 26 | 27 | 调用中的第一个参数必需是`workflow.Context`对象。这种类型的`context.Context`的`Done()`方法返回`workflow.Channel`,而不是原生 Go `chan`。 28 | 29 | 第二个参数是我们注册为活动函数的函数。此参数也可以是表示活动函数的完全限定名称的字符串。传递实际函数对象的好处是框架可以验证活动参数。 30 | 31 | 其余参数作为调用的一部分传递到工作流。在我们的示例中,我们有一个参数:`value`。此参数列表必须与工作流函数声明的参数列表匹配。 32 | 33 | 本方法的调用会立即返回并返回`workflow.Future`。这使您可以执行更多代码,而不必等待调用的工作流完成。 34 | 35 | 准备好处理工作流的结果时,请对返回的 future 对象调用`Get()`方法。该方法的参数是我们传递给`workflow.ExecuteChildWorkflow()`调用的`ctx`对象, 以及一个将接收工作流输出的输出参数。输出参数的类型必须与工作流函数声明的返回值的类型匹配。该`Get()`方法将一直阻塞,直到工作流完成且结果可用为止。 36 | 37 | 该`workflow.ExecuteChildWorkflow()`函数类似于`workflow.ExecuteActivity()`。所描述的所有`workflow.ExecuteActivity()`使用模式也都适用于该`workflow.ExecuteChildWorkflow()` 函数。 38 | 39 | 当用户取消父工作流时,可以根据可配置的子策略取消或放弃子工作流。 -------------------------------------------------------------------------------- /docs/go-continue-as-new.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-continue-as-new 3 | title: ContinueAsNew 4 | --- 5 | 6 | 需要定期重新运行的工作流可以原生地实现为带睡眠的大 **for** 循环,其中工作流的整个逻辑都在 **for** 循环的主体内。这种方法的问题在于,该工作流的历史记录将持续增长到达到服务所限制的最大大小的程度。 7 | 8 | **ContinueAsNew** 是一种底层结构,可以实现上述的工作流而不会出现失败的风险。该操作自动完成当前执行并使用相同的**工作流ID**启动工作流的新执行。新的执行不会继承旧执行的任何历史记录。要触发此行为,工作流函数应通过返回特殊的 **ContinueAsNewError** 错误来终止 9 | 10 | ```go 11 | func SimpleWorkflow(workflow.Context ctx, value string) error { 12 | ... 13 | return workflow.NewContinueAsNewError(ctx, SimpleWorkflow, value) 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/go-create-workflows.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-create-workflows 3 | title: Creating Workflows 4 | --- 5 | 6 | 工作流是编排逻辑的实现。Temporal 编程框架(也称为客户端库)使您可以将工作流编排逻辑编写为使用标准 Go 数据建模的简单过程代码。客户端库负责处理 Worker 服务和 Temporal 服务之间的通信,并确保事件之间的状态持久性,即使在 Worker 失败的情况下也是如此。此外,任何特定的执行都不会与特定的 Worker 相关联。编排逻辑的不同步骤最终可能在不同的 Worker 实例上执行,而框架确保在执行该步骤的 Worker 上重新创建必要的状态。 7 | 8 | 但是,为了促进这种操作模型,Temporal 编程框架和托管服务都对编排逻辑的实现施加了一些要求和限制。这些要求和限制的详细信息在下面的“**实现**”部分中进行了描述 。 9 | 10 | ## 概述 11 | 12 | 下面的示例代码展示了执行一个活动的工作流的简单实现。工作流还将作为初始化的一部分接收的唯一参数作为参数传递给活动。 13 | 14 | ```go 15 | package sample 16 | 17 | import ( 18 | "time" 19 | 20 | "go.temporal.io/sdk/workflow" 21 | "go.uber.org/zap" 22 | ) 23 | 24 | // SimpleWorkflow is a sample Temporal workflow function that takes one 25 | // string parameter 'value' and returns an error. 26 | func SimpleWorkflow(ctx workflow.Context, value string) error { 27 | ao := workflow.ActivityOptions{ 28 | TaskQueue: "sampleTaskQueue", 29 | ScheduleToCloseTimeout: time.Second * 60, 30 | ScheduleToStartTimeout: time.Second * 60, 31 | StartToCloseTimeout: time.Second * 60, 32 | HeartbeatTimeout: time.Second * 10, 33 | WaitForCancellation: false, 34 | } 35 | ctx = workflow.WithActivityOptions(ctx, ao) 36 | 37 | var result string 38 | err := workflow.ExecuteActivity(ctx, SimpleActivity, value).Get(ctx, &result) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | workflow.GetLogger(ctx).Info("Done", zap.String("result", result)) 44 | return nil 45 | } 46 | ``` 47 | 48 | ## 声明 49 | 50 | 在 Temporal 编程模型中,工作流通过函数来实现。函数声明指定工作流接受的参数以及它可能返回的任何值。 51 | 52 | ```go 53 | func SimpleWorkflow(ctx workflow.Context, value string) error 54 | ``` 55 | 56 | 让我们解构上面的声明: 57 | 58 | - 该函数的第一个参数是 **ctx working.Context**。这是所有工作流功能的必需参数,并且由 Temporal 客户端库用于传递执行 context 。几乎所有可从工作流功能调用的客户端库功能都需要此 **ctx** 参数。该 **context** 参数与 Go 提供的标准的 **context.Context** 概念相同 。**workflow.Context** 和 **context.Context**之间的唯一区别是 **workflow.Context** 的 **Done()** 函数返回 **workflow.Channel** 而不是标准的 go **chan**。 59 | - 第二个参数 **string** 是自定义工作流参数,可用于在启动时将数据传递到工作流中。工作流可以具有一个或多个这样的参数。工作流函数的所有参数必须可序列化,这实际上意味着参数不能是 channel、函数、可变参数或不安全的指针。 60 | - 由于仅将错误声明为返回值,因此这意味着工作流不会返回其他数据值。**错误**返回值被用于指示执行过程中遇到一个错误,该工作流应该被终止。 61 | 62 | ## 实现 63 | 64 | 为了支持工作流实现的同步和顺序编程模型,必须在工作流实现如何呈现上存在某些限制和要求以确保正确性。要求是: 65 | 66 | - 执行必须是确定性的 67 | - 执行必须是幂等的 68 | 69 | 满足这些要求的一种直接方法是工作流代码应如下: 70 | 71 | - 工作流代码只能读取和操作本地状态或从 Temporal 客户端库函数作为返回值接收的状态。 72 | - 除了通过活动调用之外,工作流代码不应与外部系统交互。 73 | - 工作流代码应仅通过 Temporal 客户端库提供的功能与**时间**进行交互(例如,**workflow.Now()**,**workflow.Sleep()**)。 74 | - 工作流代码不应直接创建 goroutine 并与 goroutines 进行交互,而应使用 Temporal 客户端库提供的功能(例如,**workflow.Go()** 代替 **go**, **workflow.Channel** 而不是 **chan**,**workflow.Selector** 而不是 **select**)。 75 | - 工作流代码应通过 Temporal 客户端库提供的日志器(即 **workflow.GetLogger()**)进行所有日志行为。 76 | - 工作流代码不应使用 range 在 maps 上进行迭代,因为 maps 迭代的顺序是随机的。 77 | 78 | 现在,我们已经奠定了基本规则,我们可以看看用于编写 Temporal 工作流的一些特殊功能和类型,以及如何实现一些通用模式。 79 | 80 | ### 特殊 Temporal SDK 函数和类型 81 | 82 | Temporal 客户端库提供了许多函数和类型以替代某些本地 Go 函数和类型。为了确保工作流代码执行在执行 context 中具有确定性和幂等性,必须使用这些替换掉相关函数和类型。 83 | 84 | 协程相关的构造: 85 | 86 | - **workflow.Go**:这是 **go** 语句的替代。 87 | - **workflow.Channel**:这是原生 **chan** 类型的替代。Temporal 同样提供缓冲通道和非缓冲通道支持。 88 | - **stream.Selector**:这是 **select** 语句的替代。 89 | 90 | 时间相关功能: 91 | 92 | - **workflow.Now()** :这是 **time.Now()** 的替代。 93 | - **workflow.Sleep()**:这是 **time.Sleep()** 的替代。 94 | 95 | ### 标记一个工作流失败 96 | 97 | 要将工作流标记为失败,所有要做的事情是使工作流函数通过 **err** 返回值返回错误。 98 | 99 | ## 注册 100 | 101 | 为了使某些客户端代码能够调用工作流, Worker 进程需要知道其有权访问的所有实现。通过以下调用来注册工作流: 102 | 103 | ```go 104 | workflow.Register(SimpleWorkflow) 105 | ``` 106 | 107 | 此调用实质上是在完全限定的函数名称和实现之间的 Worker 进程中创建了内存映射。从 **init()** 函数调用此注册方法是安全的。如果 Worker 收到未知的工作流类型的任务,它将使该任务失败。但是任务失败不会导致整个工作流失败。 108 | -------------------------------------------------------------------------------- /docs/go-distributed-cron.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-distributed-cron 3 | title: Distributed CRON 4 | --- 5 | 6 | 将任何 Temporal 工作流转换为 Cron 工作流非常简单。您需要做的就是在启动工作流时使用 [StartWorkflowOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#StartWorkflowOptions) 的 CronSchedule 参数提供 cron 计划 。 7 | 8 | 您还可以使用 Temporal CLI 启动工作流,并使用可选的`--cron`参数使用 cron 作业。 9 | 10 | 对于使用 CronSchedule 的工作流: 11 | 12 | * Cron 作业基于 UTC 时间。例如,cron 作业“ 15 8 * * * ”将每天在世界标准时间上午8:15运行。 13 | * 如果工作流失败并且 StartWorkflowOptions 也提供了 RetryPolicy,则工作流将基于 RetryPolicy 重试。重试工作流时,服务器不会安排下一次 cron 运行。 14 | * Temporal 服务仅在当前运行完成后才调度下一个 cron 运行。如果在工作流正在运行(或重试)时下一个计划到期,则它将跳过该计划。 15 | * Cron 工作流直到终止或取消后才会停止。 16 | 17 | Temporal 支持标准 cron 规范: 18 | 19 | ```go 20 | // CronSchedule - Optional cron schedule for workflow. If a cron schedule is specified, the workflow will run 21 | // as a cron based on the schedule. The scheduling will be based on UTC time. The schedule for next run only happen 22 | // after the current run is completed/failed/timeout. If a RetryPolicy is also supplied, and the workflow failed 23 | // or timed out, the workflow will be retried based on the retry policy. While the workflow is retrying, it won't 24 | // schedule its next run. If next schedule is due while the workflow is running (or retrying), then it will skip that 25 | // schedule. Cron workflow will not stop until it is terminated or cancelled (by returning temporal.CanceledError). 26 | // The cron spec is as following: 27 | // ┌───────────── minute (0 - 59) 28 | // │ ┌───────────── hour (0 - 23) 29 | // │ │ ┌───────────── day of the month (1 - 31) 30 | // │ │ │ ┌───────────── month (1 - 12) 31 | // │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday) 32 | // │ │ │ │ │ 33 | // │ │ │ │ │ 34 | // * * * * * 35 | CronSchedule string 36 | ``` 37 | 38 | 该 [crontab guru site](https://crontab.guru/) 网站可以检验你的 cron 表达式是否是有效的。 39 | 40 | ## 转换现有的 cron 工作流 41 | 42 | 在可使用 CronSchedule 之前,实现 cron 工作流的老方法是将延迟计时器用作最后一步,然后返回 `ContinueAsNew`。该实现的一个问题是,如果工作流失败或超时,则 cron 将停止。 43 | 44 | 要将这些工作流转换为使用 Temporal CronSchedule,您所需要做的就是删除延迟计时器,然后不使用而返回 `ContinueAsNew`。然后使用所需的CronSchedule 启动工作流。 45 | 46 | ## 检索最后的成功结果 47 | 48 | 有时,获取先前成功运行的进度很有用。 49 | 这在客户端库中有两个新的 API 可以支持: 50 | `HasLastCompletionResult` and `GetLastCompletionResult`. 51 | 52 | 以下是如何在Go中使用此方法的示例: 53 | 54 | ```go 55 | func CronWorkflow(ctx workflow.Context) (CronResult, error) { 56 | startTimestamp := time.Time{} // By default start from 0 time. 57 | if workflow.HasLastCompletionResult(ctx) { 58 | var progress CronResult 59 | if err := workflow.GetLastCompletionResult(ctx, &progress); err == nil { 60 | startTimestamp = progress.LastSyncTimestamp 61 | } 62 | } 63 | endTimestamp := workflow.Now(ctx) 64 | 65 | // Process work between startTimestamp (exclusive), endTimestamp (inclusive). 66 | // Business logic implementation goes here. 67 | 68 | result := CronResult{LastSyncTimestamp: endTimestamp} 69 | return result, nil 70 | } 71 | ``` 72 | 73 | 请注意,即使其中一个 cron 计划运行失败,此操作也有效。如果下一个计划至少成功完成一次,它将仍然获得最后一个成功的结果。例如,对于日常cron工作流,如果第一天运行成功而第二天运行失败,则第三天运行仍将使用这些 API 从第一天运行中获取结果。 74 | -------------------------------------------------------------------------------- /docs/go-error-handling.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-error-handling 3 | title: Error Handling 4 | --- 5 | 6 | 活动或子工作流可能会失败,并且您可以根据不同的错误情况以不同的方式处理错误。 7 | 8 | 如果活动返回`errors.New()`或`fmt.Errorf()`错误,则这些错误将转换为`*temporal.ApplicationError`并封装在`*temporal.ActivityTaskError`或`*temporal.ChildWorkflowExecutionError`中。 9 | 10 | 如果活动返回的错误为 `temporal.NewRetryableApplicationError("error message", details)`,则该错误将返回为`*temporal.ApplicationError`。 11 | 12 | 还有其他类型的错误如`*temporal.TimeoutError`,`*temporal.CanceledError`和 `*temporal.PanicError`。以下是错误处理代码的示例: 13 | 14 | ```go 15 | err := workflow.ExecuteActivity(ctx, MyActivity, ...).Get(ctx, nil) 16 | if err != nil { 17 | var applicationErr *ApplicationError 18 | if errors.As(err, &applicationErr) { 19 | // retrieve error message 20 | fmt.Println(applicationError.Error()) 21 | 22 | // handle activity errors (created via NewApplicationError() API) 23 | var detailMsg string // assuming activity return error by NewApplicationError("message", true, "string details") 24 | applicationErr.Details(&detailMsg) // extract strong typed details 25 | 26 | // handle activity errors (errors created other than using NewApplicationError() API) 27 | switch err.OriginalType() { 28 | case "CustomErrTypeA": 29 | // handle CustomErrTypeA 30 | case CustomErrTypeB: 31 | // handle CustomErrTypeB 32 | default: 33 | // newer version of activity could return new errors that workflow was not aware of. 34 | } 35 | } 36 | 37 | var canceledErr *CanceledError 38 | if errors.As(err, &canceledErr) { 39 | // handle cancellation 40 | } 41 | 42 | var timeoutErr *TimeoutError 43 | if errors.As(err, &timeoutErr) { 44 | // handle timeout, could check timeout type by timeoutErr.TimeoutType() 45 | switch err.TimeoutType() { 46 | case commonpb.ScheduleToStart: 47 | // Handle ScheduleToStart timeout. 48 | case commonpb.StartToClose: 49 | // Handle StartToClose timeout. 50 | case commonpb.Heartbeat: 51 | // Handle heartbeat timeout. 52 | default: 53 | } 54 | } 55 | 56 | var panicErr *PanicError 57 | if errors.As(err, &panicErr) { 58 | // handle panic, message and stack trace are available by panicErr.Error() and panicErr.StackTrace() 59 | } 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/go-execute-activity.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-execute-activity 3 | title: Executing Activities 4 | --- 5 | 6 | 工作流执行的主要职责是安排要执行的活动。最简单的方法是通过库方法`workflow.ExecuteActivity`。下面的示例代码演示了如何进行此调用: 7 | 8 | ```go 9 | ao := workflow.ActivityOptions{ 10 | TaskQueue: "sampleTaskQueue", 11 | ScheduleToCloseTimeout: time.Second * 60, 12 | ScheduleToStartTimeout: time.Second * 60, 13 | StartToCloseTimeout: time.Second * 60, 14 | HeartbeatTimeout: time.Second * 10, 15 | WaitForCancellation: false, 16 | } 17 | ctx = workflow.WithActivityOptions(ctx, ao) 18 | 19 | var result string 20 | err := workflow.ExecuteActivity(ctx, SimpleActivity, value).Get(ctx, &result) 21 | if err != nil { 22 | return err 23 | } 24 | ``` 25 | 26 | 让我们看一下此调用的每个组成。 27 | 28 | ## 活动选项 29 | 30 | 在调用`workflow.ExecuteActivity()`之前,必须配置调用的`ActivityOptions`。这些选项可自定义各种执行超时,并通过从初始 context 创建子 context 并覆盖所需的值来传递。然后将子 context 传递到`workflow.ExecuteActivity()`调用中。如果多个活动共享相同的选项值,则在调用`workflow.ExecuteActivity()`时可以使用相同的 context 实例。 31 | 32 | ## 活动超时 33 | 34 | 与活动相关联的超时可能有多种。Temporal 保证活动*最多*执行*一次*,因此活动成功或失败都会伴随以下超时方式之一: 35 | 36 | | Timeout | **描述** | 37 | | ------------------------ | ------------------------------------------------------------ | 38 | | `StartToCloseTimeout` | Worker 收到任务后可以花费的最长时间。 | 39 | | `ScheduleToStartTimeout` | 工作流安排任务后,活动 Worker 等待消费任务的时间。如果在指定的时间内没有可用于处理此任务的 Worker ,则该任务将超时。 | 40 | | `ScheduleToCloseTimeout` | 在工作流安排任务之后,完成任务所花费的时间。这通常比`StartToClose`和`ScheduleToStart`超时的总和要大。 | 41 | | `HeartbeatTimeout` | 如果任务在此持续时间内未对 Temporal 服务发出心跳信号,则该任务将被视为失败。这对于长时间运行的任务很有用。 | 42 | 43 | ## ExecuteActivity 调用 44 | 45 | 调用中的第一个参数必需是`workflow.Context`对象。这种类型的`context.Context`的`Done()`方法返回`workflow.Channel`,而不是原生 Go `chan`。 46 | 47 | 第二个参数是我们注册为活动函数的函数。此参数也可以是表示活动函数的完全限定名称的字符串。传递实际函数对象的好处是框架可以验证活动参数。 48 | 49 | 其余参数作为调用的一部分传递给活动。在我们的示例中,我们有一个参数:`value`。此参数列表必须与活动函数声明的参数列表匹配。Temporal 客户端库将对此进行验证。 50 | 51 | 本方法的调用会立即返回并返回`workflow.Future`。这使您可以执行更多代码,而不必等待调用的活动完成。 52 | 53 | 准备好处理活动的结果时,请对返回的 future 对象调用`Get()`方法。该方法的参数是我们传递给`workflow.ExecuteActivity()`调用的`ctx`对象, 以及一个将接收活动输出的出参。输出参数的类型必须与活动函数声明的返回值的类型匹配。该`Get()`方法将阻塞,直到活动完成并且结果可用为止。 54 | 55 | 您可以检索`workflow.ExecuteActivity()`通过 future 返回的结果值,并像任何同步函数调用中常见的返回结果来使用它。下面的示例代码演示了当返回结果是字符串时是如何使用的: 56 | 57 | ```go 58 | var result string 59 | if err := future.Get(ctx, &result); err != nil { 60 | return err 61 | } 62 | 63 | switch result { 64 | case "apple": 65 | // Do something. 66 | case "banana": 67 | // Do something. 68 | default: 69 | return err 70 | } 71 | ``` 72 | 73 | 在此示例中,我们在`workflow.ExecuteActivity()`之后紧接着在返回的 Future 上调用了`Get()`方法。但是这不是必需的。如果要并行执行多个活动,则可以重复调用`workflow.ExecuteActivity()`,存储返回的 Future,然后稍后通过调用 Future 的`Get()`方法来等待所有活动完成。 74 | 75 | 要在返回的 Future 对象上实现更复杂的等待条件,请使用`workflow.Selector`类。 -------------------------------------------------------------------------------- /docs/go-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-hello-world 3 | title: Build a Temporal "Hello World!" app from scratch 4 | sidebar_label: Build "Hello World!" app 5 | tags: helloworld, go, sdk, tutorial 6 | --- 7 | 8 | 您正在迈出构建更好的应用程序的下一步! 9 | 10 | 这是针对初次上手[ Temporal ](https://docs. Temporal .io/docs/overview)并且具有一定的Go基本知识的开发人员的教程。我们建议您预留约20分钟的时间来完成它。通过阅读本教程,您将获取以下内容: 11 | 12 | - 了解如何设置 Temporal Go应用程序项目。 13 | - 更加熟悉核心概念和应用程序结构。 14 | - 从零开始使用[ Temporal Go SDK](https://github.com/Temporalio/sdk-go)构建并测试一个简单的“ Hello World!” Temporal Workflow应用程序。 15 | 16 | ![](../img/docs/hello.png) 17 | 18 | 本教程重点介绍从头开始构建应用程序的实战。为了更好地理解*为什么*应该使用 Temporal ,我们建议您阅读本教程[运行一个 Temporal 汇款应用程序](https://docs.Temporal.io/docs/go-run-your-first-app)以了解其特性。 19 | 20 | 所有教程中的代码都可在此仓库中获得 [hello-world Go template repository](https://github.com/Temporalio/hello-world-project-template-go). 21 | 22 | ## ![](../img/docs/harbor-crane.png)    Go 项目基本框架 23 | 24 | 在开始之前,请确保您已阅读本教程的[准备条件](https://docs. Temporal .io/docs/go-sdk-tutorial-prerequisites)。 25 | 26 | 在终端中,创建一个名为“ hello-world-project”或类似名称的新项目目录,并使用 cd 进入。 27 | 28 | 在新项目目录的根目录中,初始化一个新的 Go 模块: 29 | 30 | ``` 31 | go mod init hello-world-project/app 32 | ``` 33 | 34 | 然后,添加 Temporal Go SDK作为项目依赖项: 35 | 36 | ``` 37 | go get go.temporal.io/sdk@latest 38 | ``` 39 | 40 | ## ![](../img/docs/apps.png)    "Hello World!" 应用 41 | 42 | 现在,我们准备构建 Temporal 工作流应用程序。我们的应用程序将包括四个部分: 43 | 44 | 1. 活动:活动只是包含您的业务逻辑的函数。我们将仅格式化一些文本并将其返回。 45 | 2. 工作流:工作流是组织活动函数调用的函数。我们的工作流将编排单个活动函数的调用。 46 | 3. Worker : Worker 托管“活动”和“工作流”代码,并逐段执行代码。 47 | 4. 启动器:要启动工作流,我们必须向 Temporal 服务发送一个信号,告诉它跟踪工作流的状态。我们将编写一个单独的函数来执行此操作。 48 | 49 | ### 活动 50 | 51 | 首先,让我们定义我们的活动函数,就像 Go 中的任何其他函数一样。活动旨在处理可能导致错误的不确定代码。但是对于本教程,我们要做的只是获取一个字符串,将其附加到“ Hello”,然后将其返回给工作流。 52 | 53 | 创建 activity.go 并添加以下代码: 54 | 55 | ``` go 56 | package app 57 | 58 | import ( 59 | "fmt" 60 | ) 61 | 62 | func ComposeGreeting(name string) (string, error) { 63 | greeting := fmt.Sprintf("Hello %s!", name) 64 | return greeting, nil 65 | } 66 | ``` 67 | 68 | ### 工作流 69 | 70 | 接下来是我们的工作流。在工作流函数中,您可以配置和组织活动函数的执行。同样,工作流函数的定义与其他任何 Go 函数一样。我们的工作流只是调用`ComposeGreeting()`并返回结果。 71 | 72 | 创建workflow.go并添加以下代码: 73 | 74 | ``` go 75 | package app 76 | 77 | import ( 78 | "time" 79 | 80 | "go.temporal.io/sdk/workflow" 81 | ) 82 | 83 | func GreetingWorkflow(ctx workflow.Context, name string) (string, error) { 84 | options := workflow.ActivityOptions{ 85 | ScheduleToCloseTimeout: time.Minute, 86 | } 87 | ctx = workflow.WithActivityOptions(ctx, options) 88 | var result string 89 | err := workflow.ExecuteActivity(ctx, ComposeGreeting, name).Get(ctx, &result) 90 | if err != nil { 91 | return "", err 92 | } 93 | return result, nil 94 | } 95 | ``` 96 | 97 | ### 任务队列 98 | 99 | 任务队列是 Temporal 服务向 Worker s提供信息的方式。启动工作流时,您告诉服务工作流和/或活动使用哪个任务队列作为信息队列。我们将配置 Worker 侦听工作流和活动使用的相同任务队列。由于“任务队列”名称被多个实体使用,因此让我们创建 shared.go 并在其中定义我们的“任务队列”名称: 100 | 101 | ``` go 102 | package app 103 | 104 | const GreetingTaskQueue = "GREETING_TASK_QUEUE" 105 | ``` 106 | 107 | ### Worker 108 | 109 | 我们的 Worker 托管工作流和活动函数并一起执行它们一次。通过从任务队列中提取信息,指示 Worker 执行特定函数。运行代码后,它将结果传递回服务。 110 | 111 | 创建 Worker /main.go 并添加以下代码: 112 | 113 | ``` go 114 | package main 115 | 116 | import ( 117 | "log" 118 | 119 | "go.temporal.io/sdk/client" 120 | "go.temporal.io/sdk/worker" 121 | 122 | "hello-world-project-template-go/app" 123 | ) 124 | 125 | func main() { 126 | // Create the client object just once per process 127 | c, err := client.NewClient(client.Options{}) 128 | if err != nil { 129 | log.Fatalln("unable to create Temporal client", err) 130 | } 131 | defer c.Close() 132 | // This worker hosts both Worker and Activity functions 133 | w := worker.New(c, app.GreetingTaskQueue, worker.Options{}) 134 | w.RegisterWorkflow(app.GreetingWorkflow) 135 | w.RegisterActivity(app.ComposeGreeting) 136 | // Start listening to the Task Queue 137 | err = w.Run(worker.InterruptCh()) 138 | if err != nil { 139 | log.Fatalln("unable to start Worker", err) 140 | } 141 | } 142 | ``` 143 | 144 | ### 工作流启动器 145 | 146 | 可以通过SDK或[CLI](https://docs. Temporal .io/docs/tctl)两种方法来通过 Temporal 启动工作流。在本教程中,我们使用SDK来启动工作流,这是在大多数生产环境中启动工作流的方式。这是我们刚刚执行此操作的代码: 147 | 148 | 创建 start/main.go 并添加以下代码: 149 | 150 | ``` go 151 | package main 152 | 153 | import ( 154 | "context" 155 | "fmt" 156 | "log" 157 | 158 | "go.temporal.io/sdk/client" 159 | 160 | "hello-world-project-template-go/app" 161 | ) 162 | 163 | func main() { 164 | // Create the client object just once per process 165 | c, err := client.NewClient(client.Options{}) 166 | if err != nil { 167 | log.Fatalln("unable to create Temporal client", err) 168 | } 169 | defer c.Close() 170 | options := client.StartWorkflowOptions{ 171 | ID: "greeting-workflow", 172 | TaskQueue: app.GreetingTaskQueue, 173 | } 174 | name := "World" 175 | we, err := c.ExecuteWorkflow(context.Background(), options, app.GreetingWorkflow, name) 176 | if err != nil { 177 | log.Fatalln("unable to complete Workflow", err) 178 | } 179 | var greeting string 180 | err = we.Get(context.Background(), &greeting) 181 | if err != nil { 182 | log.Fatalln("unable to get Workflow result", err) 183 | } 184 | printResults(greeting, we.GetID(), we.GetRunID()) 185 | } 186 | 187 | func printResults(greeting string, workflowID, runID string) { 188 | fmt.Printf("\nWorkflowID: %s RunID: %s\n", workflowID, runID) 189 | fmt.Printf("\n%s\n\n", greeting) 190 | } 191 | ``` 192 | 193 | ## ![](../img/docs/check.png)  测试应用程序 194 | 195 | 让我们向应用程序中添加一个简单的单元测试,以确保一切正常。创建 workflow_test.go 并添加以下代码: 196 | 197 | ``` go 198 | package app 199 | 200 | import ( 201 | "testing" 202 | 203 | "github.com/stretchr/testify/mock" 204 | "github.com/stretchr/testify/require" 205 | "go.temporal.io/sdk/testsuite" 206 | ) 207 | 208 | func Test_Workflow(t *testing.T) { 209 | testSuite := &testsuite.WorkflowTestSuite{} 210 | env := testSuite.NewTestWorkflowEnvironment() 211 | // Mock activity implementation 212 | name := "World!" 213 | env.OnActivity(ComposeGreeting, mock.Anything, name).Return(nil) 214 | env.ExecuteWorkflow(GreetingWorkflow, name) 215 | require.True(t, env.IsWorkflowCompleted()) 216 | require.NoError(t, env.GetWorkflowError()) 217 | var greeting string 218 | require.NoError(t, env.GetWorkflowResult(&greeting)) 219 | require.Equal(t, "Hello World!", greeting) 220 | } 221 | ``` 222 | 223 | 从项目根目录运行此命令以执行单元测试: 224 | 225 | ``` 226 | go test 227 | ``` 228 | 229 | ## ![](../img/docs/running.png)  运行应用程序 230 | 231 | 要运行该应用程序,我们需要启动工作流和 Worker 。您可以以任何顺序启动它们,但必须在单独的终端窗口中运行每个命令。 232 | 233 | 要启动 Worker ,请从项目根目录运行以下命令: 234 | 235 | ``` 236 | go run Worker /main.go 237 | ``` 238 | 239 | 要启动工作流,请从项目根目录运行以下命令: 240 | 241 | ``` 242 | go run start/main.go 243 | ``` 244 | 245 | ![](../img/docs/confetti.png) 246 | 247 | **恭喜,**您已经成功从头开始构建了 Temporal 应用程序! 248 | 249 | ## ![](../img/docs/wisdom.png)  回顾检查 250 | 251 | 做得好!您现在知道了如何使用Go SDK来构建 Temporal Workflow应用程序。让我们进行快速回顾,以确保您记得更重要的部分。 252 | 253 | ![One](../img/docs/one.png)  ** Temporal 工作流应用程序的最少四个组成是什么?** 254 | 255 | 1. 活动函数。 256 | 2. 工作流函数。 257 | 3. 一个用于承载“活动”和“工作流”代码的 Worker 。 258 | 4. 用于启动工作流的前端。 259 | 260 | ![Two](../img/docs/two.png)  ** Temporal 服务如何向 Worker 获取信息?** 261 | 262 | 它将信息添加到任务队列。 263 | 264 | ![Three](../img/docs/three.png)  **对或错? Temporal 活动和工作流函数只是普通的Go函数?** 265 | 266 | 对 267 | 268 | -------------------------------------------------------------------------------- /docs/go-quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-quick-start 3 | title: Quick Start 4 | --- 5 | 6 | 本章节可帮助您安装Temporal服务并实施工作流。 7 | 8 | ## 在本地安装Temporal Server 9 | 10 | 要在本地运行示例,您需要参阅[说明](https://docs.temporal.io/docs/install-temporal-server/)在本地运行Temporal服务。 11 | 12 | ## 从一个空目录开始 13 | 14 | 为项目创建目录 15 | 16 | ``` 17 | mkdir tutorial-go-sdk 18 | ``` 19 | 20 | ``` 21 | cd tutorial-go-sdk 22 | ``` 23 | 24 | ## 初始化 GO 模块和 SDK 包的依赖 25 | 26 | 初始化 Go 模块 27 | 28 | ``` 29 | > go mod init github.com/temporalio/tutorial-go-sdk 30 | go: creating new go.mod: module github.com/temporalio/tutorial-go-sdk 31 | ``` 32 | 33 | 向 Temporal Go SDK 添加依赖项 34 | 35 | ```bash 36 | > go get go.temporal.io/sdk@latest 37 | go: downloading go.temporal.io/sdk v0.29.0 38 | go: go.temporal.io/sdk upgrade => v0.29.0 39 | ``` 40 | 41 | ## 实现活动 42 | 43 | ### 获取用户活动 44 | 45 | 创建文件 get_user.go 46 | 47 | ```go 48 | package main 49 | 50 | import ( 51 | "context" 52 | 53 | "go.temporal.io/sdk/activity" 54 | ) 55 | 56 | // GetUser is the implementation for Temporal activity 57 | func GetUser(ctx context.Context) (string, error) { 58 | logger := activity.GetLogger(ctx) 59 | logger.Info("GetUser activity called") 60 | return "Temporal", nil 61 | } 62 | ``` 63 | 64 | ### 发送问候活动 65 | 66 | 创建文件 send_greeting.go 67 | 68 | ```go 69 | package main 70 | 71 | import ( 72 | "context" 73 | "fmt" 74 | 75 | "go.temporal.io/sdk/activity" 76 | ) 77 | 78 | // SendGreeting is the implementation for Temporal activity 79 | func SendGreeting(ctx context.Context, user string) error { 80 | logger := activity.GetLogger(ctx) 81 | logger.Info("SendGreeting activity called") 82 | 83 | fmt.Printf("Greeting sent to user: %v\n", user) 84 | return nil 85 | } 86 | ``` 87 | 88 | ## 实现问候工作流 89 | 90 | 创建文件 greetings.go 91 | 92 | ```go 93 | package main 94 | 95 | import ( 96 | "time" 97 | 98 | "go.temporal.io/sdk/workflow" 99 | ) 100 | 101 | // Greetings is the implementation for Temporal workflow 102 | func Greetings(ctx workflow.Context) error { 103 | logger := workflow.GetLogger(ctx) 104 | logger.Info("Workflow Greetings started") 105 | 106 | ao := workflow.ActivityOptions{ 107 | ScheduleToStartTimeout: time.Hour, 108 | StartToCloseTimeout: time.Hour, 109 | } 110 | ctx = workflow.WithActivityOptions(ctx, ao) 111 | 112 | var user string 113 | err := workflow.ExecuteActivity(ctx, GetUser).Get(ctx, &user) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | err = workflow.ExecuteActivity(ctx, SendGreeting, user).Get(ctx, nil) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | logger.Info("Greetings workflow complete", "user", user) 124 | return nil 125 | } 126 | ``` 127 | 128 | ## 在 Worker 中绑定工作流和活动 129 | 130 | 创建文件 main.go 131 | 132 | ```go 133 | package main 134 | 135 | import ( 136 | "log" 137 | 138 | "go.temporal.io/sdk/client" 139 | "go.temporal.io/sdk/worker" 140 | ) 141 | 142 | func main() { 143 | // The client is a heavyweight object that should be created once 144 | clientOptions := client.Options{HostPort: "localhost:7233"} 145 | serviceClient, err := client.NewClient(clientOptions) 146 | 147 | if err != nil { 148 | log.Fatalf("Unable to create client. Error: %v", err) 149 | } 150 | 151 | w := worker.New(serviceClient, "tutorial_tq", worker.Options{}) 152 | 153 | w.RegisterWorkflow(Greetings) 154 | w.RegisterActivity(GetUser) 155 | w.RegisterActivity(SendGreeting) 156 | 157 | err = w.Run(worker.InterruptCh()) 158 | if err != nil { 159 | log.Fatalf("Unable to start worker. Error: %v", err) 160 | } 161 | } 162 | ``` 163 | 164 | ## 启动 Worker 165 | 166 | 运行承载工作流和活动实现的 Worker 应用 167 | 168 | ```bash 169 | > go run *.go 170 | 2020-07-31T16:06:13.245-0700 INFO tutorial-go-sdk/main.go:16 Zap logger created 171 | 2020/07/31 16:06:13 INFO No logger configured for temporal client. Created default one. 172 | 2020/07/31 16:06:13 INFO Started Worker Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ 173 | ``` 174 | 175 | ## 启动工作流执行 176 | 177 | ```bash 178 | > docker run --network=host --rm temporalio/tctl:0.29.0 wf start --tq tutorial_tq -w Greet_Temporal_1 --wt Greetings --et 3600 179 | Started Workflow Id: Greet_Temporal_1, run Id: 2666b82a-c706-45e2-8d8e-ae84a5b4e892 180 | ``` 181 | 182 | ## 工作流完成执行 183 | 184 | ``` 185 | 2020/07/31 16:08:09 INFO Workflow Greetings started Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 2666b82a-c706-45e2-8d8e-ae84a5b4e892 186 | 2020/07/31 16:08:09 DEBUG ExecuteActivity Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 2666b82a-c706-45e2-8d8e-ae84a5b4e892 ActivityID 5 ActivityType GetUser 187 | 2020/07/31 16:08:09 INFO GetUser activity called Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ ActivityID 5 ActivityType GetUser WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 2666b82a-c706-45e2-8d8e-ae84a5b4e892 188 | 2020/07/31 16:08:09 DEBUG ExecuteActivity Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 2666b82a-c706-45e2-8d8e-ae84a5b4e892 ActivityID 11 ActivityType SendGreeting 189 | 2020/07/31 16:08:09 INFO SendGreeting activity called Namespace default TaskQueue tutorial_tq WorkerID 56116@local@ ActivityID 11 ActivityType SendGreeting WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 2666b82a-c706-45e2-8d8e-ae84a5b4e892 190 | Greeting sent to user: Temporal 191 | 2020/07/31 16:08:09 INFO Greetings workflow complete Namespace default TaskQueue tutorial_tq WorkerID 63248@local@ WorkflowType Greetings WorkflowID Greet_Temporal_1 RunID 6bfa9a98-7dee-44b1-8473-6f7117fae930 user Temporal 192 | ``` 193 | 194 | ## 尝试 Go SDK 样例 195 | 196 | 查看 [Go SDK Samples](https://github.com/temporalio/go-samples) 197 | 然后尝试简单的 Temporal 使用场景。 198 | -------------------------------------------------------------------------------- /docs/go-retries.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-retries 3 | title: Activity and Workflow Retries 4 | --- 5 | 6 | 由于各种中间条件,活动和工作流可能会失败。在这些情况下,我们想重试失败的活动或子工作流,甚至父工作流。这可以通过提供可选的重试策略来实现。重试策略如下所示: 7 | 8 | ``` go 9 | // RetryPolicy defines the retry policy. 10 | RetryPolicy struct { 11 | //第一次重试的退避间隔。如果系数为1.0,则会被用于每次重试。 12 | //必填,无默认值。 13 | // Backoff interval for the first retry. If coefficient is 1.0 then it is used for all retries. 14 | // Required, no default value. 15 | InitialInterval time.Duration 16 | 17 | //用于计算下一个重试退避间隔的系数。 18 | //下一个重试间隔是前一个间隔乘以该系数。 19 | //必须为1或更大。默认值为2.0。 20 | // Coefficient used to calculate the next retry backoff interval. 21 | // The next retry interval is previous interval multiplied by this coefficient. 22 | // Must be 1 or larger. Default is 2.0. 23 | BackoffCoefficient float64 24 | 25 | //重试之间的最大退避间隔。间隔以指数回退增加。 26 | //此值是间隔的上限。默认值为初始间隔的100倍。 27 | // Maximum backoff interval between retries. Exponential backoff leads to interval increase. 28 | // This value is the cap of the interval. Default is 100x of initial interval. 29 | MaximumInterval time.Duration 30 | 31 | //最大尝试次数。如果超过该时间,重试将停止,即使尚未过期也是如此。 32 | //如果未设置或设置为0,则表示无限制 33 | // Maximum number of attempts. When exceeded the retries stop even if not expired yet. 34 | // If not set or set to 0, it means unlimited 35 | MaximumAttempts int32 36 | 37 | //不可恢复的错误。这是可选的。如果错误类型与此列表匹配,则 Temporal 服务器将停止重试。 38 | // 注意: 39 | // - 取消不是失败,因此不会被重试, 40 | // - 仅可重试 StartToClose 或心跳超时。 41 | // Non-Retriable errors. This is optional. Temporal server will stop retry if error type matches this list. 42 | // Note: 43 | // - cancellation is not a failure, so it won't be retried, 44 | // - only StartToClose or Heartbeat timeouts are retryable. 45 | NonRetryableErrorTypes []string 46 | } 47 | ``` 48 | 49 | 要启用重试,请在执行它们时向`ActivityOptions`或`ChildWorkflowOptions` 提供自定义重试策略。 50 | 51 | ``` go 52 | expiration := time.Minute * 10 53 | retryPolicy := &temporal.RetryPolicy{ 54 | InitialInterval: time.Second, 55 | BackoffCoefficient: 2, 56 | MaximumInterval: expiration, 57 | MaximumAttempts: 5, 58 | } 59 | ao := workflow.ActivityOptions{ 60 | ScheduleToStartTimeout: expiration, 61 | StartToCloseTimeout: expiration, 62 | HeartbeatTimeout: time.Second * 30, 63 | RetryPolicy: retryPolicy, // Enable retry. 64 | } 65 | ctx = workflow.WithActivityOptions(ctx, ao) 66 | activityFuture := workflow.ExecuteActivity(ctx, SampleActivity, params) 67 | ``` 68 | 69 | 如果活动在失败之前对其进度进行了心跳报告,则重试尝试将包含该进度,因此活动实现可以从失败的进度中恢复,例如: 70 | 71 | ``` go 72 | func SampleActivity(ctx context.Context, inputArg InputParams) error { 73 | startIdx := inputArg.StartIndex 74 | if activity.HasHeartbeatDetails(ctx) { 75 | // Recover from finished progress. 76 | var finishedIndex int 77 | if err := activity.GetHeartbeatDetails(ctx, &finishedIndex); err == nil { 78 | startIdx = finishedIndex + 1 // Start from next one. 79 | } 80 | } 81 | 82 | // Normal activity logic... 83 | for i:=startIdx; i 0 && signalVal != "SOME_VALUE" { 29 | return errors.New("signalVal") 30 | } 31 | ``` 32 | 33 | 在上面的示例中,工作流代码使用 **workflow.GetSignalChannel** 打开命名的信号的 **workflow.Channel**。然后我们使用 **workflow.Selector** 在此 channel 上等待并处理随信号接收的有效负载。 34 | 35 | ## SignalWithStart 36 | 37 | 您可能不知道工作流是否正在运行并且可以接受信号。使用 [client.SignalWithStartWorkflow](https://pkg.go.dev/go.temporal.io/sdk/client#Client) API,可以将信号发送到当前工作流实例(如果存在)或创建新的实例运行后发送信号。因此`SignalWithStartWorkflow`不将运行ID作为参数。 38 | -------------------------------------------------------------------------------- /docs/go-tracing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-tracing 3 | title: Tracing and Context Propagation 4 | --- 5 | 6 | ## 追踪 7 | 8 | Go 客户端通过 [OpenTracing](https://opentracing.io/) 提供分布式跟踪支持。可以通过在客户端和 Worker 实例化期间分别在 [ClientOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#ClientOptions) 和 [WorkerOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) 中提供 [opentracing.Tracer](https://pkg.go.dev/github.com/opentracing/opentracing-go#Tracer) 实现来配置跟踪。跟踪使您可以查看工作流的调用图及其活动、子工作流等。有关如何配置和利用跟踪的更多详细信息,请参阅 [OpenTracing 文档](https://opentracing.io/docs/getting-started/)。OpenTracing 支持使用 [Jaeger ](https://www.jaegertracing.io/)已得到了验证,但是[这里](https://opentracing.io/docs/supported-tracers/)提到的其他实现也应该起作用。跟踪利用客户端提供的通用 context 传输来支持。 9 | 10 | ## Context 传输 11 | 12 | 我们提供了一种在工作流中传输自定义 context 的标准方法。[ClientOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#ClientOptions) 和 [WorkerOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) 允许配置 context 传输器。 context 传输器提取并传递整个工作流中`context.Context` 和`workflow.Context`对象中存在的信息。配置了 context 传输器后,您就能够像通常在 Go 中一样访问 context 对象中的所需值。作为示例,Go客户端实现了一个[追踪 context 传输器](https://github.com/temporalio/temporal-go-sdk/blob/master/internal/tracer.go)。 13 | 14 | ### 服务器端头部支持 15 | 16 | 在服务器端,Temporal 提供了一种在不同的工作流转换之间传输其头部的机制。 17 | 18 | ```proto 19 | message Header { 20 | map fields = 1; 21 | } 22 | ``` 23 | 24 | 客户端利用它来传递选定的 context 信息。[HeaderReader](https://pkg.go.dev/go.temporal.io/sdk/internal#HeaderReader) 和[HeaderWriter](https://pkg.go.dev/go.temporal.io/sdk/internal#HeaderWriter) 是允许读取和写入 Temporal 服务器头部的接口。客户端已经提供了这些[实现](https://github.com/temporalio/temporal-go-sdk/blob/master/internal/headers.go)。`HeaderWriter`在标题中设置一个字段。头部是一个映射,因此多次为同一键设置一个值将覆盖以前的值。`HeaderReader`遍历头部 map,并在每个键/值对上运行提供的处理程序函数,从而使您可以处理感兴趣的字段。 25 | 26 | ```go 27 | type HeaderWriter interface { 28 | Set(string, []byte) 29 | } 30 | 31 | type HeaderReader interface { 32 | ForEachKey(handler func(string, []byte) error) error 33 | } 34 | ``` 35 | 36 | ### Context 传输器 37 | 38 | context 传输器需要实现以下四种方法,以在工作流中传输所选的 context : 39 | 40 | - `Inject`旨在从 Go [context.Context](https://golang.org/pkg/context/#Context) 对象中选择感兴趣的 context 键,然后使用 [HeaderWriter](https://pkg.go.dev/go.temporal.io/sdk/internal#HeaderWriter) 接口将其写入头部 41 | - `InjectFromWorkflow`与上述相同,但操作的是 [workflow.Context ](https://pkg.go.dev/go.temporal.io/sdk/internal#Context)对象 42 | - `Extract`读取头部并将感兴趣的信息放回 [context.Context](https://golang.org/pkg/context/#Context) 对象 43 | - `ExtractToWorkflow`与上述相同,但操作的是 [workflow.Context](https://pkg.go.dev/go.temporal.io/sdk/internal#Context) 对象 44 | 45 | [tracing context propagator](https://github.com/temporalio/temporal-go-sdk/blob/master/internal/tracer.go) 46 | 展示了实现 context 传输的示例。 47 | 48 | ```go 49 | type ContextPropagator interface { 50 | Inject(context.Context, HeaderWriter) error 51 | 52 | Extract(context.Context, HeaderReader) (context.Context, error) 53 | 54 | InjectFromWorkflow(Context, HeaderWriter) error 55 | 56 | ExtractToWorkflow(Context, HeaderReader) (Context, error) 57 | } 58 | ``` 59 | 60 | ## Q & A 61 | 62 | ### 有完整的例子吗? 63 | 64 | [context propagation sample](https://github.com/temporalio/temporal-go-samples/blob/master/ctxpropagation/workflow.go) 65 | 这里的样例展示了如何配置自定义键的 context 传输器,并展示 context 是如何在工作流和活动的定义传输的。 66 | 67 | ### 我可以配置多个 context 传输器吗? 68 | 69 | 是的,我们建议您配置多个 context 传输器,每个传输器用于传输特定类型的 context 。 70 | -------------------------------------------------------------------------------- /docs/go-versioning.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-versioning 3 | title: Versioning 4 | --- 5 | 6 | Temporal 使用事件源来重建工作流状态,方法是在工作流定义代码上重放保存的历史事件数据。这意味着,如果处理不当,对工作流定义代码的任何不兼容更新都可能导致不确定的问题。 7 | 8 | ## workflow.GetVersion() 9 | 10 | 考虑以下工作流定义: 11 | 12 | ```go 13 | func MyWorkflow(ctx workflow.Context, data string) (string, error) { 14 | ao := workflow.ActivityOptions{ 15 | ScheduleToStartTimeout: time.Minute, 16 | StartToCloseTimeout: time.Minute, 17 | } 18 | ctx = workflow.WithActivityOptions(ctx, ao) 19 | var result1 string 20 | err := workflow.ExecuteActivity(ctx, ActivityA, data).Get(ctx, &result1) 21 | if err != nil { 22 | return "", err 23 | } 24 | var result2 string 25 | err = workflow.ExecuteActivity(ctx, ActivityB, result1).Get(ctx, &result2) 26 | return result2, err 27 | } 28 | ``` 29 | 现在,我们已经用 ActivityC 替换了 ActivityA,并部署了更新的代码。如果现在存在一个由工作流原始版本代码启动的工作流执行,其中 ActivityA 已经完成并且结果已记录到历史记录中,新版本的工作流代码将选择该工作流执行并尝试从该处恢复。但是工作流将失败,因为新代码期望从历史数据中获得 ActivityC 的结果,但它将获得 ActivityA 的结果。这将导致工作流因非确定性错误而失败。 30 | 31 | 因此我们使用 `workflow.GetVersion().` 32 | 33 | ```go 34 | var err error 35 | v := workflow.GetVersion(ctx, "Step1", workflow.DefaultVersion, 1) 36 | if v == workflow.DefaultVersion { 37 | err = workflow.ExecuteActivity(ctx, ActivityA, data).Get(ctx, &result1) 38 | } else { 39 | err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1) 40 | } 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | var result2 string 46 | err = workflow.ExecuteActivity(ctx, ActivityB, result1).Get(ctx, &result2) 47 | return result2, err 48 | ``` 49 | 当`workflow.GetVersion()`为新的工作流执行运行时,它将在工作流历史记录中记录一个标记,这样对于此更改Id(示例中的“Step1”)的所有以后对`GetVersion`的调用都将始终返回给定的版本号,在示例中为 `1` 。 50 | 51 | 如果进行其他更改,例如用 ActivityD 替换 ActivityC,则需要添加一些其他代码: 52 | 53 | ```go 54 | v := workflow.GetVersion(ctx, "Step1", workflow.DefaultVersion, 2) 55 | if v == workflow.DefaultVersion { 56 | err = workflow.ExecuteActivity(ctx, ActivityA, data).Get(ctx, &result1) 57 | } else if v == 1 { 58 | err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1) 59 | } else { 60 | err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1) 61 | } 62 | ``` 63 | 请注意,我们的`maxSupported`已从 1 更改为 2。在引入此`GetVersion()`调用之前已经跨过此调用的工作流将返回`DefaultVersion`。在`maxSupported`设置为 1 的情况下运行的工作流将返回 1。新的工作流将返回 2。 64 | 65 | 在确定版本 1 之前的所有工作流执行均已完成之后,可以删除该版本的代码。现在看起来应该如下所示: 66 | 67 | ```go 68 | v := workflow.GetVersion(ctx, "Step1", 1, 2) 69 | if v == 1 { 70 | err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1) 71 | } else { 72 | err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1) 73 | } 74 | ``` 75 | 您会注意到,`minSupported`已从`DefaultVersion`更改为`1`。如果在此代码上重放了较旧版本的工作流执行历史记录,则将失败,因为最低预期版本为 1。确定版本 1 的所有工作流执行均已完成后,可以删除 1,此时代码如下所示: 76 | 77 | ```go 78 | _ := workflow.GetVersion(ctx, "Step1", 2, 2) 79 | err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1) 80 | ``` 81 | 请注意,我们保留了对`GetVersion()`的调用。保留此调用有两个原因: 82 | 83 | 1. 这样可以确保如果仍在运行较旧版本的工作流执行,它将在此处失败并且不会继续。 84 | 2. 如果您需要对`Step1`进行其他更改,例如将 ActivityD 更改为 ActivityE,则只需`maxVersion`从 2 更新到 3 并从那里进行分支。 85 | 86 | 你只需要对每个`changeID`保留第一个`GetVersion()`调用。所有之后的具有相同更改 ID 的`GetVersion()`调用都可以安全删除。如有必要,您可以删除第一个 `GetVersion()`调用,但是需要确保以下几点: 87 | 88 | * 使用较旧版本的所有执行均已完成。 89 | * 您不能再使用`Step1` changeId。如果将来需要对同一部分进行更改,例如从 ActivityD 更改为 ActivityE,则需要使用其他的 changeId(如`Step1-fix2`),然后从 DefaultVersion 重新启动 minVersion。该代码如下所示 90 | 91 | ```go 92 | v := workflow.GetVersion(ctx, "Step1-fix2", workflow.DefaultVersion, 1) 93 | if v == workflow.DefaultVersion { 94 | err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1) 95 | } else { 96 | err = workflow.ExecuteActivity(ctx, ActivityE, data).Get(ctx, &result1) 97 | } 98 | ``` 99 | 如果您不需要保留当前正在运行的工作流执行,则升级工作流非常简单。在部署不使用`GetVersion()`的新版本的工作流代码时,您可以简单地终止所有当前正在运行的工作流执行,并暂停创建新的工作流,然后继续创建工作流。但是这不是常见的情况,您需要照顾当前正在运行的工作流执行,因此使用`GetVersion()`更新代码应是您使用的方法。 100 | 101 | 但是,如果您希望基于当前工作流逻辑继续当前正在运行的工作流,但又想确保新工作流正在新逻辑上运行,则可以将工作流定义为一个新的 `WorkflowType`,然后将开始路径(调用`StartWorkflow()`)更改为启动新的工作流类型。 102 | 103 | ## 健全检查 104 | 105 | Temporal 客户端 SDK 会执行完整性检查,以帮助防止明显的不兼容更改。健全性检查以相同的顺序验证重放中的决策是否与历史记录中的事件相匹配。通过调用以下任何方法来生成该决定: 106 | 107 | * workflow.ExecuteActivity() 108 | * workflow.ExecuteChildWorkflow() 109 | * workflow.NewTimer() 110 | * workflow.Sleep() 111 | * workflow.SideEffect() 112 | * workflow.RequestCancelWorkflow() 113 | * workflow.SignalExternalWorkflow() 114 | 115 | 添加,删除或重新排序上述任何方法都会触发完整性检查,并导致不确定性错误。 116 | 117 | 健全性检查并不执行彻底检查。例如,它不检查活动的输入参数或计时器持续时间。如果对每个属性都执行了检查,那么它将变得过于严格,难以维护工作流代码。例如,如果将活动代码从一个程序包移动到另一个程序包,则会更改`ActivityType`,从技术上讲,这将变成另一种活动。但是,我们不想在这种更改上触发失败,因此我们仅检查 `ActivityType`的函数名称部分。 118 | -------------------------------------------------------------------------------- /docs/go-workers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: go-workers 3 | title: Worker Service 4 | --- 5 | 6 | Worker 或 *Worker 服务*是承载工作流和活动实现的服务。 Worker 轮询 *Temporal 服务*中的任务,执行这些任务,并将任务执行结果传达回 *Temporal 服务*。Temporal 服务由 Temporal 客户端开发,部署和运营。 7 | 8 | 您可以在新服务或现有服务中运行 Temporal Worker 。使用框架 API 启动 Temporal Worker ,并链接您需要服务执行的所有活动和工作流实现。 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "os" 15 | "os/signal" 16 | 17 | "github.com/uber-go/tally" 18 | "go.uber.org/zap" 19 | 20 | "go.temporal.io/sdk/client" 21 | "go.temporal.io/sdk/worker" 22 | "go.temporal.io/sdk/workflow" 23 | ) 24 | 25 | var ( 26 | Taskqueue = "samples_tq" 27 | ) 28 | 29 | func main() { 30 | logger, err := zap.NewDevelopment() 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | // The client and worker are heavyweight objects that should be created once per process. 36 | serviceClient, err := client.NewClient(client.Options{ 37 | HostPort: client.DefaultHostPort, 38 | Logger: logger, 39 | }) 40 | if err != nil { 41 | logger.Fatal("Unable to create client", zap.Error(err)) 42 | } 43 | defer serviceClient.Close() 44 | 45 | worker := worker.New(serviceClient, TaskQueue, worker.Options{}) 46 | 47 | worker.RegisterWorkflow(MyWorkflow) 48 | worker.RegisterActivity(MyActivity) 49 | 50 | err = worker.Start() 51 | if err != nil { 52 | logger.Fatal("Unable to start worker", zap.Error(err)) 53 | } 54 | } 55 | 56 | func MyWorkflow(context workflow.Context) error { 57 | return nil 58 | } 59 | 60 | func MyActivity() error { 61 | return nil 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/install-temporal-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: install-temporal-server 3 | title: Install Temporal 4 | --- 5 | 6 | ## 概述 7 | 8 | 本指南将向您展示如何使用`docker-compose`来快速安装和运行Temporal 。 9 | 10 | ## 准备工作 11 | 12 | 1. [Install Docker](https://docs.docker.com/engine/install) 13 | 2. [Install docker-compose](https://docs.docker.com/compose/install) 14 | 15 | ## 安装 Temporal 16 | 17 | 在终端中,`cd`进入要安装和运行Temporal的目录。 18 | 19 | 运行以下命令以下载临时docker-compose文件: 20 | 21 | ```bash 22 | curl -L https://github.com/temporalio/temporal/releases/latest/download/docker.tar.gz | tar -xz --strip-components 1 docker/docker-compose.yml 23 | ``` 24 | 25 | 完成后,您应该在工作目录中看到该`docker-compose.yml`文件。 26 | 27 | :::note 28 | 29 | 您可以通过更改URL中的发行版本来安装特定版本的 Temporal: 30 | 31 | `https://github.com/temporalio/temporal/releases/download//docker.tar.gz` 32 | 33 | ::: 34 | 35 | ## 运行 Temporal 36 | 37 | 运行以下命令以启动 Temporal 服务: 38 | 39 | ```bash 40 | docker-compose up 41 | ``` 42 | 43 | 您将看到类似于以下内容的输出: 44 | 45 | ``` 46 | Creating network "quick_start_default" with the default driver 47 | Pulling temporal (temporalio/temporal-auto-setup:0.29.0)... 48 | ... 49 | ... 50 | temporal_1 | Description: Default namespace for Temporal Server 51 | temporal_1 | OwnerEmail: 52 | temporal_1 | NamespaceData: map[string]string(nil) 53 | temporal_1 | Status: NamespaceStatusRegistered 54 | temporal_1 | RetentionInDays: 1 55 | temporal_1 | EmitMetrics: false 56 | temporal_1 | ActiveClusterName: active 57 | temporal_1 | Clusters: active 58 | temporal_1 | HistoryArchivalStatus: Enabled 59 | temporal_1 | HistoryArchivalURI: file:///tmp/temporal_archival/development 60 | temporal_1 | VisibilityArchivalStatus: Disabled 61 | temporal_1 | Bad binaries to reset: 62 | temporal_1 | +-----------------+----------+------------+--------+ 63 | temporal_1 | | BINARY CHECKSUM | OPERATOR | START TIME | REASON | 64 | temporal_1 | +-----------------+----------+------------+--------+ 65 | temporal_1 | +-----------------+----------+------------+--------+ 66 | temporal_1 | + echo 'Default namespace registration complete.' 67 | temporal_1 | Default namespace registration complete. 68 | ``` 69 | 70 | Temporal Server 现在正在运行! 71 | 72 | 您可以在[localhost:8088上](http://localhost:8088/)查看 Temporal Web 界面。 73 | 74 | :::note 75 | 76 | 如果您希望将Temporal部署到Kubernetes集群,请遵循[helm-charts指南](https://github.com/temporalio/helm-charts)。 77 | 78 | ::: 79 | 80 | ## 运行工作流 81 | 82 | 现在,您可以通过 Temporal 运行工作流。 83 | 84 | 通过运行 [Go示例](https://github.com/temporalio/go-samples) 或 [Java示例 ](https://github.com/temporalio/java-samples)快速入门,或者使用 [Go SDK](https://docs.temporal.io/docs/go-quick-start/) 或 [Java SDK ](https://docs.temporal.io/docs/java-quick-start/)编写自己的[程序](https://github.com/temporalio/java-samples)。 85 | 86 | -------------------------------------------------------------------------------- /docs/learn-glossary.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: learn-glossary 3 | title: Glossary 4 | --- 5 | 6 | Temporal 以独特的方式概念化软件开发。与 Temporal 共享重叠的方法和概念的产品很少。 7 | 8 | 因此以下术语在 Temporal中被重新定义,并在整个文档和参考资料中用于描述其相关内容。 9 | 10 | ### 活动 11 | 12 | 活动是实现您应用程序逻辑(例如调用服务或对媒体文件进行编码转换)的业务级别函数。 13 | 14 | - 一个活动通常执行一个明确定义的动作;它可以短期或长期运行。 15 | - 活动可以实现为同步方法,也可以完全异步地涉及多个进程。 16 | - 可以根据提供的指数重试策略无限期重试活动。 17 | - 如果由于某种原因未在指定的超时时间内完成活动,则会向**工作流**报告错误,该错误将决定如何处理该活动。活动持续时间没有限制。 18 | - 活动支持**活动心跳**,有助于在活动执行失败的情况下更快地识别超时。 19 | 20 | ### 活动心跳 21 | 22 | 活动心跳向 Temporal 服务器提供了正在执行的**活动任务**的状态。 23 | 24 | - 活动心跳有助于确保快速识别**活动**执行失败和超时。 25 | - 活动心跳通过代码实现,并由[工作流]实现自行决定是否记录。 26 | - 自定义**活动**进度信息可以包含在活动心跳中,并且可以在重试**活动**时使用。 27 | 28 | ### 活动ID 29 | 30 | 标识正在执行的**活动**的唯一 ID 。Id 可以由系统生成,也可以由调用 **活动 **的工作流代码提供。可以使用活动 ID 来异步完成**活动**。 31 | 32 | ### 活动任务 33 | 34 | 一个包含**活动**调用信息的任务,而活动通过**任务队列**传递给**活动 Worker**。 35 | 36 | - 收到**活动任务**后,**活动 Worker**将执行相应的**活动**。 37 | 38 | ### 归档 39 | 40 | 归档功能是在**工作流**保留期过后自动将**事件历史记录**从正常持久性移动到 Blob 存储的功能。 41 | 42 | - 归档的目的是能够在不影响持久性存储的情况下,在需要的情况下保留**事件历史记录**。 43 | - 保留期过后,您可能要保留**事件历史记录**的原因有两个: 44 | 1. 合规性:出于法律原因,**事件历史记录**可能需要长时间保存。 45 | 2. 调试:可以参考较旧的**事件历史记录**以帮助进行调试。 46 | 47 | ### 客户端桩 48 | 49 | 客户端桩是 Java SDK 中的客户端代理,用于在其代表的实体上进行远程调用。 50 | 51 | - 例如,要启动**工作流**,将通过特殊的 API 创建代表**工作流**的 Stub 对象。然后该 Stub 用于启动、查询或发信号通知相应的**工作流**。 52 | - Go SDK不使用客户端桩。 53 | 54 | ### 命令 55 | 56 | **工作流**持久化功能所请求的任何操作都称为命令。 57 | 58 | - 例如编排**活动**、取消子**工作流**或启动计时器都是命令。 59 | - **工作流任务**包含有命令的可选列表。 60 | - **Worker**执行**工作流**会生成一个命令列表,作为工作流任务的结果。此列表作为**工作流任务**完成请求的一部分发送到 Temporal 服务。 61 | - 每个命令都作为**事件**记录在**事件历史**记录中。例如,`StartTimer`命令被记录为相应的`TimerStarted`事件。 62 | 63 | ### 事件 64 | 65 | Temporal 对于每个工作流跟踪的事件分位两种类型: 66 | 67 | 1. **命令**事件。 68 | 2. 其他。 69 | 70 | - 命令事件是与**工作流 Worker**生成的**命令**相对应的事件。 71 | - 所有其他事件表示**工作流**预期会发生的各种外部事件,例如**活动**完成,计时器触发,取消请求等。 72 | - 所有事件均记录在**事件历史记录中**。 73 | 74 | ### 事件历史 75 | 76 | 事件历史是为您的应用程序追加日志的**活动**。 77 | 78 | - 事件历史记录由 Temporal 服务持久化保留,从而可以从崩溃或故障中无缝恢复应用程序状态。 79 | - 它还用作调试的审核日志。 80 | 81 | ### 本地活动 82 | 83 | **本地活动**是一种在工作流代码相同的进程中直接调用的**活动**。 84 | 85 | - 虽然本地活动消耗的资源少于正常**活动**,但其持续时间较短且缺乏速率限制。 86 | 87 | ### 命名空间 88 | 89 | Temporal 支持多租户服务,独立的单元称为命名空间。 90 | 91 | - 默认情况下,Temporal 服务使用“default”命名空间。如果未指定,则所有 API 和工具(例如 UI 和 CLI)都默认为“default”命名空间。因此如果您不打算使用多个命名空间,建议您使用默认命名空间。 92 | - **任务队列**名称以及**工作流 ID**对应于特定的命名空间。例如当工作流启动时,它会在特定的命名空间中启动。 93 | - Temporal 保证命名空间内的**工作流ID**唯一,并支持使用相同的**工作流ID**进行**工作流执行**(如果它们在不同的命名空间中)。 94 | - 还可以通过特殊的 CRUD API 或通过[`tctl`](https://docs.temporal.io/docs/tctl/)为每个命名空间配置各种配置选项,例如保留期或存档目标。 95 | - 在多集群部署中,命名空间是故障转移的单位。 96 | - 每个命名空间一次只能在单个 Temporal 集群上处于活动状态。但是不同的命名空间可以在不同的群集中处于活动状态,并且可以独立进行故障转移。 97 | 98 | ### 查询 99 | 100 | 从调用者的角度来看,查询是一种同步操作,用于报告**工作流**的状态。 101 | 102 | - 查询逻辑在**工作流**中以代码的形式实现。 103 | - 查询本质上是只读的,不会影响工作流状态。 104 | 105 | ### 运行ID 106 | 107 | 运行ID是时间 Temporal 分配给每个**工作流**运行的 UUID 。 108 | 109 | - Temporal 临时保证一次只能打开一个具有给定**工作流ID**的**工作流执行**。但之后**工作流执行**完成后,如果配置的策略允许你也许可以在**工作流**已关闭或失败后使用相同的**工作流 ID**重新执行它。 110 | - 每次这样的重新执行称为一次运行。运行 ID 用于唯一标识一次运行,因此它与其他的运行可以共享工作流 ID。 111 | 112 | ### 信号 113 | 114 | 信号是对**工作流**的外部异步请求。 115 | 116 | - 信号可以在正在运行的**工作流**生命期的任何时间点向其传递通知或更新。 117 | 118 | ### 任务 119 | 120 | 任务是执行特定**活动**或**工作流**状态转换所需的上下文。 121 | 122 | - 任务的类型有两种: 123 | 1. **活动任务** 124 | 2. **工作流任务** 125 | - 单个**活动**执行对应于单个**活动 Task**,而**工作流执行**使用多个**工作流任务**。 126 | 127 | ### 任务队列 128 | 129 | 任务队列是**Worker **订阅并轮询以消费要执行的任务的队列。 130 | 131 | - 每个任务队列都能够对**活动任务**和**工作流任务**进行排队。 132 | - 任务队列依赖于与其余 Temporal 服务相同的持久性存储(任务队列不基于其他技术,例如Kafka)。 133 | 134 | ### 任务令牌 135 | 136 | 任务令牌是 Temporal **活动**的唯一关联ID 。 137 | 138 | - **活动**完成调用将单个任务令牌或命名空间、工作流 ID 和活动 ID 作为一组参数。 139 | 140 | ### Worker 141 | 142 | Worker 是承载**工作流**和**活动**实现的服务。 143 | 144 | - 单个 Worker 实际上包含**Activity Worker**和**Workflow Worker**,它们抽象了逻辑分离并具有执行两种类型任务的能力。 145 | - Worker 向 Temporal 服务轮询**任务**、执行**任务**,并将**任务**执行结果传达回 Temporal 服务。 146 | - Worker 服务由 Temporal 用户开发,部署和运营。 147 | 148 | ### 工作流 149 | 150 | 编排活动的故障无感知、有状态函数。 151 | 152 | - 工作流完全控制执行哪些**活动**以及执行顺序。 153 | - 工作流仅能通过**活动**与外部世界交互。 154 | - 使工作流代码变成工作流的重要因素是 Temporal 会保留其状态。因此承载工作流代码的 **Worker** 进程的任何失败都不会影响**工作流执行**。工作流将继续进行就好像这些失败没有发生一样。而同时**活动**可能由于任何原因随时会失败。 155 | - 由于工作流代码是完全故障无感知的,因此可以保证实现者能获得有关**活动**失败或超时的通知并采取相应措施。 156 | - 工作流的持续时间没有限制。 157 | 158 | ### 工作流执行 159 | 160 | **工作流**的实例。 161 | 162 | - 工作流执行可能包含多个**工作流**在运行。当**工作流**的**事件历史记录**太大时,可以使用“Continue as New”标志来调用下一个调用,以自动创建新的运行。 163 | 164 | ### 工作流ID 165 | 166 | **工作流执行**的唯一标识符。 167 | 168 | - Temporal 保证**命名空间**中 ID 的唯一性。 169 | - 如果存在另一个打开的工作流执行,尝试使用重复的 ID 启动**工作流**会导致**already started**错误。但是此行为取决于`WorkflowIdReusePolicy`标志。如果设置为`ALLOW_DUPLICATE`,则可以使用相同的工作流 ID 开始新的执行。 170 | 171 | ### 工作流任务 172 | 173 | 工作流任务是一个**任务**,其中包含调用一个**工作流**的信息。 174 | 175 | - 每次记录可能影响**工作流**状态的新外部事件时,包含该事件的**工作流**任务都会被添加到**任务队列**中,然后由**工作流Worker**处理。 176 | - 处理新事件后,将使用**命令**列表完成工作流任务。 177 | - 工作流任务的处理通常非常快,并且与**工作流**调用的操作持续时间无关。 -------------------------------------------------------------------------------- /docs/namespaces.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: namespaces 3 | title: Namespaces 4 | --- 5 | 6 | Temporal 全局命名空间功能为客户端提供了在发生数据中心故障转移时从另一个集群继续执行其工作流的功能。尽管您可以配置全局命名空间以将其复制到任意数量的集群中,但是其仅将在单个集群中被视为处于活动状态。 7 | 8 | ## 全球命名空间架构 9 | 10 | Temporal 引入了一个新的顶级实体 Global Namespaces,它为跨集群的工作流执行复制提供支持。客户端应用程序需要对所有集群上的“活动/决策”任务运行 Worker 轮询。Temporal 将仅在当前活动集群上分派任务。备用集群上的 Worker 将闲置,直到全局命名空间进行故障转移。 11 | 12 | 由于 Temporal 是提供高度一致语义的服务,因此我们仅允许在活动集群上使用外部事件,例如 **StartWorkflowExecution**,**SignalWorkflowExecution**等。全局命名空间依赖于本地集群(Local_Quorum)上的轻量级事务(paxos)来更新工作流执行状态并创建复制任务,这些任务异步应用于跨集群的状态复制。如果应用程序在全局命名空间处于待机模式下的集群上进行这些API调用,则 Temporal 将使用 **NamespaceNotActiveError** 拒绝这些调用,其中会包含当前活动集群的名称。应用程序负责将外部事件转发到当前处于活动状态的集群。 13 | 14 | ## 全球命名空间的新配置 15 | 16 | ### IsGlobal 17 | 18 | 此配置用于将集群本地的命名空间与全局命名空间区分开。它在任务更新时控制复制任务的创建,从而允许在集群之间复制状态。这是一个只读设置,仅在初始化命名空间时才能设置。 19 | 20 | ### 集群 21 | 22 | 命名空间可以故障转移到的集群列表,包括当前的活动集群。这也是一个只读设置,仅在初始化命名空间时才能设置。开发路线图上的重新复制功能将允许更新此配置,以在将来添加/删除集群。 23 | 24 | ### 主动集群名称 25 | 26 | 全局命名空间的当前活动集群的名称。每次全局命名空间故障转移到另一个集群时,都会更新此配置。 27 | 28 | ### 故障转移版本 29 | 30 | 唯一的故障转移版本,也代表全局命名空间的当前活动集群。Temporal 允许从任何集群触发故障转移,因此故障转移版本的设计用来避免错误地在两个集群上同时触发故障转移时发生冲突。 31 | 32 | ## 解决冲突 33 | 34 | 与为活动执行提供最多一次语义的本地命名空间不同,全局命名空间只能支持至少一次语义。Temporal XDC 依赖于集群之间事件的异步复制,因此在发生故障转移时,由于复制任务的滞后,有可能在新的活动集群上再次调度活动。这也意味着在新集群进行故障转移后更新工作流的执行状态后,就无法应用该执行的任何先前的复制任务。这会导致工作流在先前的活动集群中执行所获得的某些进度损失。在解决冲突期间,Temporal 在丢弃复制任务之前会将所有外部事件(如信号)重新注入到新历史记录中。即使在故障转移期间某些进度可能会回滚,但 Temporal 保证了工作流不会卡住并将继续向前推进。 35 | 36 | ## 可见性API 37 | 38 | 在活动集群和备用集群上都允许使用所有可见性 API。这使 [Temporal Web](https://github.com/temporalio/temporal-web) 可以无缝地用于全局命名空间,因此可以从复制该命名空间的任何集群中查询工作流执行的所有可见性记录。即使全局命名空间处于待机模式,直接向Temporal Visibility API进行 API 调用的应用程序也将继续工作。但是当从备用集群查询工作流执行状态时,由于复制延迟的存在,查询可能会有延迟。 39 | 40 | ## CLI 41 | 42 | Temporal CLI也可用于查询命名空间配置或执行故障转移。这是一些相关的命令。 43 | 44 | ### 查询全局命名空间 45 | 46 | 以下命令可用于描述全局命名空间元数据: 47 | 48 | ``` shell 49 | $ tctl --ns temporal-canary-xdc n desc 50 | Name: temporal-canary-xdc 51 | Description: temporal canary cross dc testing namespace 52 | OwnerEmail: temporal-dev@temporal.io 53 | NamespaceData: 54 | Status: REGISTERED 55 | RetentionInDays: 7 56 | EmitMetrics: true 57 | ActiveClusterName: dc1 58 | Clusters: dc1, dc2 59 | ``` 60 | 61 | ### 故障转移全局命名空间 62 | 63 | 以下命令可用于将全局命名空间 *my-namespace-global* 故障转移到 *dc2* 集群: 64 | 65 | ``` shell 66 | $ tctl --ns my-namespace-global n up --ac dc2 67 | ``` 68 | 69 | ## 常见问题 70 | 71 | ### 故障转移后未完成的活动会怎样? 72 | 73 | Temporal 不会跨集群转发活动的完成。任何未完成的活动最终都会根据配置超时。您的应用程序应具有重试逻辑,以便在故障转移到新的 DC 之后重试该活动并将其再次分派给 Worker 。即使没有全局命名空间,处理此操作与处理由 Worker 重新启动导致的活动超时的方式几乎相同。 74 | 75 | ### 对备用集群进行启动或信号API调用时会发生什么? 76 | 77 | Temporal 将拒绝该调用并返回 **NamespaceNotActiveError**。应用程序负责根据错误中提供的信息将失败的调用转发到活动集群。 78 | 79 | ### 建议将外部事件发送到活动集群的方式是什么? 80 | 81 | 此时的建议是,如果事件可以在任何DC中生成,则将事件发布到 Kafka 主题。然后,有一个消费者在同一 DC 中从聚合的 Kafka 主题进行消费,并将其发送给 Temporal。Kafka 消费者和全局命名空间都需要一起进行故障转移。 -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: overview 3 | title: Overview 4 | sidebar_label: Overview 5 | description: This guide will help you build your own resilient applications using Temporal Workflow as Code™ 6 | --- 7 | 8 | 大多数实际用例场景远不止一个请求响应体那么简单,它们需要跟踪复杂的状态、响应异步事件,并与外部不可靠的依赖项进行通信。构建此类应用程序的常用方法是对无状态服务,数据库,定时任务和队列系统等进行大杂烩。因为充斥着大量代码用于衔接,从而掩盖了大量底层细节背后的实际业务逻辑,这对开发人员的生产效率会产生负面影响。同时由于很难使所有组件保持健康,因此此类系统经常会出现可用性问题。 9 | 10 | Temporal 解决方案是一种[*故障无感知的有状态的*编程模型](https://docs.temporal.io/docs/workflows/),可以消除绝大多数在构建可扩展分布式应用程序时的复杂性。本质上,Temporal 提供了未链接到特定进程的持久虚拟内存,并保留了完整的应用程序状态(包括函数堆栈)以及跨各种主机和软件故障的局部变量。这使您可以发挥一个编程语言的全部功力来编写代码,而 Temporal 则来负责应用程序的耐用性,可用性和可扩展性。 11 | 12 | Temporal 由编程框架(客户端库)和托管服务(后端)组成。框架可以使开发人员能够以熟悉的语言编写和编排任务( [Go](https://github.com/temporalio/temporal-go-sdk/) 和 [Java](https://github.com/temporalio/temporal-java-sdk) 如今已经支持,在 [Python](https://github.com/firdaus/cadence-python) 和 [C#](https://github.com/nforgeio/neonKUBE/tree/master/Lib/Neon.Cadence) 中通过[代理](https://github.com/nforgeio/neonKUBE/tree/master/Go/src/github.com/loopieio/cadence-proxy)来运行的某些项目正在开发中)。 13 | 14 | 该框架使开发人员可以用熟悉的语言编写故障无感知的代码。([Go](https://github.com/temporalio/temporal-go-sdk/) 和 [Java](https://github.com/temporalio/temporal-java-sdk) 可以用于生产中。[Python](https://github.com/firdaus/cadence-python) 和 [C#](https://github.com/nforgeio/neonKUBE/tree/master/Lib/Neon.Cadence) 正在开发中)。 15 | 16 | 后端服务是无状态的,并且依赖于持久性存储。当前支持 Cassandra 和 MySQL 存储方式。实际上任何其他支持多行单分片事务的数据库都可以被添加支持。服务有不同的部署模型。在 Uber,我们的团队运营着由数百个应用程序共享的多租户集群。 17 | 18 | 观看 Uber Open Summit 上 Maxim 的演讲,了解有关 Temporal 编程模型和价值主张的介绍。 19 | 20 | [![](https://res.cloudinary.com/marcomontalbano/image/upload/v1603718168/video_to_markdown/images/youtube--llmsBGKOuWI-c05b58ac6eb4c4700831b2b3070cd403.jpg)](https://www.youtube.com/embed/llmsBGKOuWI "") 21 | 22 | Temporal 服务的 GitHub 存储库是 [temporalio/temporal](https://github.com/temporalio/temporal)。Temporal 服务的 Docker 镜像可以再在 Docker Hub 上的[temporalio/server](https://hub.docker.com/r/temporalio/server) 上获取 。 23 | 24 | -------------------------------------------------------------------------------- /docs/queries.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: queries 3 | title: Queries 4 | --- 5 | 6 | ## 同步查询 7 | 8 | 工作流代码在 Temporal 框架中是有状态的,Temporal 可在各种软件和硬件故障时保持状态。状态在工作流执行期间不断变化。为了将此内部状态暴露给外部世界,Temporal 提供了同步查询功能。从工作流实现者的角度来看,查询作为由外部实体调用的同步回调暴露给外部。可以为每种工作流类型提供多个此类回调,从而将不同的信息暴露给不同的外部系统。 9 | 10 | 要执行查询,外部客户端会调用同步 Temporal API *,*该 API 提供*命名空间、workflowId、查询名称*和可选*查询参数*。 11 | 12 | 查询回调必须是只读的,不能以任何方式改变工作流的状态。另一个限制是查询回调不能包含任何阻止代码。上述两个限制都排除了从查询处理程序调用活动的能力。 13 | 14 | Temporal 团队目前正在努力实现*更新*功能,该功能与查询的调用方式相似,但将支持工作流状态变更和本地活动调用。 15 | 16 | ## 堆栈跟踪查询 17 | 18 | Temporal 客户端库可以直接使用一些预定义的查询。当前,唯一受支持的内置查询是*stack_trace*。该查询返回所有工作流拥有的线程的堆栈。这是解决生产中任何工作流的好方法。 19 | 20 | -------------------------------------------------------------------------------- /docs/samples.md: -------------------------------------------------------------------------------- 1 | 这里主要是描述 [Go SDK samples](https://github.com/temporalio/samples-go) 中各个样例主要使用了什么特性,方便有需要的朋友想要了解相关特性时快速查找样例 2 | 3 | - branch 4 | 5 | 异步运行多个活动,最后一起使用 Future 收集结果,类似于 WaitGroup 6 | 7 | - cancelactivity 8 | 9 | 活动的运行与触发中途取消 10 | 11 | - child-workflow 12 | 13 | 在工作流中运行子工作流 14 | 15 | - child-workflow-continue-as-new 16 | 17 | 将子工作流重新作为新工作流多次运行,可以用全局参数控制运行次数 18 | 19 | - choice-exclusive 20 | 21 | 使用参数控制选择运行哪个活动 22 | 23 | - choice-multi 24 | 25 | 异步运行多个传入不同参数的活动 26 | 27 | - cron 28 | 29 | cron 作业工作流 30 | 31 | - ctxpropagation 32 | 33 | 使用 ctx 在工作流和活动之间传递数据 34 | 35 | - dsl 36 | 37 | dsl 相关内容 38 | 39 | - dynamic 40 | 41 | **动态**活动注册和运行 42 | 43 | - expense 44 | 45 | 订单创建与异步人工消费,活动流等待**活动异步完成**的触发 46 | 47 | - fileprocessing 48 | 49 | 使用**会话**绑定文件的下载处理上传在同一个 Worker 上完成 50 | 51 | - greetings 52 | 53 | 收集上一个活动的参数并传入下个活动 54 | 55 | - helloworld 56 | 57 | helloworld 58 | 59 | - mutex 60 | 61 | 使用外部 **signal** 和工作流之间 **singal** 完成一个互斥锁 62 | 63 | - parallel 64 | 65 | 使用 **workflow.Go** 并发在工作流中运行多套逻辑,每套逻辑可包含不同的活动 66 | 67 | - pickfirst 68 | 69 | 同时运行多个活动,使用 **selector.Select(ctx)** 等待一个活动完成,并取消剩余的活动 70 | 71 | - pso 72 | 73 | PSO 算法实现 74 | 75 | - query 76 | 77 | 主要展示查询 workflow 信息 78 | 79 | - recovery 80 | 81 | 工作流的执行状态恢复与查询 82 | 83 | - retryactivity 84 | 85 | 活动的重试机制,包括从失败进度点重试 86 | 87 | - searchattributes 88 | 89 | Workflow 筛选器的配置与使用 90 | 91 | - splitmerge 92 | 93 | 使用 **workflow.Go** 在工作流中并发进行块读取处理,并使用 channel 汇总结果 94 | 95 | - timer 96 | 97 | 使用计时器和 **selector.Select** 完成活动超时等待 -------------------------------------------------------------------------------- /docs/system-architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: system-architecture 3 | title: System architecture 4 | --- 5 | 6 | ## 概述 7 | 8 | Temporal 是一个高度可伸缩的,故障无感知的有状态的代码平台。故障无感知的代码是对实现故障容错和持久性的常用技术的进一级抽象。 9 | 10 | 常见的基于 Temporal 的应用程序由 Temporal 服务,工作流 Worker 和活动 Worker 以及外部客户端组成。请注意,两种类型的 Worker 以及外部客户端都是角色之一,并且可以根据需要在单个应用程序进程中并置它们。 11 | 12 | ## Temporal 服务 13 | 14 | ![时间总览](../img/docs/system-architecture.png) 15 | 16 | Temporal 的核心是高度可扩展的多租户服务。该服务通过强类型的 [Proto API](https://github.com/temporalio/temporal-proto/blob/master/workflowservice/service.proto) 暴露其所有功能。 17 | 18 | 在内部,它依赖于持久性存储。当前开箱即用地支持 Apache Cassandra 和 MySQL 存储。要使用更复杂的格式列出工作流,可以使用Elasticsearch 集群。 19 | 20 | Temporal 服务负责保持工作流状态和相关的持久化计时器。它维护内部队列(称为任务队列),其用于将任务分派给外部 Worker 。 21 | 22 | Temporal 服务是多租户的。因此可以实现不同用例的多个 Worker 池连接到同一服务实例。例如在Uber,一百多个应用程序使用了一项服务。同时,一些外部客户端为每个应用程序部署了一个 Temporal 服务实例。对于本地开发,可以使用通过 docker-compose 配置的本地 Temporal 服务实例。 23 | 24 | ![时间总览](../img/docs/system-architecture-2.png) 25 | 26 | ## 工作流 Worker 27 | 28 | Temporal 重用*工作流自动化*命名空间中的术语。因此故障无感知的有状态代码称为工作流。 29 | 30 | Temporal 服务不会直接执行工作流代码。工作流代码由外部(从服务的角度)*工作流 Worker*进程托管。这些流程从 Temporal 服务中接收*决策任务*,这些*任务*包含工作流需要处理的事件,并将其传递给工作流代码,最终将工作流*决策*传达回服务。 31 | 32 | 由于工作流代码在服务外部,因此可以用可以与服务 Thrift API 进行通讯的任何语言来实现。目前,Java 和 Go 客户端已准备就绪。Python 和 C#客户端正在开发中。如果您有兴趣用您的首选语言为客户端提供贡献,请告诉我们。 33 | 34 | Temporal 服务 API 并不强加任何特定的工作流定义语言。因此特定的 Worker 可以被实现用来执行几乎所有现有的工作流规范。Temporal 团队选择的开箱即用的模型基于持久化功能的思想。持久化的功能尽可能地接近应用程序业务逻辑,而只需要的很少的衔接代码。 35 | 36 | ## 活动 Worker 37 | 38 | 工作流故障无感知的代码不受基础架构故障的影响。但是它必须与不完美的外部世界进行沟通,在那里失败是很常见的。所有与外界的交互都是通过活动进行的。活动是代码段,可以执行任何特定于应用程序的操作,例如调用服务、更新数据库记录或从 Amazon S3 下载文件。与队列系统相比,Temporal 活动的功能非常丰富。示例特性包括将任务路由到特定进程、无限重试、心跳和无限执行时间。 39 | 40 | 活动由*活动 Worker*进程主持,这些*活动 Worker*进程从 Temporal 服务接收*活动任务*,调用相应的活动实现并报告任务完成状态。 41 | 42 | ## 外部客户端 43 | 44 | 工作流和活动 Worker 托管工作流和活动代码。但是要创建工作流实例(使用时间术语执行)时应调用 Temporal 服务的 `StartWorkflowExecution` API。通常工作流由外部实体(如UI、微服务或 CLI)启动。 45 | 46 | 这些实体还可以: 47 | 48 | - 以信号形式通知工作流有关异步外部事件的信息 49 | - 同步查询工作流状态 50 | - 同步等待工作流完成 51 | - 取消、终止、重新启动和重置工作流 52 | - 使用列表 API 搜索特定的工作流 53 | -------------------------------------------------------------------------------- /docs/task-queues.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: task-queues 3 | title: Task Queues 4 | --- 5 | 6 | 当工作流调用活动时,它将`ScheduleActivityTask` [命令](https://docs.temporal.io/docs/learn-glossary/#command)发送到 Temporal 服务。之后该服务更新工作流的状态,并将[活动任务](https://docs.temporal.io/docs/learn-glossary/#activity-task)分派给实现该活动的 Worker。Temporal 服务使用中间件队列代替直接调用 Worker ,因此会将该服务将*活动任务*添加到此队列,并且 Worker 使用长轮询请求接收该任务。Temporal 称此用于调度活动任务的队列为*活动任务队列*。 7 | 8 | 同样,当工作流需要处理外部事件时,将创建决策任务。*决策任务队列*用于传送其到工作流 Worker(也称为*决策者*)。 9 | 10 | 虽然 Temporal 任务队列是队列,但它们与常用的队列技术有所不同。主要的一点是它们不需要显式注册,而是根据需要创建的。任务队列的数量没有限制。一个常见的用例是在每个 Worker 进程中都有一个任务队列,并使用它将活动任务传递给该进程。另一个用例是每个 Worker 池都有一个任务队列。 11 | 12 | 使用任务队列交付任务而不是通过同步 RPC 调用活动 Worker 有多个优点: 13 | 14 | - Worker 不需要任何开放的端口,更安全。 15 | - Worker 不需要通过 DNS 或任何其他网络发现机制来公告自己。 16 | - 当所有 Worker 都关闭时,消息将保留在任务队列中,以等待 Worker 恢复。 17 | - Worker 仅在有可用容量时才轮询消息,因此它永远不会过载。 18 | - 自动平衡大量 Worker 的负载。 19 | - 任务队列支持服务器端限制。这使您可以限制为 Worker 池分配任务的速率 ,并且在出现高峰时仍支持以较高的速率添加任务。 20 | - 任务队列可用于将请求路由到特定的 Worker 池甚至特定的进程。 21 | 22 | -------------------------------------------------------------------------------- /docs/tctl.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: tctl 3 | title: tctl (CLI) 4 | sidebar_label: tctl (CLI) 5 | --- 6 | 7 | Temporal CLI是一个命令行工具,可用于在 Temporal 服务上执行各种任务。它可以执行命名空间操作(例如注册、更新和描述)以及工作流操作(例如开始工作流、显示工作流历史记录和信号通知工作流)。 8 | 9 | ## 使用 CLI 10 | 11 | 可以从 Docker Hub 镜像 _temporalio/tctl_ 直接使用 Temporal CLI,也可以在本地编译CLI工具。 12 | 13 | 使用 docker 映像描述命名空间的示例: 14 | 15 | ``` 16 | docker run --rm temporalio/tctl:0.29.0 --namespace samples-namespace namespace describe 17 | ``` 18 | 19 | 在Docker 18.03及更高版本上,您可能会收到“connection refused”错误。您可以通过将主机设置为“ host.docker.internal”来解决此问题(有关更多信息,请参见[此处](https://docs.docker.com/docker-for-mac/networking/#use-cases-and-workarounds))。 20 | 21 | ``` 22 | docker run --network=host --rm temporalio/tctl:0.29.0 --namespace samples-namespace namespace describe 23 | ``` 24 | 25 | 要在本地构建CLI工具,请克隆[Temporal服务存储库](https://github.com/temporalio/temporal)并运行 `make bins`。这将产生一个名为的可执行文件`tctl`。在本地构建中,用于描述命名空间的相同命令如下所示: 26 | 27 | ``` 28 | ./tctl --namespace samples-namespace namespace describe 29 | ``` 30 | 31 | 为了简洁起见,下面的示例命令将使用 `./tctl` 。 32 | 33 | ## 环境变量 34 | 35 | 为重复的参数设置环境变量可以缩短 CLI 命令。 36 | 37 | - **TEMPORAL_CLI_ADDRESS** - Temporal 前端服务的主机和端口 (默认: `127.0.0.1:7233`) 38 | - **TEMPORAL_CLI_NAMESPACE** - 工作流命名空间, 这样你就不用指定 `--namespace` (默认命名空间: `default`) 39 | - **TEMPORAL_CLI_TLS_CA** - 服务器证书颁发机构证书文件的路径 40 | - **TEMPORAL_CLI_TLS_CERT** - 公共x509证书文件的路径,用于相互 TLS 身份验证 41 | - **TEMPORAL_CLI_TLS_KEY** - 相互TLS身份验证的私钥文件的路径 42 | 43 | ## Quick Start 44 | 45 | - 运行 `./tctl -h` 获取有关顶级命令和全局选项的帮助 46 | - 运行 `./tctl namespace -h` 获取有关命名空间操作的帮助 47 | - 运行 `./tctl workflow -h` 获取有关工作流操作的帮助 48 | - 运行 `./tctl taskqueue -h` 获取有关任务队列操作的帮助 49 | 50 | **注意:**使用 CLI 之前,请确保您已运行 Temporal 服务器 51 | 52 | ### 命名空间操作的例子 53 | 54 | - 注册一个名为“ samples-namespace”的新命名空间: 55 | 56 | ``` 57 | ./tctl --namespace samples-namespace namespace register 58 | # OR using short alias 59 | ./tctl --ns samples-namespace n re 60 | ``` 61 | 62 | - 查看 "samples-namespace" 详细信息: 63 | 64 | ``` 65 | ./tctl --namespace samples-namespace namespace describe 66 | ``` 67 | 68 | ### 工作流操作的例子 69 | 70 | 以下示例假定设置了`TEMPORAL_CLI_NAMESPACE`环境变量。 71 | 72 | #### 运行工作流 73 | 74 | 启动工作流并查看其进度。在工作流完成之前,该命令不会完成。 75 | 76 | ``` 77 | ./tctl workflow run --tq hello-world --wt Workflow --et 60 -i '"temporal"' 78 | 79 | # view help messages for workflow run 80 | ./tctl workflow run -h 81 | ``` 82 | 83 | 简要说明:要运行工作流,用户必须指定以下内容: 84 | 85 | 1. 任务队列名称 (--tq) 86 | 2. 工作流类型 (--wt) 87 | 3. 以秒为单位的从执行开始到关闭的超时 (--et) 88 | 4. 以 JSON 格式输入 (--i) (可选) 89 | 90 | 上面的示例使用[此示例工作流](https://github.com/temporalio/go-samples/blob/master/helloworld/helloworld.go) ,并使用字符串作为`-i '"temporal"'`参数的输入。单引号(`''`)用于将输入包装为 JSON。 91 | 92 | **注意:**您需要启动 Worker ,以便工作流可以运行。(在 temporal-go-samples 中运行`make && ./bin/helloworld -m worker`以启动 Worker ) 93 | 94 | #### 显示任务队列中正在运行的 Worker 95 | 96 | ``` 97 | ./tctl taskqueue desc --tq hello-world 98 | ``` 99 | 100 | #### 启动工作流 101 | 102 | ``` 103 | ./tctl workflow start --tq hello-world --wt Workflow --et 60 -i '"temporal"' 104 | 105 | # view help messages for workflow start 106 | ./tctl workflow start -h 107 | 108 | # for a workflow with multiple inputs, provide a separate -i flag for each of them 109 | ./tctl workflow start --tq hello-world --wt WorkflowWith3Args --et 60 -i '"your_input_string"' -i 'null' -i '{"Name":"my-string", "Age":12345}' 110 | ``` 111 | 112 | 工作流`start`命令与该`run`命令相似,但是在启动工作流后立即返回工作流 ID 和 run_id。使用`show`命令查看工作流的历史记录/进度。 113 | 114 | ##### 启动/运行工作流时重复使用相同的工作流 ID 115 | 116 | 使用选项`--workflowidreusepolicy`或`--wrp`配置工作流 ID 重用策略。 117 | 118 | **选项0 AllowDuplicateFailedOnly:**当具有相同工作流 ID 的工作流尚未运行且上次执行关闭状态为*[终止,取消,超时,失败]中的*一种时,允许使用相同的工作流 ID 启动工作流执行。 119 | 120 | **选项1 AllowDuplicate:**当具有相同工作流 ID 的工作流尚未运行时,允许使用相同的工作流 ID 启动工作流执行。 121 | 122 | **选项2 RejectDuplicate:**不允许使用与以前的工作流相同的工作流 ID 开始执行工作流。 123 | 124 | ``` 125 | # use AllowDuplicateFailedOnly option to start a Workflow 126 | ./tctl workflow start --tq hello-world --wt Workflow --et 60 -i '"temporal"' --wid "" --wrp AllowDuplicateFailedOnly 127 | 128 | # use AllowDuplicate option to run a workflow 129 | ./tctl workflow run --tq hello-world --wt Workflow --et 60 -i '"temporal"' --wid "" --wrp AllowDuplicate 130 | ``` 131 | 132 | ##### 启动带有备忘录的工作流 133 | 134 | 备注是不可变的键/值对,可以在启动工作流时将其附加到工作流中。列出工作流时这些也是可见的。有关备忘的更多信息,请参见 [此处](https://docs.temporal.io/docs/filter-workflows/#memo-vs-search-attributes)。 135 | 136 | ``` 137 | tctl wf start -tq hello-world -wt Workflow -et 60 -i '"temporal"' -memo_key ‘“Service” “Env” “Instance”’ -memo ‘“serverName1” “test” 5’ 138 | ``` 139 | 140 | #### 显示工作流历史记录 141 | 142 | ``` 143 | ./tctl workflow show -w 3ea6b242-b23c-4279-bb13-f215661b4717 -r 866ae14c-88cf-4f1e-980f-571e031d71b0 144 | # a shortcut of this is (without -w -r flag) 145 | ./tctl workflow showid 3ea6b242-b23c-4279-bb13-f215661b4717 866ae14c-88cf-4f1e-980f-571e031d71b0 146 | 147 | # if run_id is not provided, it will show the latest run history of that workflow_id 148 | ./tctl workflow show -w 3ea6b242-b23c-4279-bb13-f215661b4717 149 | # a shortcut of this is 150 | ./tctl workflow showid 3ea6b242-b23c-4279-bb13-f215661b4717 151 | ``` 152 | 153 | #### 显示工作流执行信息 154 | 155 | ``` 156 | ./tctl workflow describe -w 3ea6b242-b23c-4279-bb13-f215661b4717 -r 866ae14c-88cf-4f1e-980f-571e031d71b0 157 | # a shortcut of this is (without -w -r flag) 158 | ./tctl workflow describeid 3ea6b242-b23c-4279-bb13-f215661b4717 866ae14c-88cf-4f1e-980f-571e031d71b0 159 | 160 | # if run_id is not provided, it will show the latest workflow execution of that workflow_id 161 | ./tctl workflow describe -w 3ea6b242-b23c-4279-bb13-f215661b4717 162 | # a shortcut of this is 163 | ./tctl workflow describeid 3ea6b242-b23c-4279-bb13-f215661b4717 164 | ``` 165 | 166 | #### 列出关闭或打开的工作流执行 167 | 168 | ``` 169 | ./tctl workflow list 170 | 171 | # default will only show one page, to view more items, use --more flag 172 | ./tctl workflow list -m 173 | ``` 174 | 175 | 使用**--query**可以使用类似SQL查询的方式列出工作流: 176 | 177 | ``` 178 | ./tctl workflow list --query "WorkflowType='main.SampleParentWorkflow' AND CloseTime = missing " 179 | ``` 180 | 181 | 这将返回所有打开的工作流,其工作流类型为 "main.SampleParentWorkflow"。 182 | 183 | #### 查询工作流执行 184 | 185 | ``` 186 | # use custom query type 187 | ./tctl workflow query -w -r --qt 188 | 189 | # use build-in query type "__stack_trace" which is supported by Temporal SDK 190 | ./tctl workflow query -w -r --qt __stack_trace 191 | # a shortcut to query using __stack_trace is (without --qt flag) 192 | ./tctl workflow stack -w -r 193 | ``` 194 | 195 | #### 信号、取消、终止工作流 196 | 197 | ``` 198 | # signal 199 | ./tctl workflow signal -w -r -n -i '"signal-value"' 200 | 201 | # cancel 202 | ./tctl workflow cancel -w -r 203 | 204 | # terminate 205 | ./tctl workflow terminate -w -r --reason 206 | ``` 207 | 208 | 终止正在运行的工作流执行将在历史记录中将 WorkflowExecutionTerminated 事件记录为关闭事件。终止执行的工作流不再调度决策任务。取消正在运行的工作流执行,将在历史记录中记录 WorkflowExecutionCancelRequested 事件,并调度新的决策任务。取消后,工作流有机会进行一些清理工作。 209 | 210 | #### 批量信号、取消、终止工作流 211 | 212 | 批处理作业基于列出工作流查询(**--query**)。它支持信号、取消和终止作为批处理作业类型。对于终止工作流的批处理作业,它将递归终止子工作流。 213 | 214 | 启动批处理作业(使用信号作为批处理类型): 215 | 216 | ``` 217 | tctl --ns samples-namespace batch start --query "WorkflowType='main.SampleParentWorkflow' AND CloseTime=missing" --reason "test" --bt signal --sig testname 218 | This batch job will be operating on 5 workflows. 219 | Please confirm[Yes/No]:yes 220 | { 221 | "jobId": "", 222 | "msg": "batch job is started" 223 | } 224 | 225 | ``` 226 | 227 | 您需要记住 JobId 或使用 List 命令来获取所有批处理作业: 228 | 229 | ``` 230 | tctl --ns samples-namespace batch list 231 | ``` 232 | 233 | 描述批处理作业的进度: 234 | 235 | ``` 236 | tctl --ns samples-namespace batch desc -jid 237 | ``` 238 | 239 | 终止批处理作业: 240 | 241 | ``` 242 | tctl --ns samples-namespace batch terminate -jid 243 | ``` 244 | 245 | 注意,批处理执行的操作不会通过终止该批处理而回滚。但是,您可以使用 reset 来回滚您的工作流。 246 | 247 | #### 重新启动、重置工作流 248 | 249 | 使用“重置”命令可以将工作流重置到特定点并从那里继续运行。 250 | 251 | 有很多使用场景: 252 | 253 | - 从头开始使用相同的启动参数重新运行失败的工作流。 254 | - 从故障点重新运行失败的工作流,而不会丢失已实现的进度(历史)。 255 | - 部署新代码后,重置打开的工作流以使工作流运行到不同的流程。 256 | 257 | 您可以重置为一些预定义的事件类型: 258 | 259 | ``` 260 | ./tctl workflow reset -w -r --reset_type --reason "some_reason" 261 | ``` 262 | 263 | - FirstDecisionCompleted: 重置到历史记录的开头。 264 | - LastDecisionCompleted: 重置到历史记录的末尾。 265 | - LastContinuedAsNew: 重置到上一次运行的历史记录的末尾。 266 | 267 | 如果您熟悉 Temporal 历史事件,则还可以使用以下方法将其重置为任何决策完成事件: 268 | 269 | ``` 270 | ./tctl workflow reset -w -r --event_id --reason "some_reason" 271 | ``` 272 | 273 | 注意事项: 274 | 275 | - 重置后,将使用相同的工作流编号开始新的运行。但是如果工作流(workflowId)正在运行,则当前运行将终止。 276 | - Decision_finish_event_id 是以下类型的事件的ID:DecisionTaskComplete / DecisionTaskFailed / DecisionTaskTimeout。 277 | - 要从头开始重新启动工作流,请重置为第一个决策任务完成事件。 278 | 279 | 要重置多个工作流,可以使用批重置命令: 280 | 281 | ``` 282 | ./tctl workflow reset-batch --input_file --reset_type --reason "some_reason" 283 | ``` 284 | 285 | #### 从不良部署中恢复-自动重置工作流 286 | 287 | 如果不良部署使工作流进入错误状态,则您可能需要将工作流重置为不良部署开始运行的时刻。但是通常很难找出所有受影响的工作流以及每个工作流的每个重置点。在这种情况下,自动重置将在给定错误的部署标识符的情况下自动重置所有工作流。 288 | 289 | 让我们熟悉一些概念。每个部署都有一个标识符,我们称其为“ **Binary Checksum** ”,因为它通常是由二进制文件的md5sum生成的。对于一个工作流,每个二进制的校验和将关联一个**自动复位点**,其中包含一个 **runid**,一个 **eventID **和二进制/部署生成工作流中的第一个决定的**创建时间**。 290 | 291 | 为了找出要重置不良部署的**二进制校验和**,您应该知道至少有一个工作流处于不良状态。使用带**--reset_points_only **选项的 describe 命令可显示所有重置点: 292 | 293 | ``` 294 | ./tctl wf desc -w --reset_points_only 295 | +----------------------------------+--------------------------------+--------------------------------------+---------+ 296 | | BINARY CHECKSUM | CREATE TIME | RUNID | EVENTID | 297 | +----------------------------------+--------------------------------+--------------------------------------+---------+ 298 | | c84c5afa552613a83294793f4e664a7f | 2019-05-24 10:01:00.398455019 | 2dd29ab7-2dd8-4668-83e0-89cae261cfb1 | 4 | 299 | | aae748fdc557a3f873adbe1dd066713f | 2019-05-24 11:01:00.067691445 | d42d21b8-2adb-4313-b069-3837d44d6ce6 | 4 | 300 | ... 301 | ... 302 | ``` 303 | 304 | 然后使用此命令告诉 Temporal 自动重置受不良部署影响的所有工作流。该命令会将错误的二进制校验和存储到命名空间信息中,并触发重置所有工作流的进程。 305 | 306 | ``` 307 | ./tctl --ns namespace update --add_bad_binary aae748fdc557a3f873adbe1dd066713f --reason "rollback bad deployment" 308 | ``` 309 | 310 | 在将错误的二进制校验和添加到命名空间时,Temporal 将不会将任何决策任务分派到错误的二进制文件。因此,请确保您已回滚到良好的部署(或使用错误修复来推出新的组件)。否则,自动重置后您的工作流将无法取得任何进展。 311 | 312 | #### 安全连接到 Temporal 集群 313 | 314 | `tctl` 支持可选的Transport Level Security(TLS),用于与 Temporal 安全通信,服务器身份验证和客户端身份验证(相互TLS)。 315 | 316 | `--tls_ca_path=`命令行参数为`tctl`正在连接的验证服务器传递证书颁发机构(CA)证书。 317 | 318 | `--tls_cert_path=`命令行参数为服务器传递证书以验证客户端(`tctl`)身份。同时`--tls_key_path`也需提供。 319 | 320 | `--tls_key_path=`传递用于与服务器安全通信的私钥的命令行参数。同时`--tls_key_path`也需提供。 321 | 322 | TLS命令行参数可以通过各自的环境变量提供,以缩短命令行。 -------------------------------------------------------------------------------- /docs/workflows.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: workflows 3 | title: Workflows 4 | sidebar_label: Workflows 5 | description: Temporal core abstraction is a fault-oblivious stateful Workflow. The state of the Workflow code, including local variables and threads it creates, is immune to process and Temporal service failures. 6 | --- 7 | 8 | ## 概述 9 | 10 | Temporal 的核心抽象是一个**故障无感知的有状态的工作流**。工作流代码的状态,包括它创建的局部变量和线程在内,都不受进程和 Temporal 服务故障的影响。这是一个非常强大的概念,因为它封装了状态、工作线程、持久化计时器和事件处理程序。 11 | 12 | ## 例子 13 | 14 | 让我们看一个用例。客户注册有试用期的应用程序。在此试用期之后,如果客户尚未取消,则应每月向他收取一次续订费用。必须通过电子邮件通知客户有关费用,并且应该能够随时取消订阅。 15 | 16 | 这个用例的业务逻辑不是很复杂,可以用几十行代码来表示。但是任何实际的实现都必须确保业务流程是容错的且可扩展的。设计这种系统的方法有很多种。 17 | 18 | 一种方法是围绕数据库来展开。应用程序进程将定期扫描数据库表中处于特定状态的客户,执行必要的操作,并更新其状态以反映该状态。尽管这个方法是可行的,但是该方法具有很多缺点。最明显的是客户状态的状态机很快会变得极为复杂。例如从信用卡充值或发送电子邮件可能会由于下游系统不可用而失败。失败的调用可能需要长时间的重试,同时最好使用指数增长的重试策略。调用频率应该被限制以免外部系统过载。如果由于某种原因而无法处理单个客户的记录,则应该支持放弃任务以避免阻塞整个过程。基于数据库的方法通常也存在性能问题。 19 | 20 | 另一种常用的方法是使用计时器服务和队列。任何更新都将推送到队列,然后从中消费数据的 Worker 将更新数据库,并可能在下游队列中推送更多消息。对于需要调度的操作可以使用外部计时器服务。这种方法通常有更好的可扩展性,因为数据库不会不断地轮询更改。但这使编程模型更加复杂且容易出错,因为通常在队列系统和数据库之间没有事务级别的更新。 21 | 22 | 使用 Temporal 可以将整个逻辑封装在一个简单的持久化函数中,该函数直接实现业务逻辑。由于该函数是有状态的,因此实现者无需使用任何其他系统来确保耐用性和容错能力。 23 | 24 | 这是实现订阅管理用例的示例工作流。它是 Java 语言编写的,但也支持 Go。Python 和 .NET 库正在积极开发中。 25 | 26 | ``` java 27 | public interface SubscriptionWorkflow { 28 | @WorkflowMethod 29 | void execute(String customerId); 30 | } 31 | 32 | public class SubscriptionWorkflowImpl implements SubscriptionWorkflow { 33 | 34 | private final SubscriptionActivities activities = 35 | Workflow.newActivityStub(SubscriptionActivities.class); 36 | 37 | @Override 38 | public void execute(String customerId) { 39 | activities.sendWelcomeEmail(customerId); 40 | try { 41 | boolean trialPeriod = true; 42 | while (true) { 43 | Workflow.sleep(Duration.ofDays(30)); 44 | activities.chargeMonthlyFee(customerId); 45 | if (trialPeriod) { 46 | activities.sendEndOfTrialEmail(customerId); 47 | trialPeriod = false; 48 | } else { 49 | activities.sendMonthlyChargeEmail(customerId); 50 | } 51 | } 52 | } catch (CancellationException e) { 53 | activities.processSubscriptionCancellation(customerId); 54 | activities.sendSorryToSeeYouGoEmail(customerId); 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | 再次注意,该代码直接实现了业务逻辑。即使任何其调用的操作(又称活动)会花费很长的时间,代码也是一样的。如果下游处理服务中断了一天,在`chargeMonthlyFee`处阻塞那么长时间也是没问题的。同样,在工作流代码中阻塞睡眠30天也是正常操作。 61 | 62 | Temporal 实际上对打开的工作流实例的数量没有可伸缩性限制。因此即使您的站点有数亿消费者,上述代码也同样适用。 63 | 64 | 开发人员在学习 Temporal 时提出的常见问题是“如何处理我的工作流中的 Worker 进程失败/重新启动”?答案是你不需要。**工作流代码完全不关心 Worker 的任何故障和停机时间,甚至都不关心 Temporal 服务本身**。一旦它们被恢复并且工作流需要处理某些事件(例如计时器或活动完成),工作流就会完全恢复当时的状态并且继续执行。工作流失败的唯一原因是工作流业务代码引发异常,而不是基础架构宕机。 65 | 66 | 另一个常见问题是 Worker 是否可以处理比其缓存大小或支持的线程数更多的工作流实例。答案是处于阻塞状态的工作流可以安全地从 Worker 中删除。之后如果需要(以外部事件的形式)时,可以在其他或相同的 Worker 上重新调用它。因此如果一个 Worker 性能足够,则它可以处理数百万个打开的工作流。 67 | 68 | ## 状态恢复与决定论 69 | 70 | 工作流状态恢复利用了事件源,它对代码的编写方式施加了一些限制。主要限制是工作流代码必须是确定性的,这意味着如果多次执行必须产生完全相同的结果。这会排除工作流代码中的所有外部 API 调用,因为外部调用可能会间歇性失败或随时更改其输出。这就是为什么与外界的所有交流都应该通过活动进行的原因。出于相同的原因,工作流代码必须使用 Temporal API 来获取当前时间、睡眠和创建新线程。 71 | 72 | 要了解 Temporal 执行模型以及恢复机制,请观看以下视频。涵盖恢复部分的动画从 15:50 开始。 73 | 74 | [![](https://res.cloudinary.com/marcomontalbano/image/upload/v1603718194/video_to_markdown/images/youtube--qce_AqCkFys-c05b58ac6eb4c4700831b2b3070cd403.jpg)](https://www.youtube.com/embed/qce_AqCkFys "") 75 | 76 | ## ID 唯一性 77 | 78 | 启动工作流时,工作流 ID 由客户端分配。通常是业务级别 ID,例如客户 ID 或订单 ID。 79 | 80 | Temporal 保证在任何时间每个[命名空间](https://docs.temporal.io/docs/learn-glossary/#namespace)只能打开一个具有给定 ID 的工作流(所有工作流类型)。尝试启动使用相同 ID 的工作流将失败,并返回`WorkflowExecutionAlreadyStarted`错误。 81 | 82 | 如果尝试启动存在具有相同 ID 的完整工作流,则取决于以下`WorkflowIdReusePolicy`选项: 83 | 84 | - `AllowDuplicateFailedOnly` 表示仅当先前执行的具有相同 ID 的工作流失败时,才允许启动工作流。 85 | - `AllowDuplicate` 表示允许它不依赖于先前的工作流完成状态而启动。 86 | - `RejectDuplicate` 表示完全不允许使用相同的工作流 ID 启动工作流执行。 87 | 88 | 默认值为`AllowDuplicateFailedOnly`。 89 | 90 | 为了区分具有相同工作流ID的工作流的多次运行,Temporal 通过两个 ID 标识工作流:`Workflow Id` 和 `Run Id`。`Run Id`是服务分配的 UUID。确切地说,任何工作流由一个三元组:`Namespace`,`Workflow Id`和`Run Id` 来唯一标识。 91 | 92 | ## 子工作流 93 | 94 | 一个工作流可以将其他工作流作为子工作流`child workflows`执行。子级工作流完成或失败会报告给其父工作流。 95 | 96 | 使用子工作流的一些原因是: 97 | 98 | - 子工作流可以由不包含父工作流代码的一组单独的 Worker 托管。因此,它将充当可以从多个其他工作流中调用的单独服务。 99 | - 单个工作流的大小有限。例如它不能执行10万个活动。子工作流可用于将问题分成较小的块。父工作流中有 1000 个子工作流,每个子工作流执行 1000 个活动,就是一百万个执行的活动。 100 | - 子工作流可以使用其 ID 来管理某些资源,以保证唯一性。例如,管理主机升级的工作流可以每个主机有一个子工作流(主机名是工作流 ID),并使用它们来确保主机上的所有操作都是序列化的。 101 | - 子工作流可用于执行某些定期逻辑,而不会增加父工作流历史记录的大小。当父工作流启动一个子工作流时,它会执行周期性的逻辑调用,该逻辑调用根据需要连续执行多次直至完成。如果从父工作流视角来看,它只是一个子工作流调用。 102 | 103 | 与在单个工作流中囊括所有应用程序逻辑相比,子工作流的主要限制是缺少共享的状态。父工作流和子工作流只能通过异步信号进行通信。但是如果它们之间存在紧密的耦合,那么使用单个工作流并仅依赖共享对象状态可能会更简单。 104 | 105 | 如果您的问题在已执行活动和已处理信号的数量方面受到限制,我们建议从实现单个工作流开始。它比多个异步通信的工作流更直接。 106 | 107 | ## 流程超时 108 | 109 | 通常情况下,限制特定工作流可以运行的时间是很有必要的。为此,工作流提供以下三个参数作为选项: 110 | 111 | - `WorkflowExecutionTimeout`允许工作流运行的最长时间,包括重试并作为新的工作流运行。使用`WorkflowRunTimeout`来限制单次运行的执行时间。 112 | - `WorkflowRunTimeout` 允许单个工作流运行的最长时间。 113 | - `WorkflowTaskTimeout`从 Worker 拉出任务的时间点开始处理工作流任务的超时时间。如果决定任务丢失,则会在此超时后重试。 114 | 115 | ## 工作流重试 116 | 117 | 工作流代码不受基础架构层次的停机时间和故障影响。但是它仍然可能由于业务逻辑层次的故障而失败。例如,活动可能由于超过重试间隔而失败,并且该错误无法由应用程序代码处理,或者工作流代码具有错误。 118 | 119 | 某些工作流需要保证即使出现此类故障也可以继续运行。为了支持此类用例,可以在启动工作流时指定可选的指数*重试策略*。如果指定了此选项,则工作流失败后将在计算的重试间隔后从头开始重新启动工作流。以下是重试策略参数: 120 | 121 | - `InitialInterval` 第一次重试的退避间隔。 122 | - `BackoffCoefficient`重试策略是指数的。该系数指定重试间隔的增长速度。系数1表示重试间隔始终等于`InitialInterval`。 123 | - `MaximumInterval`指定重试之间的最大间隔。对于大于1的系数有用。 124 | - `MaximumAttempts`指定在出现故障时尝试执行工作流的次数。如果超过此限制,则工作流将失败,并且不会重试。 125 | - `NonRetryableErrorReasons`允许指定不应重试的错误。例如,在某些情况下,对无效参数错误重试没有任何意义。 -------------------------------------------------------------------------------- /img/docs/apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/apps.png -------------------------------------------------------------------------------- /img/docs/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/block.png -------------------------------------------------------------------------------- /img/docs/boost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/boost.png -------------------------------------------------------------------------------- /img/docs/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/check.png -------------------------------------------------------------------------------- /img/docs/confetti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/confetti.png -------------------------------------------------------------------------------- /img/docs/harbor-crane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/harbor-crane.png -------------------------------------------------------------------------------- /img/docs/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/hello.png -------------------------------------------------------------------------------- /img/docs/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/one.png -------------------------------------------------------------------------------- /img/docs/repair-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/repair-tools.png -------------------------------------------------------------------------------- /img/docs/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/running.png -------------------------------------------------------------------------------- /img/docs/system-architecture-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/system-architecture-2.png -------------------------------------------------------------------------------- /img/docs/system-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/system-architecture.png -------------------------------------------------------------------------------- /img/docs/temporal-high-level-application-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/temporal-high-level-application-design.png -------------------------------------------------------------------------------- /img/docs/temporal-server-and-sdk-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/temporal-server-and-sdk-icons.png -------------------------------------------------------------------------------- /img/docs/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/three.png -------------------------------------------------------------------------------- /img/docs/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/two.png -------------------------------------------------------------------------------- /img/docs/use-this-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/use-this-template.png -------------------------------------------------------------------------------- /img/docs/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/warning.png -------------------------------------------------------------------------------- /img/docs/web-ui-activity-error-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/web-ui-activity-error-info.png -------------------------------------------------------------------------------- /img/docs/wisdom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/wisdom.png -------------------------------------------------------------------------------- /img/docs/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/docs/workflow.png -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/favicon.ico -------------------------------------------------------------------------------- /img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/favicon.png -------------------------------------------------------------------------------- /img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/readme/forkrepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/readme/forkrepo.png -------------------------------------------------------------------------------- /img/readme/netlifypreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/readme/netlifypreview.png -------------------------------------------------------------------------------- /img/undraw_docusaurus_mountain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /img/undraw_docusaurus_react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | docu_tree -------------------------------------------------------------------------------- /img/v1-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/v1-pipeline.png -------------------------------------------------------------------------------- /img/workflow-meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linvon/temporal-doc-CN/70aed6524396b4e9a2e1ff93a0f7a21696397eed/img/workflow-meme.png --------------------------------------------------------------------------------