在 OpenAI 打造流处理平台:超大规模实时计算的实践与思考

简介: 本文介绍OpenAI构建流处理平台的实践与挑战。面对Kafka高可用、Python生态兼容、云环境限制等问题,团队基于PyFlink打造跨区域流处理架构,集成Kafka HA组、自研代理与控制平面,支撑实时Embedding生成、特征计算等场景,并推动开源协作与平台自动化演进。

本文整理自 OpenAI _基础设施团队的 Shuyi Chen 和 Joey Pereira 在 Current 2025 伦敦会议上的演讲 ”Building a Stream Processing Platform at OpenAI“_

主要演讲内容为:

  • OpenAI 的流式基础设施;

  • 构建流处理平台的动机及遇到的挑战;

  • OpenAI 的整体架构及深入解读;

  • OpenAI 业务用例以及平台未来的演进方向。

一年前的流式基础设施

回顾一年前,OpenAI 的流式基础设施主要围绕 Kafka 及其生产者和消费者服务构建。Kafka 被广泛用于数据摄入、异步处理和服务间通信。随着 ChatGPT 的上线,Kafka 需求迅速增长,已成为支撑众多关键业务的核心基础设施之一。

image.png

我们面临的主要挑战之一是确保 Kafka 基础设施的可用性和可靠性。我们的 Kafka 基础设施构建在云上,曾一度拥有数十个 Kafka 集群。在云环境中,集群可能崩溃、区域可能失效、网络光缆也可能被切断。因此,单个 Kafka 集群可能成为依赖它的使用场景的单点故障。我们确实经历过单个 Kafka 集群故障对业务造成严重影响的实例。

为应对这一挑战,我们引入了“高可用组”(high availability group)的概念。一个高可用组将跨区域的多个物理 Kafka 集群组合在一起,以提供高可用性。这样,当某个集群故障时,我们可以绕过它,为生产者和消费者服务提供 HA 保障。例如,典型的 HA 组配置包括一个 West US 集群、一个 Central 集群和一个 East US 集群。

image.png

然而,HA 组中多集群的引入也为生产者和消费者服务带来了不小的复杂性,因为它们必须理解 Kafka 基础设施的底层拓扑。为解决这一问题,我们构建了生产者和消费者代理(proxy)进程,将基础设施细节对用户隐藏,所有复杂性都封装在代理之后。该代理为 Kafka 的生产和消费提供了一个简单且一致的接口。

image.png

例如,当 East US 集群开始故障时,生产者和消费者端的代理会将流量绕过故障集群。同样,我们也可以向 HA 组中添加一个新集群(例如 South US),而这一切对生产者和消费者服务都是透明的。

image.png

关于 Kafka 基础设施设置的更多细节,请参考我们团队在本次会议上关于 Kafka 迁移以及 OpenAI 如何简化 Kafka 消费的演讲。

为何需要流处理?

随着 Kafka 使用量的增加,我们自然开始思考:流处理(stream processing)或 Apache Flink 能带来什么?

场景一:数据飞轮(Data Flywheel)

从高层次看,数据飞轮是一个自强化系统,其中数据生成、模型改进和产品使用不断相互促进,以推动性能和价值的提升。我们发现,更快地将产品使用数据反馈给大模型,实际上能带来有意义的差异。流处理技术可以通过提供一个可扩展的框架,在 OpenAI 的规模上近乎实时地处理和转换数据,从而帮助实现数据飞轮的目标。

image.png

场景二:实验数据处理与摄入

在当今的 AI 开发中,快速实验和迭代对模型开发至关重要。能够快速处理、关联并可视化实验结果,对于加速模型开发非常重要。在流处理出现之前,工程师和研究人员有时会为处理大量实验数据而构建自定义的临时系统。这些系统通常涉及复杂的关联或状态管理,并且由于在大规模运行系统时的挑战,也容易出现数据新鲜度问题。这正是 Apache Flink 等流处理技术可以大放异彩的地方——它为预处理实验数据提供了一个稳健且可扩展的基础。

image.png

除此之外,还有其他业务使用场景,我们稍后会详细介绍。

构建流处理平台的挑战

接下来,我将谈谈我们在 OpenAI 构建流处理平台时遇到的一些挑战。

image.png

挑战一:Python

