本文为原创文章,转载请注明出处
摘要
Pulsar Functions 是一个轻量级的计算框架,像 AWS 的 lambda、Google Cloud 的 Functions 一样,Pulsar Functions 可以给用户提供一个部署简单、运维简单、API 简单的 FASS(Function as a service)平台。
设计的初衷
当我们进行流式处理的时候,很多情况下,我们的需求可能只是下面这些简单的操作:
- 简单的 ETL 操作
- Real-time 的聚合操作
- event 路由
- 基于 event 的服务
...
但是,为了实现这些功能,我们不得不去部署一整套 SPE 服务。部署成功后,我们发现需要的仅仅是 SPE 服务中的一小部分功能,部署 SPE 的成本可能比用户开发这个功能本身更困难。由于SPE 本身 API 的复杂性,诸如:map, flatmap, reduce 等,我们需要了解这些算子的使用场景,明白不同算子之间有哪些区别,什么情况下,应该使用什么算子来处理相应的逻辑。
基于以上原因,我们设计并实现了 Pulsar Functions,在 Pulsar Functions 中,用户只需关心计算逻辑本身,而不需要去了解或者部署 SPE 的相关服务,当然你也可以将 pulsar-function 与现有的 SPE 服务一起使用。也就是说,在 Pulsar Functions 中,无需部署 SPE 的整套服务,就可以达到与 SPE 服务同样的优势。
架构设计
Instance
在 Pulsar 中,我们把需要处理的操作单元抽象为主题。如上图所示,整个 instance 的设计中,我们可以将其拆分为三类主题:
- input topic
- output topic
- log topic
input topic 是数据的来源,在 Pulsar Functions 中,所有的数据均来自 input topic。当数据进入input topic 中,Pulsar Functions 充当消费者的角色,去 input topic 中消费消息;当从 input topic 中拿到需要处理的消息时,Pulsar Functions 充当生产者的角色往 output topic 或者 log topic 中生产消息。
output topic 和 log topic 都可以看作是 Pulsar Functions 的输出。从是否会有 output 这个点来看,我们可以将 Pulsar Functions 分为两类,当有输出的时候 Pulsar Functions 会将相应的 output 输出到 output topic 中。log topic 主要存储用户的日志信息,当 Pulsar Functions 出现问题时,方便用户定位错误并调试。
综上所述:我们不难看出 Pulsar Functions 充当了一个消息处理和转运的角色。具体我们可以参考下图:
Pulsar Functions Worker
上述处理过程总结如下:
- 用户给 REST server 发送一个请求
- REST server 响应用户的请求
- Function Metadata Manage 将更新写到 FMT
- Function Metadata Manager 从 FMT 中读取更新
- Scheduler Manager 将更新写到 assignment 主题
- Function Runtime Manager 从 assignment topic 中读取更新
- Membership Manager 配合 Coordination Topic 来做 leader 的选举
- Membership Manager 配合 Coordination Topic 保证 active membership 的资格
下面是一个典型的 Pulsar Functions worker 运行的例子
- 用户提交一个请求到 REST sever 来执行一个 function 的实例。因为 function 的分配不依赖于任何 worker,所以这个请求可能被提交到任意的一个 worker 来处理。
- REST server 将请求传递给 Function Metadata Manager,Function Metadata Manager 将该请求写入 Function Metadata Topic(FMT)。
- Function Metadata Manager 会监听所有新进入 FMT 的 message,当 Function Metadata Manager 从 FMT 收到一个新的 messgae 时,首先会去检查消息是否过期。如果过期了,就直接丢弃;没过期的话,Function Metadata Manager 使用该消息更新其内部状态,其中包含正在运行的所有 function 的全局视图。由于每个工作程序都运行一个 Function Metadata Manager,因此每个 worker 都有一个最终一致的全局视图,其中包含所有正在运行的函数的状态。因此,可以将读取请求(例如获取正在运行中函数的状态请求)提交给任何工作者。
- 当 Function Metadata Manager 更新其内部状态时,会去触发 Scheduler Manager。因为这个时候系统有新的更新进来,肯定需要去对这个新的更新进行计算。这时候,处于leader 状态的 worker 执行调度策略,看这一次的计算分配给谁执行比较合适,然后将新的分配写入到 Assignment Topic。 Membership Manager 用于维护集群中的 leader 以及所有处于 active 状态成员的列表
- Function Runtime Manager 会监听 Assignment Topic,看是否有新的更新。当有更新进来时,Function Runtime Manager 将更新其内部状态,其中包含所有 worker 的全局视图。如果有更新,Function Runtime Worker 会根据这个更新,判断是否需要 start 或者stop function 的实例。
Processing guarantees
Pulsar Functions 提供了以下三种处理语义,用户可根据具体的场景作出选择:
- At most once (默认)
- At least once
- Effectively once
-
At most once
是指消息最多会被处理一次。从 input topics 中接收到之后,在真正处理消息之前去执行, at-most-once 模式下,不管 function 是否执行成功,这个 message 都会被确认(ack),而且只发送一次,无论是否发送成功,都不会重发。 -
At least once
是指消息至少发送一次。如果消息未能接受成功,可能会重发,直到接收成功。在整个 Pulsar Functions 处理消息的过程中,如果失败,都需要对该 message 执行 nack(重发) 操作,来保证At least once
语义的正确性。 -
Effectively once
是指消息会被有效执行一次。上述两种语意都没办法保证系统 crash 之后数据的一致性问题,Effectively once
可以保证只会对结果产生一次影响。Effectively once
本身更像是一个事务的处理过程,首先我们在 setup 生产者的时候需要保证生产者的幂等性;其次在处理消息的过程中,如果出现错误,我们需要让整个 function 停止操作,这点不同于At least once
。
Pulsar Functions subscribeType
为了同时兼容 queue 和 stream 的消费方式,Pulsar 在消费者之间抽象了一层订阅层,在 Pulasr 中,订阅的方式主要分为如下三种:
- exclusive
- failover
- share
但是 Pulsar Functions 中并没有支持 exclusive
的订阅方式。这是为什么呢?
在大部分 functions 的特定场景下,exclusive
的订阅类型没多大用,我们分为两种情况来讨论:
- 如果只有一个 instance,那么
failover
就相当于独占的类型。 - 如果有多个 instance,
exclusive
类型的订阅会不断的 crash、 restart,而failover
的订阅是通过 failover 的方式来进行切换,保证有一个 active 的 worker。(这个是本质原因)
Pulsar Functions runtime
在 Pulsar Functions 中,我们把每个运行的实例称作 instance,一个 instance 执行的是一个 function 的副本。pulsar-function 支持同时并行执行多个 instance,具体 instance 执行的数量可以通过配置文件来指定。为了最大程度提高部署的灵活性,我们支持以下三种 runtime 的形式,用户可以根据需求选择。
- thread runtime
- process runtime
- kubernetes runtime
不同的运行时提供的是不同的隔离级别和成本。这个是一个相对的关系,成本越低,隔离效果越低;反之亦然。thread runtime 是 java 框架开发的,所以目前只有 Java 的 instance 支持 thread runtime。
如何部署 Pulsar Functions
Pulsar Functions 目前支持 java、 python 和 go 三种语言(稍后将支持更多的语言),大家可以选择自己熟悉的语言编写相应的处理函数。目前,Pulsar Functions 支持以下两种部署方式:
- local run
- 在本地运行或者集群外运行一个 Pulsar Functions,适用于开发者。
- cluster
- 在集群内运行 Pulsar Functions。在该模式下部署 function 时,Apache BookKeeper 将自动处理状态存储,目前 go 版本的 function 暂时不支持状态存储。