uber/cadence:Cadence是一种分布式,可扩展,持久且高度可用的流程编排引擎

19-09-13 banq
              

定位类似zeebe、activiti之类工作流引擎,高级别的区别在于zeebe是基于BPMN的,Cadence支持使用Java或Go等普通编程语言编写编排代码。大多数复杂程序都不是使用可视化编程编写的。它只是不适用于复杂的用例。学习Cadence编程也更简单,因为您只需要学习一些编写Java(或其他)代码的规则。BPMN更具限制性和复杂性。

系统架构方式存在很多技术差异。Cadence依赖于分区数据库。目前支持MySQL和Cassandra,未来还会有更多支持。Zeebe使用 EventSourcing,写入吞吐量更大。

Cadence文档很晦涩难懂,这也是Uber大裁员的一个原因吧,不使用业内通用模式和架构,自己创建一套,但是需要首先说明自己这套相比通用的有什么缺点和优点,直接上来阐述功能,怪不得被人比喻成类似奥斯卡颁奖礼仪。

它的大概原理是:

业务逻辑被建模为“工作流workflow”和“活动activities”。“工作流workflow”是业务协调逻辑的实现,其唯一目的是协调“活动activities”执行。“活动activities”是用于实现业务逻辑的任务。

“工作流workflow”和“活动activities”的实现是在工作进程中托管和执行。这些工作者长期轮询Cadence服务器以执行任务,通过调用“工作流workflow”和“活动activities”实现来执行任务,并将任务结果返回给Cadence服务器。此外,工作人员可以实现为完全无状态的服务,这反过来允许无限制的水平扩展。

有的企业已经开始应用Cadence了,他们的体验如下:

我们已经将Cadence投入生产近一年了。我们将其用于云提供商的基础架构配置。

第一个POC在两周内准备就绪,再过两个星期我们将其推向生产阶段。最初的学习曲线相当高,因为Cadence的构建工作流程起初并不一定容易(当时我们开始时没有太多的文档),但之后它更容易。

相关博客:https://banzaicloud.com/tags/cadence/

市场上有各种不同的任务队列(我不是在开玩笑,你可以自己在taskqueues.com上查看它们),那么是什么让Cadence出类拔萃呢?是什么让它成为一个复杂的分布式任务队列管理器?首先,您可以在代码中定义工作流程,让Cadence处理状态,超时,历史记录和所有其他必需品。然后,可以在提交后检查这些工作流程,以便您可以检查其进度和结果,或根据需要向其发送外部信号。

Matching Service是负责分派任务,保证了at-most-once语义,这意味着工作流的每个活动要么只执行一次,要么(在失败的情况下)根本不执行。

History Service是神奇魔力所在。它管理队列,处理事件,存储和改变工作流状态。

您必须编写的代码:

  • 定义”工作流workflow”并执行“活动activities”的实际任务的工作者worker,
  • 用于提交工作流程执行请求的代码,以及
  • 可选择检查”工作流workflow”进度或结果。

与Activities工作流程的可重用构建块(保证最多运行一次)不同,”工作流workflow”代码会重复执行多次。虽然您可以自由地按照自己喜欢的方式组织工作流代码,并且可以安全地使用Go的控制结构和纯函数(即没有副作用的那些),但必须确保您的”工作流workflow”具有确定性。要实现这一点,您应该使用Cadence提供的特殊功能,而不是具有副作用的构造或依赖于某些外部状态的构造,如数据库,熵源或挂钟时间。我们更喜欢将大多数变量重新定位为工作流参数,而不是在执行期间查询它们。

不要忘记,对于“活动activities”没有这些的限制,可以在运行较长时间的程序中使用适当的Go构造。当然,您不应泄漏资源或在代码中启动无人参与的Go例程。另一方面,您必须在自己的工作流代码中处理失败和重新执行活动,这在幂等活动中通常更容易。

在Cadence”工作流workflow”中执行“活动activities”

// Synchronous activity execution
workflow.ExecuteActivity(ctx, sendEmailActivity).Get(ctx, &result)

// Asynchronous activity execution
future := workflow.ExecuteActivity(ctx, sendEmailActivity)

运行Go例程

// Cadence
workflow.Go(ctx, func(ctx workflow.Context) {/* … */})

// Plain Go
go func(ctx context.Context) {/* … */}(ctx)

创建channel,发送和接收数据

// Cadence
channel := workflow.NewChannel(ctx)
channel.Send(ctx, true)
channel.Receive(ctx, &result)

// Plain Go
channel := make(chan interface{})
channel <- true
result := <-channel

创建可取消的上下文:

// Cadence
childCtx, cancelHandler := workflow.WithCancel(ctx)

// Plain Go
childCtx, cancelHandler := context.WithCancel(ctx)

计时器:

// Cadence
workflow.NewTimer(childCtx, processingTimeThreshold)

// Plain Go
timer.NewTimer(processingTimeThreshold)

sleep:

// Cadence
workflow.Sleep(sleepTime)

// Plain Go
time.Sleep(sleepTime)

等待完成一项行动:

// Cadence
selector := workflow.NewSelector(ctx)
selector.AddFuture(future, handle)
selector.Select(ctx)

// Plain Go
select {    
  case <-ready: handle()        
  case <-ctx.Done():
}

在K8s上可以安装Cadence,见博客文档,点击标题进入原文