跳到主要内容

Channel

查看 Markdown

Channel 是单个 Flow run 内部一条 durable 的 first-in, first-out (FIFO) 队列。Step 和 RPC 向它发布有类型的消息。Step 在 WaitFor 里等待这个 Channel,从而消费消息。消息不会跨 Flow run。

什么时候用 Channel​

Channel 适合 Step 要等待的事件,例如一次审批、用户回复或新的工作项。会被覆盖、多次读取的状态,用 Attribute。可以丢失的实时进度,用 Stream。

要等待并行分支完成,用 Join。Join 建在 ChannelMap 之上,它会替你数分支,并区分每一轮。见 等待并行 Step。

保证​

  • Dex 把每条消息和 Flow 一起 durable 存储。
  • 消息保持到达顺序。
  • 一条消息由一个 wait 消费。Dex 不会把它复制给其他等待中的 Step。
  • Step 的 wait 消费了消息之后、Dex 保存它的 Execute 结果之前,Step 可能被取消。这时 Dex 把这些消息按原来的顺序放回 Channel 的最前面。之后的 wait 可以再消费它们。
  • 被取消的 Execute 可能已经看到过放回的消息。所以一条消息可能两次到达 Execute。消息处理要做成幂等的。
  • 如果 Step 是因为 Flow 关闭而被取消的,Dex 会丢弃这些消息,不再放回。StopFlow 请求、Flow timeout,以及用 ForceComplete 或 ForceFail 关闭 Flow 的决定,都属于这种情况。
  • 其他取消都会放回消息,即使 Flow 之后关闭,例如在 GracefulComplete 之后。Flow 关闭后,不会再有 Step 消费它们。
  • 一个 Step 方法取消某个 Step、同时发布消息时,被取消的 Step 不会收到这些消息。
  • 多个 Step 等待同一个 Channel 时,最先开始等待的 Step 最先有机会拿到新消息。如果一个 Step 的 wait 需要的消息比待处理的多,它会被跳过,之后的 Step 可以拿走这些消息。
  • 所以当其他 Step 一条一条地拿走消息时,等待多条消息的 Step 可能永远等不到。在同一个 Channel 上,不要同时有等待一条消息的 Step 和等待多条消息的 Step。
  • 消息可以在 Step 开始等待之前到达。Dex 会保留它,直到之后的 wait 消费它。
  • 只有发布消息的 Step 方法或 RPC 成功时,Dex 才会发布这条消息。见 Step 的保证。

定义、发布和等待​

Channel 有稳定的名字和消息类型。先定义它,再加到 Flow 的 persistence schema。

在这个例子里,start Step 等待 ApprovalMessages Channel 上的一条消息,或者一个 Timer。publishApprovalMessage RPC 发布审批消息。

channel_flow.pyPython
class ChannelWait(Step[int]):
def __init__(
self,
approval_messages: Channel[str],
queued_messages: Channel[str],
) -> None:
self.approval_messages = approval_messages
self.queued_messages = queued_messages

def get_step_options(self) -> StepOptions:
return StepOptions(execute_load_channels=(self.queued_messages,))

def wait_for(self, context: Context, input: int) -> Wait:
return Wait.any_of(
self.approval_messages.for_one(),
Timer.by_duration(timedelta(seconds=input)),
)

def execute(self, context: Context, input: int) -> StepDecision:
pending_queued_messages = self.queued_messages.pending_messages(context)
if pending_queued_messages:
self.queued_messages.delete(context, pending_queued_messages[0].message_id)
return graceful_complete(pending_queued_messages[0].value)
if context.has_timer_fired():
return graceful_complete("approval timed out")
approval_message_values = self.approval_messages.results(context)
return graceful_complete(approval_message_values[0])

class ChannelFlow(Flow[int]):
approval_messages = Channel("ApprovalMessages", str)

@rpc
def publish_approval_message(self, context: Context) -> None:
self.approval_messages.publish(context, "approved")

例子: examples/python/dex_examples/primitives/channel/channel_flow.py

如果第二个 Channel QueuedMessages 上有 RPC 放入的待处理消息,Execute 会先取走它,见管理待处理消息。Timer 分支在读取 Channel 结果之前返回。审批胜出时,Channel condition 已经消费了一条消息,所以第一个结果一定存在。完整示例还会在 Step options 中加载 QueuedMessages,在 persistence schema 中注册这些 Channel,并注册 RPC。

等待并消费​

WaitFor 中的 Channel condition 会等到队列里有足够的消息。然后 Dex 按 FIFO 顺序移除选中的消息,作为 Channel results 传给 Execute。

Condition何时继续消费什么
ForOne至少有一条消息在队列中。正好一条消息。
ForN队列中至少有请求的数量。正好这个数量的消息。
AtLeast队列中至少有请求的数量。当前所有可用消息。
AtMost立即继续,队列为空时也一样。当前队列中最多请求数量的消息。
AtLeastAtMost队列中至少有下限数量。当前队列中从下限到上限数量的消息。

发布消息​

在 Step 或 RPC 里,用 handler 的 Context 发布。Worker 之外的代码通过调用 Flow 的 RPC 来发布。这样校验、授权和相关的状态变更都留在 Flow 内。

管理待处理消息​

Dex 接受每条消息时会给它一个 ID。Step 或 RPC 可以加载待处理消息,按 FIFO 顺序读取,并按 ID 删除其中一条。读取不会消费消息。handler 运行时,其他 Step 和 RPC 可能改变队列,所以加载到的列表可能已经过时。

在 Step 或非 transactional RPC 中,删除一条已经不在的消息不会有任何效果。transactional RPC 则会失败,Dex 不保存它的任何修改。从 dexcli 或 Dex Web 直接删除时,会返回 not-found 错误。见 Pending messages。

ChannelMap​

一个 Flow 需要很多独立队列时,例如每个订单、客户或任务一条,用 ChannelMap。ChannelMap 有一个名字和一种消息类型。每个 instance key 有自己的 FIFO 队列。

参考​

每个 SDK 参考的 Channel 页列出该语言的 API 名称。

Data primitive semantics 详细说明 wait condition、待处理消息、删除、状态加载和 ChannelMap key。