虽然大多数开源流处理技术都是基于 JVM 的,但在 AI 领域,Python 是事实上的标准语言。在 OpenAI,许多业务处理逻辑和服务都是用 Python 编写的,几乎没有 Java 支持。尽管 Apache Flink 提供了 Python 支持,但总体而言,其开发和采用相对较新,与 Java 版本相比也还不够成熟。

挑战二:云厂商的限制

我们经常发现,云厂商宣传的 Kubernetes 集群最大规模往往过于乐观——在实际生产中,受限于控制平面的性能瓶颈,我们很难稳定运行接近该上限的集群。此外,在实践中,由于某些区域的物理限制,我们很难从这些区域获得足够的容量。为了满足运行流处理工作负载的容量需求,我们从一开始就不得不在多个 Kubernetes 集群之上构建我们的平台。而且,正如之前提到的,在云环境中,集群和区域都可能失效,因此我们的平台也必须能够跨区域可靠运行。

挑战三:高可用 Kafka 集群带来的复杂性

最后但同样重要的是,我们之前提到的 HA Kafka 集群设置,也为运行 Apache Flink 等框架带来了挑战。在 Kafka HA 组设置中读取一个主题(Topic),实际上会转化为并行地从多个物理 Kafka 集群进行多次消费,如果实现不当,反而可能导致可用性降低。

平台架构概览

在设计流处理平台时,我们始终牢记上述挑战。以下是我们的整体架构概览。

image.png

首先,我们决定使用 PyFlink 作为主要的流处理框架,并与 Flink 社区合作,持续改进 PyFlink。这使我们的所有用户都能利用 Apache Flink 提供的流处理技术,同时还能复用所有现有的 Python 库来构建他们的流处理管道。事实证明,使用 Python 也帮助提高了我们用户的开发速度和生产力。

其次,在每个 Flink Kubernetes 集群内部,我们使用开源的 Flink Kubernetes Operator 来管理 Flink 作业。我们在跨区域的活跃 Flink 集群之上构建了一个控制平面(control plane)抽象层。这使我们能够通过单一的控制平面集中管理所有 Flink 作业。

最后,我们还将 Flink 与 OpenAI 的 Kafka 生态系统进行了深度集成,以确保 Flink 能够与我们上面讨论的 Kafka HA 设置可靠地协同工作。

平台架构细节

从宏观角度看,用户和其他平台(例如机器学习平台)通过控制平面抽象层与流处理平台交互。这里的控制平面旨在为管理所有流处理管道提供一个统一的入口。

为了让 Flink 对我们的工程师更易用,我们将其与现有的服务脚手架、测试、构建和部署基础设施进行了深度集成,使用户可以遵循与微服务开发相同的工作流程。控制平面将负责跨不同区域的多个 Kubernetes 集群协调作业管理。

image.png

在每个 Kubernetes 集群内部,我们使用开源的 Flink Kubernetes Operator 来编排 Flink 作业。该 Operator 为 Kubernetes 集群内的管道提供生命周期管理。我们将每个 Flink 作业作为 Flink Deployment 自定义资源运行。Flink 部署通过 Kubernetes 命名空间在不同团队和组织之间进行隔离。我们为每个命名空间运行一个专用的 Flink Kubernetes Operator。

image.png

虽然 Flink Kubernetes Operator 处理了 Flink 的大部分管道生命周期管理,但为了满足 OpenAI 的特定需求,我们还设置了一个跨集群的看门狗(watchdog)服务,用于监控 Flink 作业所依赖的 OpenAI 特定配置变更。例如,看门狗服务会定期检查每个 Flink 作业的主题的 Kafka 拓扑。如果我们发现有新的物理集群被添加或移除,看门狗将触发 Flink 作业的重启,以便它能获取最新的 Kafka 拓扑变更,从而避免数据丢失或延迟。

image.png

对于有状态的管道,我们使用本地 RocksDB 来存储状态,并为每个命名空间设置 Azure Blob Storage 账户,并为该账户启用异地复制(geo-replication)。在主区域发生故障时,我们可以初步故障转移到辅助区域。目前,平台为所有团队管理 Azure Blob Storage 账户,但我们也允许用户选择提供自己的 Blob 存储账户。

image.png

在构建过程中,我们遇到了一个需要注意的问题:目前开源的 Apache Flink 实际上并不支持 Azure Workload Identity 身份验证,而这是 Azure 推荐的用于安全访问存储账户的方式。为了解决这个问题,我们内部将 hadoop-azure 库升级到了 3.4.1,以启用 Azure Workload Identity 身份验证。我们也计划将此贡献回社区。

