摘要:数据分析师(Data Analyst)主要责任:分析业务数据,发现趋势和洞察创建报表和可视化仪表板支持业务决策进行临时数据查询和分析数据处理语言: SQL为主(OLAP,数据仓库),Python/R为辅(Pandas,Matlab)数据科学家和机器学习工程师(
在数据领域,大致有以下的职责分工
数据分析师 (Data Analyst)主要责任:分析业务数据,发现趋势和洞察创建报表和可视化仪表板支持业务决策进行临时数据查询和分析数据处理语言: SQL为主(OLAP,数据仓库),Python/R为辅(Pandas,Matlab)数据科学家和机器学习工程师(Data Scientist)主要责任:构建预测模型和机器学习算法进行高级统计分析开发数据产品构建ML Pipeline将ML模型部署到生产环境模型监控和维护以及优化模型性能A/B测试设计与分析数据处理语言: Python为主(scikit-learn、TensorFlow、PyTorch)数据工程师 (Data Engineer)主要责任:构建和维护数据管道(ETL/ELT)设计数据仓库架构确保数据质量和可用性优化数据基础设施数据处理语言: SQL(dbt、Snowflake、BigQuery),Python,Scala/Java (Spark、Flink,Kafka、Airflow)我们看到Python是数据领域被使用最为广泛的语言,SQL次之。这也是为什么Spark和Flink虽然是以Java/Scala为主要的编程接口,也都推出了自己的Python和SQL:
SparkPySparkSpark SQLFlinkPyFlinkFlink SQL这样做就是为了尽可能的满足已有用户的使用习惯。
但是因为架构上PySpark和PyFlink都是采用了Python和JVM进程间通信的方式来实现,这样的复杂架构带了了诸多不便和问题。如进程间数据拷贝的开销,序列化/反序列化的开销,无法利用JVM优化等问题。于是社区希望能有一款Python原生的流数据处理工具。
Faust 是 Robinhood 一次雄心勃勃的尝试,旨在将流处理引入Python。在被Robinhood悄然放弃之前,它曾在生产环境中每天处理数十亿条事件。尽管它在技术上取得了一定的成功,实现了基于 Python 的流处理(利用 async/await 模式和基于 RocksDB 的状态存储),但该项目饱受关键可靠性问题困扰,包括消费者崩溃、内存泄漏以及不可靠的“精准一次(exactly-once)”语义。Faust的故事不禁让我们思考,在构建分布式数据基础设施时,什么时候 Python 适合,什么时候不适合。
Faust 诞生于 Robinhood 的爆炸式增长背景下——2017 年新增超过 100 万投资者。它最初作为风险检测、欺诈预防和实时分析的解决方案而出现。Faust 由 Ask Solem(也因 Celery 而知名)创建,并于 2018 年 8 月发布,率先在纯 Python 中引入了有状态代理、窗口聚合和可查询状态等概念。然而,到 2020 年 10 月,开发工作在没有任何解释的情况下停止,用户只能通过无人回应的问题和停滞的发布节奏发现该项目已被弃置。
Faust 的 API 设计优先考虑 Python 风格的简洁优雅,而不是领域特定语言。核心对象 faust.App 配置了整个流处理环境,包括 Kafka broker、序列化格式和状态存储后端。基于这个 app 对象,开发者可以定义 agent ——通过 @app.agent 装饰的流处理器,从 topic 消费数据,并通过 async for 迭代流。对于熟悉 Python 3.6 引入的 async/await 语法的开发者,这种模式感觉十分自然。
这里给一个应用的例子:
import faustclass Order(faust.Record):account_id: strproduct_id: strprice: floatquantity: float = 1.0app = faust.App('order-processor', broker='kafka://localhost')orders_topic = app.topic('orders', value_type=Order)@app.agent(orders_topic)async def process_order(orders):async for order in orders:total = order.price * order.quantityprint(f"Order {order.account_id}: ${total}")启动 worker 只需一条命令:
faust -A myapp worker -l info无需单独的集群搭建,无需 master 节点,也没有复杂的配置文件。Worker 会自动通过 Kafka 的 consumer group 协议进行协调。
有状态处理 通过 Table 实现——一种由 RocksDB 和 Kafka changelog topic 支撑的分布式键值存储。Table 的使用方式类似 Python 字典,键值会自动分区。group_by 的重新分区模式确保相同 key 的所有事件会落到同一个 worker 上,从而实现一致的状态更新:
order_count = app.Table('order_count', default=int)@app.agent(orders_topic)async def count_orders(orders):async for order in orders.group_by(Order.account_id):order_count[order.account_id] += 1窗口机制 提供了基于时间的聚合:
滚动窗口 (Tumbling):将时间分成固定、不重叠的区间。滑动窗口 (Hopping):允许区间重叠。访问窗口状态的方法包括 .now(处理时间窗口)、.current(事件时间口)、.delta(timedelta)(相对时间范围)。不过用户一致反映窗口机制的文档“不够清晰”,特别是滑动窗口,由于 API 不支持同时更新所有重叠窗口,容易让人困惑。
Faust 自带 Web 服务器,通过 @app.page 装饰函数即可暴露 HTTP 接口,查询 Table 状态、提供健康检查或展示监控面板,这避免了额外开发独立 API 服务的需要。
类型注解贯穿整个代码库,支持 mypy 静态类型检查,在运行前捕获错误。数据模型继承自 faust.Record,并使用 Python 3.6+ 的类型提示定义 schema。序列化层则会根据类型声明自动处理 JSON、二进制或自定义编码。
学习曲线对 Python 开发者来说比其他方案更平缓。文档明确:“你不需要真正理解 asyncio 的内部机制,只需模仿示例即可。”
安装也很简单:
pip install faust-streaming然而,还是有一些痛点:
对不熟悉 asyncio 的开发者来说,async/await 仍然有心理门槛。窗口机制复杂,想要实现高级模式往往需要深入阅读源码。测试存在问题:mock 对象将一切都视为单分区,无法捕捉到多 worker 环境下才会出现的分区与 rebalance bug。高级模式和运维方面的文档不足,团队常常只能依靠“试错”来学习。Faust 的优势源自真正的技术创新和精心的设计选择。它的 Python 原生方式 免去了使用领域特定语言(DSL)或 JVM 封装层的需求。开发者只需用纯 Python 编写熟悉的代码模式——无需 XML 配置,无需单独的拓扑定义,无需切换到 Java 进行调试。与 Python 生态的集成也极具变革性:可以在流数据上应用 scikit-learn 模型、用 Pandas 做转换、利用 TensorFlow 做实时推理——全部在 Python 内完成,无语言边界,也无序列化开销。
对 Python 团队而言,开发效率显著提升。从想法到原型的时间是“小时级”,而非“天级”。学习曲线足够平缓,即便是没有分布式系统经验的数据科学家也能快速上手构建流处理管道。Faust 的 API 设计中也能看出其作者 Ask Solem(Celery 的作者)的经验积累。Kafka 联合创始人 Jay Kreps 甚至公开称赞:“Faust 把一切都做得很 Pythonic,非常棒!”
部署简洁 是另一大亮点。没有 master 节点,就意味着没有单点故障或复杂的集群协调。Worker 通过 Kafka consumer group 协议自动协作。横向扩展只需多启几个 worker,结合 Kubernetes 部署非常直接。内置的 Web 服务器还能提供健康检查和可查询状态,无需单独搭建 API 服务。
有状态处理则首次把复杂流处理特性带到了 Python:
Tables:由 RocksDB 支撑的本地状态存储,结合 changelog 主题实现自动容错;窗口操作:支持基于时间的聚合;备用副本:恢复时间缩短到秒级;可查询状态:通过 REST API 暴露实时计算结果。类型安全也得到了保障:mypy 静态检查可以在运行前发现整类错误,Record 类型的版本化支持 schema 演进。基于 async/await 的并发模型避免了回调地狱与线程同步困境。
在 Robinhood,Faust 确实每天处理数十亿条事件、PB 级别的数据,支撑了风险检测、欺诈防控和面向用户的关键功能。这证明了基于 Python 的流处理在金融级场景中也能跑通,并且达到规模化。
然而,致命的劣势逐渐显现,最终导致项目被放弃。
最大的问题是可靠性:
[Issue #175] 消费者“逐个死亡,直到全部停止响应”,即便 REST API 仍显示“运行中”;[Issue #340] 在启用 exactly-once 配置后,一次崩溃中 30 万条事件丢失了 644 条;[Issue #604] noack 参数未生效,破坏了处理保证。内存泄漏问题严重:
[Issue #169] 即使不处理消息,内存也无限增长;[Issue #433] 内存以每秒 10MB 速度增长,直至 OOM 崩溃;[Issue #646] 即便没有消费 topic 也会泄漏;[Issue #383] Table 与 RocksDB 的使用场景触发泄漏;分析显示 asyncio.Task 对象泄漏速率惊人。这些并非极端个案,而是常见模式,导致 Faust 不适合长时间运行的生产环境。
运维挑战也接连出现:
空闲 30 分钟后延迟从 10–20ms 激增到 10 秒(Kafka 默认 9 分钟连接超时所致);大 changelog 的状态恢复需数分钟;exactly-once 模式下,升级必须同时停掉所有 worker,无法滚动更新;监控 API 会误报“健康”,实际 worker 已冻结。性能受限于 Python 本身:
单核 worker 每秒处理“数万条事件”,远低于 Kafka Streams 的百万级;GIL 限制了真正的并行;单进程的高内存开销降低了部署密度。这些不是 bug,而是 Python 运行时的固有约束。
专业评估公司 Kapernikov 总结了生产现实:“我们很快遇到 Faust 崩溃且无法恢复的情况,或者它虽然没崩溃,却突然停止处理记录……exactly-once 处理并不总是如预期,代码冗长,测试存在问题。” 他们最终放弃 Faust,转而选择 Apache Flink。
Faust虽然技术优雅,却终被生产的可靠性所拖垮。但这仍然不失是流处理领域里的一次很重要的尝试。
Faust一词来自德国大师歌德笔下的文学巨作《浮士德》。故事讲述了浮士德与魔鬼梅菲斯特签订契约,用灵魂换取无限知识和世俗享乐。从项目的命运来看,"浮士德式交易"的隐喻最终成真——Robinhood 获得了 Python 的便利,但最终因为可靠性问题和维护困难而放弃了这个项目,浮士德最终也要为他的选择付出代价。
来源:闻数起舞