为什么在 Orleans 中使用流?

可以使用现有的各种技术来构建流处理系统。 这包括持久存储流数据的系统(例如事件中心Kafka),以及表达针对流数据的计算操作的系统(例如 Azure 流分析Apache StormApache Spark 流式处理)。 这些系统非常出色,让你可以构建高效的数据流处理管道。

现有系统的局限性

但是,这些系统不适合用于针对流数据的精细自由形式计算。 上述流计算系统都允许指定以相同方式应用于所有流项的操作的统一数据流图。 如果数据是统一的,并且你希望对此数据表达相同的一组转换、筛选或聚合操作,则可以使用这个强大的模型。 但在某些用例中,需要对不同的数据项表达根本上不同的操作。 而在其中一些用例中,在进行这种处理的过程中,有时需要发出外部调用,例如调用一些任意的 REST API。 统一的数据流流处理引擎要么不支持这些方案,要么以受限、受约束的方式支持这些方案,要么对它们的支持效率低下。 这是因为,这些引擎固有地针对大量类似项进行了优化,并且通常在表达能力和处理方面受到限制。 Orleans 流面向其他某些方案。

动机

这一切都始于 Orleans 用户提出的支持从 grain 方法调用返回一系列项的请求。 可以想象,这只是冰山一角。 他们需要的远不止如此。

Orleans 流的一个典型方案是为每个用户创建流,并且在单个用户的上下文中为每个用户执行不同的处理。 我们可能有数百万用户,但其中一些用户对天气感兴趣,因而可能订阅特定地点的天气预报;而另一些用户则对体育赛事感兴趣;还有一些用户想要跟踪特定航班的状态。 处理这些事件需要不同的逻辑,但你不希望运行两个独立的流处理实例。 某些用户只对特定的股票感兴趣,并且仅限某个外部条件适用的情况,而某个条件可能不一定是流数据的一部分(因此需要在运行时进行处理期间动态检查)。

用户的兴趣会随时改变,他们会动态订阅或取消订阅特定的事件流,因此流拓扑也会动态且快速地变化。 除此之外,每个用户的处理逻辑也会根据用户状态和外部事件动态演变。 外部事件可能会修改特定用户的处理逻辑。 例如,在游戏作弊检测系统中,当发现新的作弊方式时,需要使用新规则更新处理逻辑以检测这种新的违规行为。 这当然需要在不中断正在进行的处理管道的情况下完成。 批量数据流流处理引擎不是为支持此类方案而构建的。

几乎不用说,此类系统必须在多台联网计算机上运行,而不是在单个节点上运行。 因此,处理逻辑必须以可缩放的弹性方式分布在整个服务器群集中。

新要求

我们认识了流处理系统的 4 个基本要求,满足这些要求,该系统即可用于上述方案。

  1. 灵活的流处理逻辑
  2. 支持高度动态的拓扑
  3. 精细流粒度
  4. 分发

灵活的流处理逻辑

我们希望系统支持流处理逻辑的不同表达方式。 上述现有系统要求开发人员编写声明性数据流计算图(通常按照函数编程风格)。 这限制了处理逻辑的表达能力和灵活性。 Orleans 流与处理逻辑的表达方式无关。 它可以表达为数据流(例如,通过使用 .NET 中的反应式扩展 (Rx))、函数程序、声明性查询或采用一般的命令式逻辑形式。 逻辑可以是有状态或无状态的,不一定有副作用,并且可以触发外部操作。 一切由开发人员作主。

对动态拓扑的支持

我们希望系统允许拓扑动态演变。 上述现有系统通常仅限于静态拓扑,这些拓扑在部署时是固定的,无法在运行时演变。 在以下数据流表达式示例中,在更改之前一切都很简单。

Stream.GroupBy(x=> x.key).Extract(x=>x.field).Select(x=>x+2).AverageWindow(x, 5sec).Where(x=>x > 0.8) *

更改 Where 筛选器中的阈值条件,在数据流图中添加 Select 语句或添加另一个分支,并生成新的输出流。 在现有系统中,如果不拆除整个拓扑并从头开始重启数据流,则做不到这一点。 实际上,这些系统将对现有计算创建检查点,并可以从最新检查点重启。 尽管如此,这种重启对于实时生成结果的联机服务而言是破坏性的,并且成本高昂。 当我们谈论使用相似但不同的(按用户、按设备等)且不断变化的参数执行大量此类表达式时,这种重启变得特别不切实际。

我们希望系统允许通过向计算图添加新的链接或节点,或者通过更改计算节点内的处理逻辑,在运行时演变流处理图。

精细流粒度

在现有系统中,最小的抽象单位通常是整个流(拓扑)。 但是,许多目标方案要求拓扑中的单个节点/链接本身就是一个逻辑实体。 这样,就潜在地能够独立管理每个实体。 例如,在包含多个链接的大型流拓扑中,不同的链接可以有不同的特征,并可以针对不同的物理传输实现。 某些链接可以通过 TCP 套接字,而其他链接可以通过可靠队列。 不同的链接可以有不同的交付保证。 不同的节点可以有不同的检查点策略,其处理逻辑可以用不同的模型甚至不同的语言来表达。 这种灵活性在现有系统中通常是不可能的。

抽象单位和灵活性参数类似于 SoA(面向服务的体系结构)与执行组件的比较。 执行组件系统可以实现更大的灵活性,因为每个执行组件本质上都是一个独立管理的“微小服务”。 同样,我们希望流系统允许这种精细控制。

分发

当然,我们的系统应该具备“良好的分布式系统”的所有属性。 这包括:

  1. 可伸缩性 - 支持大量流和计算元素。
  2. 弹性 - 允许添加/删除资源以根据负载增长/收缩。
  3. 可靠性 - 弹性应对故障
  4. 效率 - 有效使用基础资源
  5. 响应能力 - 实现准实时方案。

我们在构建 Orleans 流时已考虑到这些要求

说明:Orleans 目前不直接支持编写以上示例中所示的声明性数据流表达式。 当前的 Orleans 流式处理 API 是更低级的构建基块,如此处所述。 提供声明性数据流表达式是我们将来的目标。

另请参阅

Orleans 流编程 API