深入 PyFlink

现在,让我们深入探讨几个关键话题。

首先,我们来看看 Python。开源的 PyFlink 提供了 DataStream API 和 Table/SQL API。在 OpenAI 内部,我们将 PyFlink 与我们的单体仓库(monorepo)系统集成,使用户可以像开发常规 Python 项目一样,复用所有现有的 Python 库。

image.png

PyFlink 使用了大部分 Flink JVM 栈,并在 Flink SDK 和运行时中增加了对运行 Python 函数的支持。在 SDK 侧,它基本上使用 Py4J 将新的 Python DataStream 和 Table/SQL API 映射到 Java 版本。在运行时侧,Python 函数被映射到 Java 图中的自定义 Python 算子。该 Python 算子由运行用户 Python 逻辑的 Python Worker 以及与 Python Worker 通信的自定义 Java 算子组成,后者负责处理检查点(checkpointing)、水印(watermarking)以及与 Python Worker 的数据和状态交换。

PyFlink 目前支持两种不同的执行模式来运行 Python 用户自定义函数:进程模式(process mode)和线程模式(thread mode)。默认模式是进程模式。在进程模式下,用户的 Python 函数作为单独的进程运行,并使用 Apache Beam 的可移植性框架与 JVM 算子通信。它具有良好的资源隔离性,总体上也更成熟。然而,其局限性在于 IPC 开销,因为它们使用 gRPC 在 JVM 进程和 Python 进程之间进行通信。这会带来序列化和反序列化的开销。此外,这也需要更多的调优参数来适应不同类型的工作负载,例如批处理大小(batch size)和批处理超时(batch timeout)。

image.png

PyFlink 也支持线程模式。在线程模式下,用户的 Python 函数在与 JVM 线程相同的进程中运行。它带来了吞吐量、延迟的提升以及更短的检查点时间。然而,其局限性在于目前仅支持 CPython 和应用模式(application mode),总体上不如进程模式成熟。我们实际上与社区委员会合作,修复了线程模式中的几个问题,包括日志记录以及 JVM 中的共享对象加载。

到目前为止,我们已经在 OpenAI 将 PyFlink 投入生产。然而,我们也观察到了一些挑战,首先是效率问题。基本上,正如我们所见,所有的 Python 函数(用户逻辑)都在 Python 中运行,并且在进程模式下 IPC 期间会产生额外的序列化和反序列化成本。因此,对于大规模作业,我们也支持用户用 Java 实现他们的处理函数。PyFlink 实际上支持从 Python DataStream API 调用它们,因此我们可以支持用 Python 编排流处理逻辑,但实际代码将在 JVM 中运行。

image.png

此外,异步 I/O(async I/O)和流式关联(streaming join)在 Python 的 DataStream API 中尚未得到支持。我们计划与社区合作,增加这些支持。最后,PyFlink 目前还不支持 Python 3.12,我们也在内部和社区中努力增加这一支持。

Flink 与 Kafka 的集成

接下来我们聊聊 Kafka 连接器——需要特别说明的是,这里指的是 Flink 自带的原生连接器,而非 Confluent 提供的版本。如前所述,我们的 Kafka 部署采用了高可用组(HA Group)架构:多个跨区域的物理集群组成一个逻辑集群,目的是确保即使其中一个集群完全失效,整个系统仍能正常运行。这就带来了一个核心挑战:如何让 Flink 应用适配这种多集群架构?我们能否构建出真正容忍 Kafka 集群中断的流处理作业?

image.png

乍看之下,这似乎并不难。我们只需要在 Source 和 Sink 两端“堆”几个 Kafka 连接器就行了。先看 Source 端:拿到集群列表后,为每个集群创建一个 Kafka Source,再把它们 union 起来接入主处理逻辑——问题不就解决了吗?

可惜,现实没那么美好。

你可能已经猜到了结果——否则我也不会花时间专门讲这一段。事实上,当某个 Kafka 集群宕机后不久,我们的 Flink 应用就开始频繁崩溃。深入排查后,我们发现根本原因在于连接器的初始化机制:当 Kafka Source 启动时,它会尝试获取主题的分区元数据、枚举所有分区,并为每个分区创建读取任务。如果此时某个集群不可达,Flink 内部对 Kafka 元数据的请求就会失败,并直接抛出异常——整个作业因此被拉垮,毫无容错能力可言。

