在分布式系统中,重试不可避免。从网络错误到复制问题甚至在下游依赖项中的中断时,必须准备以大规模操作操作的服务,以遇到,识别和处理尽可能优雅的故障。
鉴于优步操作的范围和步伐,我们的系统必须在智能故障失败时是容错和不妥协的。要完成这一点,我们利用ApacheKafka.是一个开源分布式消息传递平台,该平台已被实业化,用于以尺度提供高性能。
利用这些属性,UBER保险工程团队通过使用非阻塞请求再加工和未封锁请求和死信队列(DLQ)以实现解耦,可观察到的误差处理,而不会中断实时流量。该策略有助于我们的选择司机伤害保护计划在200多个城市可靠地运行,每次登记司机扣除每英里溢价。
在本文中,我们突出了我们在大型系统中重新处理请求的方法,实时SLA并分享经验教训。
在活动驱动的架构中工作
驾驶员伤害保护的后端位于Kafka消息传递架构中,通过Java服务运行,该架构通过Uber较大的微服务生态系统中的多个依赖项进行了挂钩。然而,为了本文的目的,我们更专门地关注我们的重试和死字刻字的策略,以其通过管理预订的理论应用程序,以便为蓬勃发展的在线业务管理不同产品的预订。
在此模型中,我们想要两者)付款,B)为每个用户的每个产品预订创建单独的记录捕获数据以生成实时产品分析。这类似于我们的程序后端架构处理的单个驾驶员伤害保护行程如何具有实际充电组件和为报告目的而创建的单独记录。
在我们的示例中,每个功能都通过其各自服务的API提供。图1,下面,在两个相应的消费类别中模拟它们,都订阅了与预订事件的相同频道(在这种情况下,Kafka主题预购):
用于实现重试的快速和简单的解决方案是在客户端呼叫点使用反馈周期。例如,如果图1中的支付服务正在遇到长期延迟并开始抛出超时异常,则商店服务将继续调用付款在一些规定的重试限制下 - 或许具有一些退避策略 - 直到它成功或达到另一种停止条件。
简单重试的问题
在通过反馈周期重试客户级别的客户级别可以有用,在大规模系统中重试可能会受到以下内容:
- 堵塞批处理。当我们需要实时处理大量消息时,重复失败的消息可以CLOG批处理处理。最糟糕的罪犯始终如一地超过重试限制,这也意味着他们需要最长并使用最多的资源。如果没有成功响应,Kafka消费者不会提交新的偏移量,并且将阻止具有这些不良消息的批次,因为它们再次重新消耗,如下图2所示。
- 检索元数据难点。在重试时获得元数据可能很麻烦,例如时间戳和NTH.重试。
如果请求继续在重试后重试,我们希望在DLQ中收集这些故障以获得可见性和诊断。DLQ应允许列出用于查看队列的内容,清除清除这些内容,并合并以便重新处理死字母消息,允许对受共享问题影响的所有失败进行全面分辨率。在优步,我们需要一种重试策略,可以可靠,可扩展为我们这些能力。
在单独的队列中处理
要解决批量批次的问题,我们使用单独定义的KAFKA主题设置不同的重试队列。在此范例下,当消费者处理程序在一定数量的重试后返回给定消息的失败响应时,消费者将该消息发布到其对应的重试主题。处理程序然后返回真的到原始的消费者,它提交其偏移量。
消费者成功从成功的处理程序响应中重新定义,意思是零故障,建立消耗的消息的结论结果,这是预期的响应或其放置在其他地方进行单独处理。
在此类系统中重试请求非常简单。与主要的处理流一样,单独的重试消费者组将读取其相应的重试队列。这些消费者的行为类似于原始架构中的那些,除了他们从不同的Kafka主题中消耗。同时,通过创建多个主题来实现执行多次重试,其中包含针对每个重试主题的不同的侦听器集合。当特定主题的处理程序返回给定消息的错误响应时,它将将该消息发布到它下面的下一个重试主题,如图3和4所示。
最后,DLQ被定义为此设计中的线端Kafka主题。如果最后重试主题的消费者仍然没有返回成功,那么它将将该消息发布到死信主题。从那里,可以使用许多技术用于列出,清除和从主题合并,例如创建由其自己的消费者使用偏移跟踪的命令行工具。死信消息被合并以通过发布回第一个重试主题来重新输入处理。这样,它们仍然是分开的,无法阻碍现场交通。
重要的是不要简单地重新尝试失败的请求之一,另一个之一;这样做将放大呼叫数量,基本上垃圾邮件不良请求。相反,每个后续的重试消费者级别都可以实施处理延迟,换句话说,随着消息通过每个重试主题的步长而增加的超时。这种机制遵循一个泄漏的桶模式其中流速由重试队列内的延迟消息消耗的阻塞性表达。因此,我们的队列并不是如此重试队列,因为它们是延迟处理队列,其中重新执行错误情况是我们的最大的努力交货:处理程序调用将至少发生在配置的超时后,但可能稍后。
我们通过基于队列的再加工获得了什么
现在,我们讨论了我们所描述的方法的好处,因为它涉及确保可靠和可扩展的再加工:
未锁存的批处理
失败的消息输入了自己的指定频道,使得在同一批处理中启用成功,而不是要求它们与故障一起重新处理。因此,传入请求的消耗向前移除,实现更高的实时吞吐量。
去耦
独立的工作流在同一活动中运行,每个都有自己的消费者流动,并分开重新处理和死信队列。在一个依赖关系中失败不需要重试对成功的其他人的特定消息。例如,在图1中,如果报告已结束,但付款成功后,只需要重新尝试和潜在死信。
可配置性
创建新主题实际上没有开销,并且对这些主题产生的消息可以遵守相同的模式。与每个重试通道一起,可以在易于写入的更高级别的消费类别下管理,该类别由Config读取和发布到(在发生故障时)的主题名称时管理, as well as the length of the enforced delay before executing an instance’s handler.
我们还可以区分对不同类型的错误的处理,允许诸如网络的案例进行重新尝试,而Null指针异常和其他代码错误应该直接进入DLQ,因为重试不会修复它们。
可观察性
消息处理分段为不同主题促进了错误的消息的路径,何时以及消息已重试的次数以及其有效载荷的确切属性的何时以及多次追踪。与重新处理主题和DLQ相比,将生产速率监测到原始处理主题中,DLQ可以为自动警报和跟踪实际操作正常运行时间提供通知阈值。
灵活性
虽然Kafka本身是在Scala和Java中编写的,但Kafka以多种语言支持客户端库。例如,Uber使用的许多服务用于他们的Kafka客户端。
Kafka消息格式,具有序列化框架,如Avro.支持无法扩张的模式。如果需要更新我们的数据模型,则需要最小的重新引发来反映此更改。
性能和可靠性
默认情况下,Kafka在至少一度的语义上提供。这种耐用性保证在容错和信息故障的背景下非常有价值;谈到提供业务关键数据(如优步的情况),消息无损是至关重要的。此外,Kafka的并行模型和基于拉力的系统使得能够高吞吐量和低延迟。
其他考虑因素
由于Kafka仅保证分区内的秩序处理而不是跨越它们,因此应用程序必须可以接受应用程序以处理它们发生的确切顺序之外的事件。此外,在至少一旦消息传递需要消费者依赖性幂幂,则是任何分布式系统的共同特征。
上一节中概述的优势具有显着的好处,但在线和实施可能因使用情况而异。例如,根据给定的应用程序句柄的数据类型数,每个事件类型的每个工作流的一组主题可能导致大量的主题来管理。在这种情况下,对基于计数的队列的替代方案可能是用其他字段包装事件类型,从而以更可管理的方式跟踪重试数和时间戳。此权衡需要一些重新考虑如何进行调度,因为这通过队列梯形图管理。
向前进
使用基于计数的Kafka主题作为单独的再处理和死字法队列使我们能够在基于事件的系统中重试请求,而不会阻止批量消耗实时流量。在此框架内,工程师可以根据需要配置,增长,更新和监控,而不会在开发人员时间或应用程序正常运行时间内进行惩罚。
本文中描述的设计坐在我们身后司机伤害保护计划,作为一部分推出优步的180天的变革运动。如果您有兴趣构建此服务背后的可靠和可扩展的系统和其他人的驱动程序,请考虑申请职务在我们的团队!





