在Uber,我们使用强大的数据处理系统,例如Apache Flink和Apache Spark,为流媒体应用程序供电,帮助我们计算最新定价,增强驱动程序调度和对我们的平台进行欺诈。这样的解决方案可以实时地以大规模的规模处理数据完全一切的语义并且在过去几年中,这些系统的出现已经解锁了在低延迟下编写流数据处理应用的行业范围内的能力,以前不可能以规模实现的功能。
但是,由于流传输系统本质上无法保证事件顺序,因此它们必须在处理后期数据的方式进行权衡。通常,流系统减轻了这一点无秩序问题是使用事件时间窗口和水印。虽然高效,这种策略可以通过减少水印后到达的任何事件来引起不准确的。支持需要流管线的低延迟的系统和批量管道的正确性,许多组织利用Lambda架构,首先是一个概念由Nathan Marz提出。
利用Lambda架构允许工程师可靠地回填流媒体管道那但它还需要维护两个不同的码条,一个用于批处理,一个用于流式传输。虽然流流管线实时运行,但批量流水线被安排在延迟间隔以进行最准确的结果进行重新处理数据。虽然Lambda架构提供了许多好处,但它还介绍了必须在流和批处理码库中协调业务逻辑的难度。
抵消这些限制,Apache Kafka的共同创造者Jay Kreps建议使用Kappa架构用于流处理系统。Kreps的关键思想是从结构化数据源重播数据,例如Apache Hive表中的kafka流。然后,此设置只需在这些重播的Kafka主题上重新运行流媒体作业,在批处理和流流管道和生产和回填用例之间实现统一的代码库。
虽然有很多文献,但描述了如何构建kappa架构,但很少有函数案例描述如何在生产中成功将其移除。关于该主题的许多指南省略了讨论在制造建筑决策时工程师需要考虑的性能成本计算,特别是因为Kafka和Yarn集群有限。
在Uber,我们设计了一个kappa架构,以便使用统一的代码库来促进我们的流媒体工作负载的回填。这种新颖的解决方案不仅允许我们更加无缝地加入我们的数据源进行流式分析,但也提高了开发人员的生产力。我们希望读者将从我们的经验教训中受益转换到Kappa架构,以支持优步数据流管道,以改进我们平台的匹配和计算。
动机
我们的管道会话化骑手体验仍然是优步核心业务中最大的有状态流用例之一。我们最初建立它,为许多高级建模用例提供低延迟功能,供Uber的供电动态定价系统。但是,优步的团队发现了多次用途,以便我们对最初目的的会话定义,例如用户体验分析和机器人检测。Uber的数据科学家,分析师和运营管理人员开始在大型时间段内运行后向后分析时将我们的会话定义作为规范会话定义。
流化流水线生产的数据用于在正确性和延迟方面具有大大不同需求的使用情况。有些团队使用我们的会话化系统对需要第二级延迟并优先顺序快速计算的分析系统。在频谱的另一端,团队还利用该管道用于使用案例,这些情况在一个月内商业分析中的一个月内运行分析的时间内使用案例,这些情况会在更长的时间范围内使用更长的时间范围。我们发现,没有强大的回填策略的有状态流管线对于覆盖这种不同用例而不适合。
回填管线通常在合理的时间窗口经过以延迟到达和无序事件之后重新计算数据,例如当骑车者等待速度速度,直到其下一个超级应用程序会话。在这种情况下,虽然流动管道错过了事件,但具有几天的滞后的回填管道可以轻松地将此事件归因于正确的会话。因此,回填管道不仅有用的计数器延迟,而且还用于填补由流管道引起的数据中的轻微不一致和孔。
设计注意事项
建立了对优步的有状态流管道的可扩展回填策略的需求,我们审查了用于建立回填解决方案的当前最先进的技术。
为了我们的第一次迭代回填解决方案,我们考虑了两种方法:
方法1:从Hive重播我们的数据进入Kafka
在此策略中,我们从结构化数据源重播旧事件,例如蜂巢表回到Kafka主题,并在重放的主题上重新运行流式作业,以重新生成数据集。虽然这种方法不需要流媒体作业本身的代码更改,但我们需要编写自己的Hive-to-Kafka重播者。编写一个IDEMPotent的重播者会棘手,因为我们必须确保以原始Kafka主题出现的秩序大致相同的顺序在新的Kafka主题中复制重播事件。使用kafka主题输入重播新的回填工作,不像原始订单可能导致事件时间窗口逻辑和水印不准确。
这种策略的另一个挑战是,在实践中,它会限制我们可以有效地重播到Kafka主题的数天数。Backfilling more than a handful of days’ worth of data (a frequent occurrence) could easily lead to replaying days’ worth of client logs and trip-level data into Uber’s Kafka self-serve infrastructure all at once, overwhelming the system’s infrastructure and causing lags.
方法2:利用Spark中的统一数据集API
自从我们选择火花流,Spark的API用于流处理的扩展,我们利用了我们的有状态流应用,我们还可以选择利用结构化流媒体统一的宣言API.并重新使用回填的流代码。在Spark的批处理模式下,结构化流询问时忽略事件时间窗口和水印运行批处理查询。
虽然该策略实现了最大的代码重用,但在长时间尝试回填数据时,它会停止。对于许多我们的流处理用例,利用结构化流要求,我们需要在单个批处理作业中从多天回收数据,从而强制提供具有多余资源的作业,这些作业可能不一定在共享生产群集中可用。
此外,许多优步生产流水线目前正在从Kafka处理数据,并将其分散回Kafka下沉。然后,下游应用程序和专用的弹性或蜂窝出版商将消耗来自这些汇款的数据。即使我们可以使用额外的资源来启用多天数日数据的单次回填,我们需要实现生成数据的速率限制机制,以便免除我们可能需要对准其回填的下游水槽和消费者与我们的上游管道。
组合方法
我们审查并测试了这两种方法,但为我们的需求既不是可扩展的;相反,我们决定通过找到一种方法来利用这些解决方案的最佳功能,同时减轻他们的缺点。
apache蜂巢到apache kafka重播方法(方法1)可以运行相同的精确流管线,没有代码更改,使其非常易于使用。但是,这种方法需要为每个回填的Kafka主题(例如每个回填的Kafka主题)建立一次性基础架构资源(如专用主题),并将数周重放为您的Kafka集群中的数据。这些任务的纯粹努力和不切实性使得蜂巢到Kafka重播方法难以证明在我们的堆栈中的规模实施。
同样地,以批处理模式运行火花流工作(方法2)在多个日期后回填数据时,由于这种策略可能压倒下游汇和其他系统消耗此数据的策略,而不是使用统一的API在资源约束问题上呈现了我们的资源约束问题。
为了将两种方法综合到适合我们需求的解决方案中,我们选择通过将蜂巢表建模为火花的流源来模拟我们的新流系统作为kappa架构,从而将桌子转换为无限的流。就像这一样卡夫卡来源在Spark中,我们的流媒体源从一个Hive表而不是Kafka主题获取每个触发事件的数据。该解决方案提供了方法1的好处,同时跳过了必须首先将数据重播到临时Kafka主题的后勤麻烦。
这种组合系统还避免了压倒性的下游水槽,如方法2,因为我们从蜂巢中逐步读取而不是尝试单次回填。我们的BrechFininger按照它们发生的顺序计算窗口的聚合。例如,在T0触发的窗口W0之前始终计算窗口W1在T1触发。
此外,由于我们从过去发生的事件流回填,我们可以在生产流水线中的Windows而不是几秒钟'或分钟'之间的捷克数小时的数据。我们通过指定回填特定触发间隔和事件时间窗口,有效地回收数据集。
kappa架构实现
在测试我们的方法后,并决定这两种方法的组合,我们解决了以下原则,用于建立我们的解决方案:
- 流媒体和批处理作业之间的切换应该简单地切换在管道中的Hive中的Kafka数据源。解决方案不应尝试任何其他步骤或专用代码路径。
- 除了切换到Hive连接器,调整事件时间窗口和用于高效回填的水印参数,回填解决方案应该不会对管道的其余部分施加任何假设或改变。
- 事件时间窗口操作和水印应该在回填和生产作业中相同的方式工作。
- Hive连接器应在流媒体作业类型方面同样运行。例如,它应该同样适用于有状态或无状态应用程序,以及事件时间窗口,处理时间窗口和会话窗口。
在回填模式下运行时保留原始流媒体作业的窗口和水印语义(在上面的第三点中概述的原理)允许我们通过按照它们发生的顺序运行事件来确保正确性。这种策略也是通过一次而不是立即回填工作一个窗口来限制速率限制器。由于我们处于回填模式,我们可以控制一个窗口所消耗的数据量,允许我们以比只需使用生产设置的简单运行作业更快的速率来回填。例如,我们可以花一天才能回填几天的数据。
为了演示我们如何为我们的火花流管道实施这两个双管齐全的回填系统,我们建模了一种简单(非回填)的状态流工作,该作业消耗了两个Kafka流。此作业具有十秒钟的事件时间窗口,这意味着每次对作业的水印到十秒钟,它会触发窗口,并且每个窗口的输出都持紧到内部状态商店。
我们通过使用上面概述的原则组合两种方法来更新了此作业的回填系统,导致使用Spark的源API创建我们的Hive连接器作为流源。从本质上讲,我们希望用触发器之间的事件窗口执行蜂巢查询来替换Kafka读取。在重新设计这个系统时,我们还意识到我们每十秒钟不需要查询蜂巢,以获得10秒的数据,因为这将是效率低下。相反,我们从十秒钟到两个小时轻松地放松了我们的水印,因此在每个触发事件中,我们从蜂巢中阅读了两个小时的数据。
在与原则上保持三个中,我们系统的此功能可确保在下游管道上施加任何更改,除了切换到Hive连接器,在回填期间调整事件时间窗口大小和水印持续时间以进行效率。我们实施了这些更改,以将图1中的状态流媒体作业放入带有Hive连接器的回填模式。我们在图2中建模了这些结果:
当我们使用Hive创建回填的Kafka连接器时,我们保留原始流媒体工作的状态持久性,窗口和触发语义符合我们的原则。由于我们可以控制在触发器之间读取的数据量,因此我们可以逐渐回收多天的数据,而不是在一个人中读取所有数据。此功能允许我们使用相同的生产群集配置作为生产状态流工作,而不是在回填工作中抛出额外资源。
比较这两项工作,生产中的作业在75个核心上运行,纱线集群上的1.2核心内存。我们的回填工作回填围绕九天的数据,这在我们的蜂巢群集上的数量大约为10磅的数据。
优步Kappa建筑的未来
在设计可扩展的无缝系统到回收Uber的流媒体管道时,我们发现在生产中实现Kappa架构更容易完成。两个最常见的方法都是从Hive和回程作为批处理工作中重放数据到Kafka的数据并没有扩展到我们的数据速度或需要太多的群集资源。结果,我们发现最好的方法是将我们的Hive连接器建模为流源。
我们在Spark Streaming中实施了此解决方案,但其他组织可以在将该系统设计为其他流处理系统(例如Apache Flink)时应用我们发现的原则。
如果您有兴趣在旨在以刻度处理数据的构建系统,请访问优步职业问题。






