Restate:支持JavaScript/Java的Rust低延迟持久工作流


Restate:使用持久的异步/等待以容错方式轻松构建工作流、事件驱动的应用程序和分布式服务。

  • 带有 JS/Java/Kotlin 中的 SDK
  • 内置于 Rust/Tokio 中的轻量级运行时。

它是免费且开放的,SDK 是 MIT 许可的,运行时是 BSL

Restate擅长构建:

核心
Restate 为简化应用程序开发提供的基本原语如下:

  • 可靠执行:用户代码将始终运行至完成。中间失败会导致重试,使用持久执行机制来恢复部分进度,而不会重复已执行的步骤。
  • 暂停用户代码:长时间运行的用户代码在等待承诺时暂停,并在该承诺得到解决时恢复。
  • 可靠通信:用户代码以精确一次的语义进行通信。Restate 可靠地传递消息,并将发送者和接收者锚定在持久执行中,以确保不会发生丢失或重复。
  • 持久定时器:用户代码可以休眠(和暂停)或者安排稍后的调用。
  • 隔离性:用户代码可以被键入,这使得 Restate 安排它们遵循每个键单写入器的语义。
  • 一致状态:键控用户代码可以附加键/值状态,该状态在调用期间被急切地推送到处理程序中,并在完成后写回。这对于 FaaS 部署特别有效(有状态无服务器,耶!)。
  • 可观察性和自省:Restate 会自动生成处理程序之间交互的开放遥测跟踪,并为您提供 SQL shell 来查询应用程序的分布式状态。

特征:

  • API 的核心设计目标是保持熟悉的风格。应用开发人员应该查看 Restate 示例并说“嘿,这看起来很熟悉”。
  • 基本上每个操作(处理程序调用、步骤等)都经过共识层,以实现高度的弹性和一致性。
  • Restate 的运行时是一个独立的二进制文件,除了持久磁盘外没有其他依赖项。它基本上包含持久日志、工作流状态机、状态存储等的轻量级集成版本。这使得它非常紧凑,并且易于在笔记本电脑和服务器上运行。
  • Restate 不仅为工作流实现持久执行,其核心构建块是持久 RPC 处理程序(或事件处理程序)。它在持久执行的基础上添加了一些概念,例如虚拟对象(将 RPC 处理程序转变为虚拟参与者)、持久通信和持久承诺。更多详细信息请参见:https://restate.dev/programming-model
  • 轻量级以日志为中心的架构使 Restate 仍然具有良好的延迟:例如,对于 3 步持久工作流处理程序(在 EBS 上对每一步进行 fsync 重述),往返(从调用到结果)大约需要 50ms。



网友讨论
1、作者是 Apache Flink 的原始创建者之一

2、作者swwen:
Flink Stateful Functions 是我们首次尝试构建一个系统来满足我们目前的用例。具体来说,在 Virtual Objects 中,你可以看到这一遗产。
借助 Stateful Functions,我们很快意识到我们需要为事务构建一些东西,而 Flink 是为分析构建的。
这体现在很多方面,可能最明显的是延迟:事务持久性在 Flink(检查点间隔)中需要几秒钟,而在 Restate 中则需要几毫秒。
此外,我们可以为 Restate 提供一个非常不同的开发环境,使其更兼容现代应用程序开发。Flink 来自数据工程方面,具有非常不同的集成、工具等。

3、此类工具如何处理不断变化的工作流程?例如,如果我有一个“持久工作流程”,它会休眠一个月,然后执行其下一个操作,如果我需要在该月内更改工作流程,我该怎么办?我真的很喜欢这个概念,但这似乎是除了相当短的工作流程之外的任何问题。如果我将数据和算法分开,我可以在工作流程处于“活动”状态时修改事件处理代码。

