分布式架构
— 焉知非鱼Distributed Architecture
分布式架构 #
一个有状态的 Functions 部署是由几个组件交互在一起组成的。在这里,我们将描述这些组件及其相互之间的关系和 Apache Flink 运行时。
高层视图 #
一个 Stateful Functions 部署由一组 Apache Flink Stateful Functions 进程和可选的执行远程函数的各种部署组成。
Flink Worker 进程(TaskManagers)从入口系统(Kafka、Kinesis 等)接收事件并将其路由到目标函数。它们调用函数并将产生的消息路由到下一个各自的目标函数。指定用于出口的消息被写入出口系统(同样,Kafka、Kinesis…)。
组成部分 #
繁重的工作由 Apache Flink 进程完成,它管理状态,处理消息传递,并调用有状态的函数。Flink 集群通常由一个主进程和多个工作者(TaskManagers)组成。
除了 Apache Flink 进程,完整的部署还需要 ZooKeeper(用于主站故障转移)和批量存储(S3、HDFS、NAS、GCS、Azure Blob Store等)来存储 Flink 的检查点。而部署时不需要数据库,Flink 进程也不需要持久化卷。
逻辑同位,物理分离 #
许多流处理器的一个核心原则是,应用逻辑和应用状态必须是共位的。这种方法是它们开箱即用的一致性的基础。Stateful Functions 采用了一种独特的方法,在逻辑上将状态和计算共置,但允许在物理上将它们分开。
-
逻辑上的共置。消息传递、状态访问/更新和函数调用被紧密地管理在一起,与 Flink 的 DataStream API 的方式相同。状态按键分片,消息按键路由到状态。每个 key 一次有一个写入器,也是对函数调用进行调度。
-
物理分离。函数可以远程执行,消息和状态访问作为调用请求的一部分。这样,函数就可以像无状态进程一样独立管理。
函数的部署风格 #
有状态的函数本身可以以不同的方式部署,这些方式可以相互交换某些特性:一方面是松散的耦合和独立的扩展,另一方面是性能开销。每个函数模块可以是不同的种类,所以有些函数可以远程运行,而有些函数可以嵌入式运行。
远程函数 #
远程功能采用上述物理分离的原则,同时保持逻辑上的同位。状态/消息层(即 Flink 进程)和功能层是独立部署、管理和扩展的。
功能调用通过 HTTP/gRPC 协议发生,并通过服务将调用请求路由到任何可用的端点,例如 Kubernetes(负载平衡)服务、Lambda 的 AWS 请求网关等。因为调用是自足的(包含消息、状态、访问计时器等),所以目标函数可以像任何无状态的应用程序一样对待。
详情请参考 Python SDK 和远程模块的文档。
共置函数 #
部署函数的另一种方式是与 Flink JVM 进程共处。在这样的设置中,每个 Flink TaskManager 将与坐在"旁边"的一个 Function 进程对话。一种常见的方式是使用 Kubernetes 这样的系统,部署由 Flink 容器和 Function 侧车容器组成的 pod;两者通过 pod-local 网络进行通信。
这种模式支持不同的语言,同时避免了要通过 Service/LoadBalancer 来路由调用,但它不能独立扩展状态和计算部分。
这种部署方式类似于 Flink 的 Table API 和 API Beam 的可移植层部署和执行非 JVM 函数的方式。
嵌入式函数 #
嵌入式函数类似于 Stateful Functions 1.0 的执行模式,也类似于 Flink 的 Java/Scala 流处理 API。函数在 JVM 中运行,直接调用消息和状态访问。这是最有性能的方式,不过代价是只支持 JVM 语言。函数的更新意味着更新 Flink 集群。
按照数据库的类比,嵌入式 Functions 有点像存储程序,但方式更有原则。这里的函数是实现标准接口的普通 Java/Scala/Kotlin 函数,可以在任何 IDE 中开发/测试。