通过对 Kafka Source 进行定制化改造,我们将其配置为在元数据请求失败时无限重试。这样一来,那些已经成功初始化、正在从 Kafka 分区读取数据的任务会继续正常运行;而针对故障集群的读取任务则会持续重试,虽不产出数据,但也不会导致作业崩溃。这已经满足了我们的首要目标:保证应用持续运行。

更进一步,即使在某个 Kafka 集群宕机的情况下,我们也能正常重启 Flink 作业——它会跳过不可达集群的分区,仅对可用集群创建读取器。这让我们离理想状态又近了一步。

但这还不是全部。我们的 Flink 应用在启动时会动态加载集群配置,并据此构建 Kafka Source 列表。正如前文提到的,由于作业拓扑依赖于这份配置,一旦配置变更(例如移除或新增集群),就必须重启作业才能生效。为此,我们引入了一个“看门狗”(watchdog)服务,持续监听配置变化,并在必要时触发作业重启。

这意味着,当某个集群彻底失联、确认无法恢复时,我们可以直接从应用配置中将其移除,彻底停止对其消费;而当它恢复后,只需重新加入配置,配合自定义的偏移量初始器(initializers),就能从上次中断的位置继续消费(大致如此)。通过这种方式,我们实际上拥有了两层容错机制:运行时重试 + 配置驱动的动态调整。

需要特别说明的是,这套方案目前主要应用于不依赖水印(watermark)的管道。如果管道使用了水印,而某个 Source 因集群宕机停止输出数据,就会导致水印无法推进,整个流处理逻辑被卡住。理论上可以通过设置空闲超时(idle timeout)来缓解,但这一路径我们尚未像无水印场景那样充分验证。

当然,一旦故障集群恢复,积压的数据会重新流入系统——不过会被标记为迟到事件(late events)。从设计角度看,这其实提供了一种明确的权衡选择:用户可以在强一致性视图和高可用性之间做出取舍。选择后者,就意味着接受更多迟到数据,但换来的是系统在部分基础设施失效时仍能持续运转的能力。

image.png

关于 Source 端,还有一点值得补充:Flink 开源社区其实已经提供了一个非常实用的方案——动态 Kafka Source(Dynamic Kafka Source)。它将多个主题或多个集群的 Kafka 源统一抽象为一个真正的 Flink Source。你可以实现一个轻量级的元数据服务(或一个简单的类),动态指定当前需要消费哪些集群和主题。更重要的是,它支持在运行时重新加载元数据,并直接将新发现的分区分配给任务,完全无需修改作业拓扑或重启作业

这意味着,你不再需要依赖外部的“看门狗”来触发重启——配置变更可以实时生效,灵活性大幅提升。而且,这个功能是完全开源的。它的任务分配逻辑与原生 Kafka Source 高度一致:分配的最小单元不是单纯的“分区”,而是 (分区 × 主题 × 集群) 的组合,能准确反映多集群拓扑结构。

我们目前尚未在生产中采用它,主要有两个原因:一是 PyFlink 尚未提供对应的 Python 封装(wrapper),二是它在某些细节上仍有局限——例如,无法为每个集群和主题单独配置偏移量初始器(offset initializers)。

尽管如此,它的整体表现已经相当可靠,远胜于手动重启作业并喊一句“嘿,醒醒,加个集群!”。因此,我们计划尽快为 PyFlink 补齐对动态 Kafka Source 的支持,并在落地过程中持续修复和优化,充分释放其潜力。

有了这个方案,我们终于有了一条清晰可行的路径:即使某个 Kafka 集群宕机,Flink 作业也能无缝继续消费其他集群的数据,用户几乎感知不到任何中断。

现在来看 Sink 端。我们最初设想了一个看似简洁的方案:通过一个分流函数,将主数据流均匀拆分到多个旁路输出(side outputs)中,再为每个 Kafka 集群分别挂载一个 Sink。理想情况下,三个集群各承担约三分之一的流量——逻辑清晰,实现简单,当时我们甚至觉得这方案稳了……

可惜,现实很快给了我们一记重击。你大概已经猜到结果了:一旦某个 Kafka 集群下线,整个作业几乎立刻陷入停滞,无法继续处理数据。