关键要点:

  1.  不可变代码平台(如 Lambda)使事情变得更加容易处理: 旧代码“只要您的处理程序运行”即可执行,这是您所需要的属性。这也可以在 Kubernetes 中使用一些巧妙的控制器来实现
  2.  通过这种方式延迟 RPC 和跨度时间的能力,您可以让处理程序运行时间非常短,但执行时间却非常长。这​​比在循环中反复休眠要好得多 - 相反,您可以执行延迟尾部调用。

4、您能否分享设计工作流程时需要注意的限制的详细信息?我希望能够一目了然地参考以下几点:

  1. 工作流的最大执行时长
  2.  服务调用的最大输入/输出有效负载大小(以字节为单位)
  3.  服务调用的最大超时时间
  4. 工作流中允许的最大状态转换次数
  5.  最大日志历史记录保留时间

答:
  1. Restate 工作流没有最大执行时长。使用 Restate,工作流只能运行几秒钟或跨越数月。对于长期运行的工作流,需要记住的一件事是,您可能必须在其生命周期内改进代码。这就是为什么我们建议将它们编写为延迟尾部调用序列(https://news.ycombinator.com/item?id=40659687
  2.  Restate 目前默认不严格限制输入/输出消息的大小(但它可以选择限制大小以保护系统)。不过,建议不要过分强调输入/输出大小,因为 Restate 需要将输入消息发送到服务端点才能调用它。因此,输入/输出大小越大,调用服务处理程序并将结果发送回用户所需的时间就越长(增加延迟)。目前,只要消息超过 10 MB,我们就会发出软警告。
  3.  如果用户没有为其对 Restate 的调用指定超时,则系统不会超时。当然,对于长时间运行的调用,可能会发生外部客户端失败或其连接中断的情况。在这种情况下,Restate 允许重新连接到正在进行的调用或检索其结果(如果在此期间完成)。
  4.  Restate 对工作流状态转换的最大次数没有限制。
  5.  只要调用/工作流正在进行,Restate 就会保留日志历史记录。一旦工作流完成,我们将删除日志,但会保留完成的结果 24 小时。

补充:
您可以在 Restate 中存储大量数据(工作流事件、步骤)。记录的事件会快速移动到嵌入式 RocksDB,该数据库在每个节点上都具有很高的可扩展性。该架构是分区的,虽然我们尚未完成所有多节点功能,但内部的所有内容都是以分区可扩展的方式构建的。

因此,问题不在于系统能做什么,而在于你想要什么:

  • - 如果您保存了数万个日记条目,重播可能需要一些时间。(附注:您也不需要这样做,Restate 对显式状态的支持为您提供了一种直观的替代方案,可以替代其他一些系统所推广的“永远运行无限日记”工作流模式。)
  • - 默认情况下,工作流的执行持续时间不受限制。更大的问题是,您希望将旧版本的业务逻辑实例保留多长时间?
  • - 历史记录保留(我们目前仅针对“工作流”类型的任务进行此操作),保留的量取决于您愿意为存储投入多少。RocksDB 非常适合让旧数据沿 LSM 树流动,而不会造成阻碍。

我们希望得到一些关于制定最佳默认设置的建议,因此我们很乐意在 Discord 上进行更多交流:https://discord.gg/skW3AZ6uGd

我认为我们唯一需要(并且已经)硬性限制的是消息大小,因为如果您有许多处理程序处理非常大的消息,这可能会对系统稳定性产生不利影响。这最终将需要一个功能,例如用于大消息的带外传输(例如,通过 S3)。

4、我不确定“In Rust”是否有任何营销价值。产品的成功很少与编程语言的使用有关,甚至根本没有关系。我理解 Paul Graham 关于编程语言有效性的论点,但对于工作流管理器而言,像我这样的用户根本不关心工作流系统使用哪种编程语言,即使我必须侵入系统内部,延迟实际上也比吞吐量重要得多。

答:最近我花了很多时间编写 Rust,这对我来说是一个很大的缺点。
对于并发来说,这是一种糟糕的语言,并且传递依赖性可能会导致你经常无法恢复的恐慌。
这意味着整个生态系统就像坐在一颗即将爆炸的旧炸药上。
JVM 确实已经证明自己是迄今为止高并发后端应用程序的最佳选择。

5、我还没有开始采用 Restate,但它已经在考虑中。Step Functions 可能比 Restate 更胜一筹的一点是,它可以将状态机定义和执行历史可视化。能够在概念层面而不是实施层面找到根本原因,这真的很棒。

Step Functions是无服务器编排层,这是 Step Functions 最吸引人的部分之一。我们有许多大型工作流程,每天只运行几次,需要几个小时,还有一些短工作流程,运行时间不到一分钟,在高峰时段执行更频繁。即使经过多次状态转换,Step Functions 最终也非常具有成本效益,因为大多数时候,编排器处于空闲状态。

6、看起来真的很棒。一直在寻找一些易于使用的异步工作流 + cronjobs 服务,以便与 Vercel 等无服务器一起使用。

7、我找不到与temporal 编解码器服务器类似的概念,temporal 会加密事件日志中的所有数据。
目前,Restate 不支持此功能。由于 Restate 不需要访问输入/输出消息或状态(它将其作为字节发送到服务端点),因此您可以添加自己的客户端加密机制。在可预见的未来,Restate 可能会为其添加更集成的解决方案。

8、Restate 与temporal 比较:

  • (1) 据我所知,Restate 的延迟是 Temporal 无法实现的。Restate 的延迟较低,因为 (a) 它的事件日志架构和 (b) Restate 不需要为活动生成任务,而是调用 RPC 处理程序。
  • (2) Restate 与 FaaS 配合得非常好。FaaS 本质上需要一个“推送事件”模型,这正是 Restate 所做的(推送事件、调用处理程序)。如果我没记错的话,Temporal 有一个拉取任务的工作者模型,而拉取模型对 FaaS 来说并不好。Restate + AWS Lambda 实际上是一个很棒的任务队列,您可以超快速地提交任务,并且可以几乎无限地自动扩展其工作者(Lambda)。
  • (3) Restate 是一个独立的二进制文件,你下载并启动它就大功告成了。我认为这与大多数系统(不仅仅是 Temporal)的体验截然不同。为什么应用程序开发人员如此喜欢 Redis,尽管它的耐用性值得商榷?我认为这是他们喜欢的超轻量级方式,而这正是我们想要复制的(尽管具有适当的耐用性)。
  • (4) 也许最重要的是,Restate 的功能远不止工作流。你可以将它仅用于工作流,但也可以实现持久通信(恰好一个 RPC)、以参与者风格的方式维护状态(通过虚拟对象)或从 Kafka 提取事件的服务。

这可能不是您构建的第一件事,但它向您展示了如果您愿意的话您可以走多远:它是一个完整的应用程序,具有许多服务、工作流、数字孪生,其中一些连接到 Kafka。https ://github.com/restatedev/examples/tree/main/end-to-end-...

所有执行和通信都是异步、持久、可靠的。我认为使用 Temporal 构建这种应用程序非常困难,如果你构建它,你可能会使用一些非常奇怪的信号怪癖,例如在构建数字孪生的状态维护时,这不会让其他任何应用程序开发人员觉得非常直观。

9、Restate自带数据层的理由:

倾向于考虑在状态机中构建服务:每个重要步骤都在安全的地方进行跟踪,并通过状态机进行状态转换。如果手动执行此操作,您将联系 DBMS 并在发生重要事件时明确检查您的状态。

为了实现幂等性,您最终会在代码中加入准备提交类型的步骤,首先读取存储的状态,然后在每个逻辑步骤中决定是恢复之前的部分执行还是重新开始。这很快就会过时,因此大多数代码最终可能依赖于开始时的一次幂等性检查,然后调用者重试。您还需要一个外部任务队列或某种类型的清除程序来拾取并重新驱动部分完成的执行。

像 Restate 这样完整的专用系统的优点在于,它为您提供了专为跟踪执行任务而设计的持久日志服务,并且还为您提供了一个 SDK,使您可以轻松实现“幂等块链”效果,而无需手动操作巨大的状态机。

您不必使用 Restate 来保存数据,尽管您可以这样做 - 并且您可以获得在日志记录过程中使用相同隔离属性自动提交状态更改的好处。但是,您可以轻松地将写入操作编排到外部存储(例如 RDBMS、KV、队列)中,并使用与 Restate 服务其余部分相同的保证进度语义。它的执行语义使这变得更容易、更愉快,因为您可以立即重试。

最后,值得一提的是,我们公开了一个与 PostgreSQL 协议兼容的 SQL 查询端点。这允许您查询您选择存储在 Restate 中的任何状态以及服务元数据,即反映活动调用。

10、Restate 与 Apache Airflow 或 Prefect 相比如何?

  • 一个区别是 Airflow 似乎更适合更繁重的操作,例如数据管道。相比之下,Restate 默认不会生成任何任务,但它更像是 RPC 或事件处理程序的代理/代理,并添加了持久重试、日志记录、创建持久 RPC 的能力等。这使得它非常轻量级:如果处理程序在运行的容器中速度很快,则整个过程会导致超快的周转时间(毫秒)。
  • 另一个区别在于逻辑的定义方式、可维持状态、可对其他处理程序进行一次调用。

11、令人兴奋的工具让使用微服务变得更加容易。

12、处理 RPC 的持久性是一个好主意。您可以进行链式回滚吗?即,调用堆栈下的 RPC 失败,而不是重试,而是恢复整个堆栈?
这是示例仓库中另一个执行补偿的示例。还有一个 Java 示例 https://github.com/restatedev/examples/blob/main/basics/basi...

13、Restate 杀手级功能:

  • 能够完全隐藏持久执行,让用户无法看到,方法是能够在任何时间点拍摄执行快照,然后透明地将其记录在引擎中。

现在假设存在这样的语言,并且它也可以相当快地拍摄这些快照,但确定在哪里拍摄快照在逻辑上是安全的,以及何时执行无法继续,因为您需要等待存储结果的确认,这仍然是一个很大的问题。例如,假设您有以下代码:

val 结果A = 调用A()
val 结果B = 调用B(结果A)

A 和 B 都会执行一些非确定性操作,例如,它们会向其他系统执行 HTTP 调用。假设 callB() 完成后,但在得到 HTTP 响应之前,你的代码由于某种原因崩溃了。

如果你在 callA() 和 callB() 之间没有拍摄任何快照,那么你将永远完全丢失 B 被调用 resultA 的事实,而当你下一次重新执行 A 时,它生成的结果可能与第一次生成的结果不同。

由于这个问题,你仍然需要以某种方式手动定义一些 "安全点",以便安全地拍摄这些快照。这意味着我们无法真正向用户隐藏持久执行,因为你仍然需要一些类似于 "snapshot_here "的语句来告诉引擎在哪里拍摄快照是安全的。

在我们的 Restate  SDK 中,我们有效地实现了这一点,采取的安全方法是在连续执行两个 ctx.run() 时始终等待存储确认。

14、设计灵感:
Restate 构建为分片复制状态机,类似于


的设计方式。

我们不依赖于特定的共识实现,而是决定将此部分封装到虚拟日志中(受 Delos https://www.usenix.org/system/files/osdi20-balakrishnan.pdf启发),因为这样可以更轻松地针对不同的部署场景(本地、云、经济高效的 blob 存储)调整系统。

此外,它还允许其他一些很酷的功能,例如无缝地从一种日志实现移动到另一个日志实现。

除此之外,整个系统设计还受到流处理系统(如 Apache Flink ( https://flink.apache.org/ )、日志存储系统(如 LogDevice ( https://logdevice.io/ ) )等)等思想的影响。

这是设计理念的混合体。LogDevice(免责声明,我是 LogDevice 设计师之一)和 Delos(Bifrost,我们的分布式日志设计)肯定有灵感。您可以在https://www.usenix.org/system/files/osdi20-balakrishnan.pdf 中阅读有关 Delos 的信息