在这篇文章中,我们将讨论流式处理所面临的挑战、Keystone 的设计原则、思维模式、架构总览、我们的愿景以及 Keystone 为 Netflix 所带来的核心价值。
单个流式作业:
通过平台管理这些作业:
Netflix 为来自 190 多个国家的 1.3 亿用户提供服务。流式处理平台每天处理数万亿个事件和 PB 级别的数据,以支持日常的业务需求。随着用户数量的持续增长,整个平台需要进行伸缩。
Keystone 路由服务:这个服务负责根据用户的配置将事件路由到托管接收器上。每个传递线路都通过并行流式处理作业来实现。用户可以定义可选的过滤器或投影聚合。事件最终被传递给存储接收器,便于后续的批处理或流式处理(这些处理实现了至少一次语义)。用户可以在延迟和重复处理之间做出权衡。
流式处理即服务:SPaaS 平台只在生产环境中运行了大约一年时间,但我们已经遇到了各种各样的需求。以下是一些常见的问题和权衡。
作业状态:从完全无状态并行处理到需要数十 TB 本地状态存储的作业。
作业复杂性:从将所有 operator 链接在一起的并行作业,到具有多个 shuffle 阶段和复杂会话逻辑的复杂作业 DAG。
窗口 / 会话:窗口大小从几秒钟(即捕获事务的开始 / 结束事件)到数小时的自定义会话窗口。
流量模式:不同用例的流量模式存在很大差异。它们可能是突发的,也可能保持在 GB/ 秒级别不变。
故障恢复:有些用例需要秒级的低故障恢复延迟,当作业持有很大的状态并涉及 shuffle 时,就变得相当具有挑战性。
回填(backfill)和回放(rewind):某些作业需要从批处理数据源重放数据或从先前的检查点回放数据。
资源争用:作业可能会在任何物理资源上产生瓶颈:CPU、网络带宽或内存等。用户依赖平台提供的用于进行性能调整的见解和指导。
重复与延迟:应用程序在重复与延迟方面可能有不同的权衡偏好。
事件排序:大多数用例不依赖严格的排序,但有些确实会依赖排序。
传递和处理语义:某些用例允许管道中丢失一些事件,而其他用例可能要求更高的持久性保证。某些有状态的流式作业期望具备恰好一次处理保证,计算状态需要始终保持一致。
用户受众:我们的用户群十分广泛,从技术娴熟的分布式系统工程师到业务分析师,有些团队选择基于我们的平台产品构建特定领域的平台服务。
Keystone 支持数千个流式作业,从数据传输、数据分析,一直到支持微服务架构模式。因为流式作业的多样性,为了向每个用户提供有意义的服务级别保证,基础设施需要提供运行时和运营隔离,同时还要最小化共享平台开销。
尽管大多数流都具有固定的流量模式,我们仍然需要让系统能够应对突发情况(流行的节目上线或意外故障引起的流量爆发),而且能够自动适应并对这些情况做出响应。
Netflix 的微服务完全是在云端运行的。云具有弹性、持续变化、更高的故障率等特点,因此我们需要让系统能够监控、检测和容忍故障,包括网络不稳定、实例故障、区域故障、集群故障、服务间拥塞或回压、区域灾难故障等。
我们的平台目前为数千个路由作业和流式应用程序提供服务。如果依靠平台团队手动管理所有流,成本会很高。因此,应该由用户负责声明作业的生命周期,同时基础设施应该尽可能自动化。
我们希望能够进行快速的开发和部署,每天可以进行多次部署。我们也希望能够保持用户使用平台的敏捷性。
这个平台的主要目标之一是让其他团队能够专注于业务逻辑,让流式处理作业的实验、实现和运营变得更容易。通过平台将“难啃的硬骨头”抽离出来,消除用户的复杂性,这将极大提升团队的敏捷性并促进产品的创新。
我们努力让用户能够:
快速发现数据和开展试验,通过数据驱动的创新来推动产品的发展;
快速的流式处理解决方案原型设计;
充满信心地进行服务的生产和运营;
深入了解性能、成本、作业生命周期状态等,以便能够做出明智的决策;
进行自助服务。
为了能够让用户专注于业务逻辑而不必担心分布式系统的复杂性或某些预先存在的解决方案的一般性细节,我们需要为用户提供一组可以轻松接入到流式作业 DAG 的可组合 operator。
此外,流式作业本身也可以成为其他下游服务的构建块。我们与一些合作伙伴团队合作,构建“托管数据集”和其他特定领域的平台。
我们还努力通过利用其他构建模块(如容器运行时服务、平台动态配置、通用注入框架等)与 Netflix 软件生态系统深度集成。这不仅有助于我们基于其他现有解决方案构建出新的服务,还让我们的用户更加熟悉开发和运营环境。
任何一个复杂的分布式系统本身都有一定的局限性,因此在设计这种系统时需要考虑到各种权衡,如延迟与重复、一致性与可用性、严格排序与随机排序等。某些用例还可能涉及各种权衡组合,所以平台必须提供调整入口,为个人用户提供定制的可能性,让他们可以声明对系统的需求。
在大规模分布式系统中,故障是一种常态,在云环境中就更是如此。任何设计合理的云原生系统都应该将故障视为一等公民。
以下是影响我们设计的一些重要方面:
假设网络是不可靠的;
信任底层运行时基础设施,但需要自动修复能力;
实现多租户的作业级别隔离;
出现故障时减少影响范围;
出现组件状态漂移或发生灾难故障时能够进行自动调节;
正确处理和传播回压。
在用户和平台之间:用户应该能够通过平台 UI 或 API 声明“目标状态”。目标状态被保存在单个事实源当中,应该由平台作业流程负责处理从“当前状态”到“目标状态”的变化。
在控制平面和数据平面之间:控制平面负责作业流程编排和协调,数据平面负担处理繁重的任务,以确保一切处在目标状态内。
在不同的子组件之间:每个组件负责自己的作业和状态。每个组件的生命周期都是独立的。
运行时基础设施:流式处理作业部署在开源的 Netflix Titus Container 运行时服务上,该服务提供配置、调度、资源级别的隔离(CPU、网络、内存)、高级网络等。
考虑到上述的挑战和设计原则,我们几乎完成了一个声明式的调和架构,用以实现自助服务平台。这个架构允许用户通过 UI 声明所需的作业属性,平台将编排和协调子服务,以确保尽快达到目标状态。
以下部分介绍了平台的架构和平台设计的各个方面。
声明式调和协议被用在整个架构栈上,从控制平面到数据平面。从逻辑上讲,利用这个协议的目的是将用户声明的目标状态的单个副本保存为持久的事实来源,其他服务基于这些事实来源进行调和。当出现状态冲突时,不管是临时故障导致还是正常的用户触发动作,这些事实来源都应该被视为权威,其他所有版本的状态应该被视为当前视图。整个系统最终需要将事实来源作为调和目标。
事实来源存储是一种持久的存储,用于保存所有需要的状态信息。我们目前使用的是 AWS RDS,它是整个系统的唯一事实来源。例如,如果 Kafka 集群因为 ZooKeeper 状态损坏而出现故障,我们可以根据事实来源重新创建整个集群。相同的原则也适用于流式处理层,这使得持续自我修复和自动化运营成为可能。
这个协议的另一个好处是操作的幂等性。这意味着从用户传给控制平面再传给作业集群的控制指令和不可避免的故障条件不会造成长时间的对立面效应。这些服务最终会自行调和,同时也带来了运营的敏捷性。
控制平面通过与 Netflix 内部的持续部署引擎 Spinnaker 发生交互来编排作业流程。Spinnaker 对 Titus 容器运行时集成进行了抽象,控制平面可以以不同的权衡方式来协调部署。
Flink 集群由作业管理器和任务管理器组成。我们通过为每个作业创建独立的 Flink 集群来实现完整的作业隔离。唯一的共享服务是用于达成共识协调的 ZooKeeper 和用于保存检查点状态的 S3 后端。
在重新部署期间,无状态应用程序可以在延迟或重复处理之间做出权衡。对于有状态应用程序,用户可以选择从检查点 / 保存点恢复或从新状态重新开始。
对于路由作业:用户可以通过自助服务请求生成事件(可声明过滤器或投影聚合),然后将事件路由到托管接收器(如 Elasticsearch、Hive)或者让下游实例进行实时的消费。自助服务 UI 从用户那里获取输入,并将其转换为最终期望的系统状态。我们因此可以构建一个能够实现目标状态的编排层,还可以抽离出用户可能不关心的某些信息(例如要发送到哪个 Kafka 集群或某些容器的配置),并在必要的时候提供灵活性。
对于自定义 SPaaS 作业,我们提供了命令行工具用于生成 Flink 代码模板存储库和 CI 集成等。
在用户签入代码后,CI 自动化流程将开始构建 Docker 镜像,并通过平台后端注册镜像,用户可以执行部署和其他操作。
我们目前正在基于 Apache Flink 为 Keystone 的分析用例构建一个生态系统。我们计划集成和扩展 Mantis 流式处理引擎。
为了帮助我们的用户提高开发敏捷性和创新,我们提供了全方位的抽象,包括托管连接器、让用户可以接入处理 DAG 的 operator,以及与各种平台服务的集成。
我们为 Kafka、Elasticsearch、Hive 等提供了托管连接器。这些连接器抽象出了自定义连线格式、序列化、批处理 / 限定行为以及接入处理 DAG 的便利性。我们还提供动态数据源 / 接收器 operator,用户可以在不同的数据源或接收器之间切换,而无需重新构建。
其他托管的 operator 还包括过滤器、投影聚合和易于理解的数据卫生自定义 DSL。我们将继续与用户合作开发更多的 operator,并让更多团队可以使用这些 operator。
多租户配置管理有一定的挑战性。我们希望提供动态且易于管理的配置体验(用户无需重新提交和构建代码)。
托管的配置和用户定义配置都保存在应用程序的属性文件中,这些配置可以被环境变量覆盖,也可以通过自助 UI 覆盖这些属性。这种方法适用于我们的调和架构,用户通过 UI 声明想要的配置并部署编排,确保运行时的最终一致性。
在分布式系统中,故障是不可避免的。我们完全相信故障会在任何时候发生,所以我们的系统被设计成具有自我恢复能力,这样就不必在半夜醒来处理事故。
从架构上看,平台组件服务被隔离出来,以便在发生故障时减少影响范围。调和架构还通过持续调和来确保系统级别的自我恢复能力。
单个作业遵循相同的隔离模式,以减少故障影响。但是,为了处理故障并从故障中恢复,每个托管流式作业都配有健康监视器。健康监视器是运行在 Flink 集群中的内部组件,负责检测故障情况并执行自我修复:
集群任务管理器漂移:当 Flink 容器资源视图与容器运行时视图不匹配时就会出现漂移,通过主动终止受影响的容器,可以自动纠正漂移。
暂停作业管理器首领:如果未能选举出首领,集群就会进入无脑状态,此时需要对作业管理器执行纠正措施。
不稳定的容器资源:如果某个任务管理器出现不稳定的模式(如定期重启 / 故障),它将被替换。
网络分区:如果容器遇到网络连接问题,它将自动终止。
因为故障是不可避免的,所以有时候用户可能需要回填或回放处理作业。
对于备份到数据仓库中的源数据,我们在平台中构建了相应的功能,用来动态切换数据源而无需修改和重新构建代码。这种方法有一定的局限性,建议将它应用在无状态作业上。
或者,用户可以选择回到之前自动保存的检查点开始重新处理。
所有单个的流式作业都配有个性化的监控和警报仪表盘。这有助于平台 / 基础设施团队和应用程序团队监控和诊断问题。
随着平台和底层基础设施服务不断创新,提供越来越多的新功能和改进,快速采用这些变化的压力是自下而上的(架构层面)。
随着应用程序的开发和发布,可靠性的压力是自上而下的。
于是,压力在中间相遇了。为了获得信任,我们需要让平台和用户能够有效地测试整个技术栈。
我们坚持为所有用户进行单元测试、集成测试、金丝雀运营。我们正在这方面取得进展,但仍然有很多问题需要解决。
在过去的一年半中,Keystone 流式处理平台每天可以处理万亿个事件。我们的合作伙伴团队已经用它构建各种流式分析应用。此外,我们也看到了一些建立在 Keystone 之上的更高级别的平台。
但是,我们的故事并未就此结束。要实现我们的平台愿景,还有很长的路要走。以下是我们正在研究的一些有趣的事项:
模式(schema)
让平台交互变得更灵活的服务层
提供流式 SQL 和其他更高级别的抽象,为不同的用户提供价值
分析和机器学习用例
微服务事件溯源架构模式
英文原文:
https://medium.com/netflix-techblog/keystone-real-time-stream-processing-platform-a3ee651812a
点击下方图片即可阅读
Kafka落选!InfoWorld最佳开源数据平台奖公布
极客时间《从 0 开始学架构》专栏,目前专栏已有 2.6W+ 技术人订阅。50 期课程中,资深技术专家李运华从架构基础、三大架构模式和实战的角度,分享他近 10 年从业经历积累的一整套架构设计方法论。目前已经全部更新完,你学习后能快速理解陌生的架构设计,对架构设计游刃有余,照着做你也能成为架构师。
今天,《从 0 开始学架构》专栏推出 24 小时限时福利,拼团价¥68/2 人,仅限 9 月 29 号,30 号立即恢复原价¥99!
现在订阅,还有以下福利:
福利一:订阅专栏,一次性获得专栏全集,附赠《架构师成长技能图谱》一份
福利二:老用户每邀请一位好友购买,可以获得 24 元现金返现,上不封顶,立即提现。
如果你喜欢这篇文章,或希望看到更多类似优质报道,记得给我留言和点赞哦!