更深层次的问题在于,这种设计存在结构性缺陷。由于所有 Sink 共享同一个处理流水线,任何一个集群出现性能波动(比如网络延迟、磁盘瓶颈或限流),都会通过背压(backpressure)传导至整个作业,拖慢所有数据流。我们原本希望通过静态均分来实现负载均衡,却忽略了现实环境中的异构性——不同区域的 Flink 作业与 Kafka 集群之间的写入能力差异极大:有的集群吞吐迅猛,有的则响应迟缓,固定比例的分流非但无法均衡负载,反而放大了系统短板。

image.png

那我们怎么办?坦白说,我们“走了一条捷径”。事实上,OpenAI 的大多数服务早已在使用一个统一的生产者代理,它封装了重试、断路器(circuit breakers)、集群动态发现等高可用逻辑。于是,我们直接把这个代理调用包装成一个 Flink 函数来实现 Sink 功能。当然,这样做也带来了一些限制和注意事项。

首先,我们的代理并不真正支持基于键的自定义分区(key-based custom partitioning)。而如果强制使用键分区,就会把某个分区的数据绑定到单一 Kafka 集群上——这与我们追求高可用的目标直接冲突。毕竟,我们不想让任何一个 Kafka 集群成为系统的单点依赖。 其次,事务(transactions)目前也无法支持。要在代理中集成完整的状态管理与事务语义,复杂度极高,几乎得不偿失。需要特别说明的是,由于我们将代理封装成了一个普通的 Flink 函数,天然受限于函数的执行模型——事务性写入无法在单个函数内实现,必须通过完整的 Sink 实现才能支持。 最后,这种方案还引入了额外的性能开销,尤其是在高吞吐场景下,代理层的调用链路会成为瓶颈,这显然不是理想状态。 那么,下一步怎么优化?我们正在规划将这一能力重构为一个真正的开源 Sink 实现,并计划向社区提交正式提案,打造一个既高可用又高性能的标准解决方案。对于非事务性写入,实现起来其实相对直接:你可以替换底层的写入器(writer)实现,让它将数据分发给多个 Kafka 集群的写入器,从而构建一个写入器池。在这个池子之上,你可以封装重试、故障转移、负载均衡等逻辑。更重要的是,这套机制可以像“动态 Kafka 源”那样支持运行时动态配置——集群的增删、迁移等操作都能实时生效,无需重启作业,极大提升了运维灵活性。 相比之下,事务性写入的实现要复杂得多。虽然当前的 Kafka Sink 内部确实有一个生产者池(pool of producers),理论上可以尝试将其扩展到多集群场景,但一旦引入跨集群事务,就会迅速陷入各种边缘情况的泥潭:事务边界如何界定?故障时如何回滚?不同集群间的协调如何保证?这些问题目前都没有成熟的解决方案。尽管如此,我们仍认为存在一条可行的技术路径——通过更精细的控制和设计,未来有望实现一种既支持高可用又兼顾迁移灵活性的事务性发布方案,远优于当前依赖静态配置的替代方案。 结合 Source 和 Sink 两端的实践,你应该已经能感受到:在真实的大规模生产环境中,面对多集群、跨区域的 Kafka 架构,流处理系统必须在可用性、一致性与运维效率之间做出务实权衡。而我们的探索,正是为了在这条复杂路径上找到更稳健的前行方式。

让我们稍作停顿,重新聚焦一下:为什么这些设计对我们如此关键?

因为在实际运行中,故障不是“会不会发生”的问题,而是“何时发生”的问题。中断早已成为常态,而非例外。你可能经历过,也可能没经历过——但在 OpenAI,我们确实遇到过诸如整个区域宕机、光缆被挖断导致跨区域延迟骤增等极端情况。有时问题甚至源于我们自己的操作失误。但无论原因如何,系统都必须能扛得住。

正因如此,我们必须从底层基础设施到上层应用,全栈考虑容错能力,确保流处理作业在任何异常情况下都能持续稳定运行。

回顾一下我们前面提到的关键挑战:

  • Flink 集群可能失效 → 我们需要能在多个 Kubernetes 集群间自由迁移作业;

  • 存储可能丢失 → 我们依赖异地复制的存储,并支持主备切换;

  • Kafka 集群可能中断 → 我们通过高可用组和代理层,确保任一 Kafka 集群都不是单点故障,无论是在消费端还是生产端。

当然,脱离实际场景谈架构意义有限。接下来,我们通过几个真实受益于这套设计的业务管道,来看看这些能力是如何落地的。

image.png

用例一:实时 Embedding 生成

我们有一类典型的管道,用于为各类产品实时生成 Embedding。其逻辑非常直接:接收输入数据,调用模型服务(RPC),再将结果输出。这类任务之所以选择 Flink,一方面是因为其 API 简洁易用,但更关键的原因在于——我们需要将结果同步分发到多个下游区域,而这些区域各自托管着对应的数据副本。

image.png

在这个场景中,数据的新鲜度远比“截至某个水印的完整视图”更重要。因此,系统必须具备容忍单个 Kafka 集群故障的能力:即使某个区域的集群宕机,仍能继续消费其他活跃区域的数据流。这类管道通常不依赖水印,也不需要对迟到数据做特殊处理;我们只期望当故障集群恢复后,积压的数据能自然流入并被正常处理——就像什么都没发生过一样。

用例二:传统 ML 特征计算

另一个典型场景是传统机器学习特征的实时计算——毕竟,一场不提 AI 的 OpenAI 技术分享多少有点说不过去。

image.png

借助像开源项目 Chronon 这样的框架,我们可以用声明式方式定义特征逻辑(例如 “统计用户过去 1 小时内点击按钮的次数”),然后由系统自动编译成 Flink 作业执行。这类管道同样遵循 OpenAI 内部广泛采用的 “一次计算,到处分发”(compute once, distribute everywhere) 范式。原因很实际:原始数据往往来自多个区域,而下游应用可能部署在本地,或需要在多个区域冗余存储同一份特征数据(即便某些区域使用频率较低)。

特别值得注意的是,输入数据本身也是跨区域分布的。这进一步强化了我们的架构要求:Kafka 集群绝不能成为任一区域的单点故障——否则,特征计算的完整性将直接受到威胁。

未来工作

在结束本次分享之前,我们想简要谈谈未来的演进方向。这一路走来,我们踩过不少坑,也向社区提交了一系列 issue 和 PR。其中不少集中在 PyFlink 上——比如相比 Java 版本仍存在的功能缺失或稳定性问题;还有一些则涉及更细粒度的部署定制和云环境特有的挑战。

image.png

但值得庆幸的是,整个生态是开源的,任何人都可以参与共建。虽然 PyFlink 的成熟度尚不及 Java,但社区响应迅速、协作氛围良好。对我们而言,它绝非“洪水猛兽”,而是一个值得投入的方向——尤其当你的用户群体主要是 Python 工程师时,让他们直接用熟悉的语言开发流处理作业,远比说服他们转用 Scala 或 Java 来得高效。

image.png

除了持续回馈开源社区,我们也在思考如何提升平台自身的自动化能力。目前,控制平面主要依赖部署系统调用 CLI 工具进行管理。未来,我们希望构建一个统一的 Flink 应用管理平台,能够智能决策诸如:

  • “这个作业该调度到哪个 Kubernetes 集群?是基于负载、资源位置,还是容灾策略?”

  • “何时自动扩缩容底层集群?”

  • “当某个集群异常时,能否自动触发跨集群故障转移,而不是半夜把工程师叫起来手动迁移?”

归根结底,我们的目标是让平台真正“好用”。为此,我们正在探索一些关键体验优化,例如:

  • 自助式 SQL 管道:工程师打开一个 Notebook,就能像写查询一样快速构建流处理逻辑;
  • 完整的 PyFlink 功能支持:确保异步 I/O、流式 Join、Python 3.12 兼容等能力尽快落地;
  • 端到端可靠性提升:包括零停机部署、动态连接器更新等。

image.png

最终,我们希望用户只需关注业务逻辑本身——无论是写一段 SQL 还是一个 Python 函数——而无需操心“作业怎么跑、状态怎么存、集群挂了怎么办”。平台应该默默扛下所有复杂性,让开发者专注创造价值。

以上就是我们今天的全部内容,感谢大家的聆听!现在进入问答环节,欢迎提问。

Q&A 环节

问:Flink 应用程序的软件生命周期是怎样的?
在批处理或数据仓库场景中,我们可以反复重跑全量数据、调整查询、验证结果,直到输出正确为止。但在你们的 Flink 流处理架构下,这个过程是如何实现的?比如,当我要上线一个包含新字段的新版本作业时,是否需要重放 TB 级的历史数据?

答:
目前在 OpenAI,PyFlink 作业默认启动时会从 Kafka 中最早的可用偏移量(即保留窗口起点)开始消费。我们为 Kafka 主题默认保留 7 天的数据,因此大多数情况下可以通过重放这 7 天的数据来验证或更新作业逻辑。

如果作业逻辑发生变更,同时 Kafka 主题的 Schema 也发生了变化(例如新增字段),我们当前主要通过回填(backfill) 的方式支持数据重处理——即重新消费 Kafka 中的历史数据并应用新逻辑。不过,目前我们的回填能力仅限于从 Kafka 本身读取,尚不支持从数据湖等其他存储源进行重新消费。

至于自动化程度:当你部署新版本作业时,系统还不会自动触发全量回放。是否重放、从哪里开始重放,目前仍需用户手动配置。这确实是平台的一个待完善点。

需要说明的是,我们的 Flink 平台投入生产的时间并不长——大约从去年年底才开始规模化使用。因此,在作业生命周期管理、自动化回填、Schema 演进支持等方面,我们还有大量工作要做,也欢迎社区和用户一起推动这些能力的落地。

问:你们向用户暴露的编写 Python 作业的接口是什么? 你们是否集成了 Jupyter Notebook,或者围绕 Jupyter Notebook 做了 CI/CD?

答: 我们向用户暴露的接口与我们内部工程师开发 Python 项目和 Python 微服务的方式相同。他们不会去写一个 FastAPI 应用,而是会使用 PyFlink API 编写一个 main.py。这是相当标准的 IDE——VS Code、IntelliJ——IDE 体验与编写微服务时完全相同,只是使用的不是 FastAPI 框架,而是 PyFlink 框架。因此,你可以使用所有工程师在开发微服务时使用的 Python 库。

人们测试或开发的一些方式通常是使用开发环境中的一些共享或可用的测试集群。所以你能够相当快速地进行推送——我认为只需几分钟就能运行一个针对测试环境的部署命令,它就会带着你的本地代码在那里运行。

问:你们是否也提供某种能力,对存储在数据湖仓中的数据进行重处理——比如用文件源或 Sink 替换 Kafka?

答: 我们目前没有这个功能,但这在你想处理更复杂的事情时基本上是必要的——比如如果你需要比 Kafka 中或你的保留期更多的历史数据。也许你出于各种原因对保留期做了相当激进的削减,那么这就会变得更加具有挑战性——这基本上是必要的,但我们目前还没有这样做。

问:你们提到的集群和处理之间的代理,是开源的还是内部专有的?

答: 它是内部的。它基本上是围绕标准 Java Kafka 客户端的一个常规脚手架,包含了我们想要和需要的所有逻辑,比如故障转移、断路器、身份验证、重试逻辑等。你不想为了添加一个新的 Kafka 集群或修复一个问题而去部署一百个服务——你只需部署一个代理。所以它实际上是一个轻量级的 gRPC 服务。

补充一点:生产者代理是内部的,正如前文所提到的。但对于消费者代理,我们实际上使用了 Uber 的 uForward 代理,这是开源的。我们的队友在另一个演讲中介绍了我们如何利用消费者端代理来简化 OpenAI 的 Kafka 消费。

问:你们谈到的业务用例是假设性的还是真实的?例如,Embedding 生成是否真的在使用这个管道?如果它们是真实的,那么 Flink 替代了什么?你们在早期技术中遇到了什么问题,以至于需要使用 Flink?

答: 这些用例大多是真实生产场景的简化版,而且基本都属于全新构建的项目。我们并没有用 Flink Pipeline 去替换任何已有的系统,而是当团队在解决新问题时,往往会先尝试一些临时甚至“有点疯狂”的方案——比如在数据库里手动排队、写脚本轮询等。这时我们就会介入,建议他们:“不如试试用一个 Kafka 主题加一个 Flink 应用?这样可能比手搓一套更简单、也更可靠。” 因此,这些 Flink 应用通常服务于全新的功能或能力,而非对旧系统的迁移。

以 Embedding 实时生成为例,在实际落地过程中,我们就遇到了一个具体挑战:PyFlink 目前缺乏对异步 I/O 的原生支持。为了解决这个问题,我们不得不自行实现绕过方案。但与此同时,我们也正积极与 Flink 社区合作,推动将异步 I/O 能力正式集成到 PyFlink 中,以便更好地支撑这类高并发、低延迟的典型流处理场景。

问:对于高可用性,对于一个特定的主题,你们是将主题存储在多个集群或区域中,还是只在一个特定区域中?

答: 是的,每个主题的数据实际上会分布在多个区域的 Kafka 集群中。你可以把它理解为一种逻辑上的分片(sharding):生产时,我们会根据随机策略或优先级规则,将数据写入其中一个集群。如果某个集群宕机,系统会自动尝试其他可用集群;如果断路器已经触发,甚至根本不会考虑那个故障节点。因此,数据天然就是跨集群分布的。

这意味着,任何 Kafka 消费者——无论是否基于 Flink——都必须从多个区域同时读取数据,才能获得完整的视图。 没错,这正是我们构建生产者和消费者代理的核心原因:把多集群拓扑这类基础设施复杂性完全封装起来,对上层应用透明。用户只需像使用单个 Kafka 集群一样进行生产和消费,无需关心底层到底有几个集群、哪个可用、如何路由。

这些代理是独立服务吗? 是的,我们将其设计为独立的中间服务。这样做的好处显而易见:如果重试逻辑、集群列表或故障转移策略分散在上百个业务服务里,作为平台团队,每次调整配置或修复问题都将是一场灾难。而通过集中式的代理服务,我们可以快速迭代、统一治理,所有客户端只需通过它通信即可——既降低了用户的心智负担,也极大提升了平台的可维护性。

问: PyFlink 相比基于 Java/JVM 的 Flink 应用肯定有额外开销。你们有没有具体的性能数据,能说明使用 Python 到底会带来多少资源损耗?

答: 我们目前还没有进行正式的基准测试,但从实际运行情况来看,由于用户逻辑是在 Python 进程中执行的,PyFlink 作业在 CPU 和内存资源消耗上确实明显高于纯 JVM 实现。

正因如此,对于真正大规模、高吞吐的作业,我们提供了一种混合开发模式:用 Python 编排作业拓扑(比如定义 Source、Sink 和算子连接关系),但将核心计算逻辑实现在 JVM 算子中。这样既能保留 Python 在开发效率和生态集成上的优势,又能确保关键路径的性能和资源效率。

事实上,已经有多个团队在实践中采用了这种策略。当他们遇到作业性能瓶颈或内存不足(OOM)问题时,第一反应往往是:“能不能把这段计算密集型逻辑移到 Java 算子里?”——而通常这样做并不复杂,只需少量重构,就能显著改善资源使用和稳定性。

问: 从 Kafka 的角度看,你们如何应对流量高峰——比如 GPT 图像生成功能上线时的突发流量?Kafka 是如何扩展以应对这类场景的?

答: 实际上,对我们而言,最大的流量增长往往来自新功能或新用例的接入,而非产品发布本身。虽然像 GPT 图像生成上线这样的事件确实会带来流量激增,但这类情况通常比外界想象的更容易提前规划。

在 OpenAI,团队在设计阶段就会主动与我们沟通:“我们打算做这个功能,计划用 Kafka。” 这时我们会深入讨论关键问题:消息量级是多少?是否需要按用户或消息键保序?数据保留周期多长?吞吐和延迟要求如何?基于这些信息,我们就能提前做好容量评估、集群扩容和分区规划。

正因为有这种前置协作机制,大多数高流量场景都能被平稳承接,实际运行中并没有出现“措手不及”的情况。

相关文章
|
6天前
|
数据采集 人工智能 安全
|
15天前
|
云安全 监控 安全
|
1天前
|
存储 SQL 大数据
删库跑路?别慌!Time Travel 带你穿回昨天的数据世界
删库跑路?别慌!Time Travel 带你穿回昨天的数据世界
237 156
|
9天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
628 5
|
12天前
|
人工智能 自然语言处理 API
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸
一句话生成拓扑图!next-ai-draw-io 结合 AI 与 Draw.io,通过自然语言秒出架构图,支持私有部署、免费大模型接口,彻底解放生产力,绘图效率直接爆炸。
787 152
|
20天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1893 9
|
2天前
|
机器学习/深度学习 人工智能 监控
别把模型当宠物养:从 CI/CD 到 MLOps 的工程化“成人礼”
别把模型当宠物养:从 CI/CD 到 MLOps 的工程化“成人礼”
222 163