Cherami:Uber Engineering的耐用和可扩展的任务队列

0.
Cherami:Uber Engineering的耐用和可扩展的任务队列

Cherami是我们开发的分布式,可扩展,耐用的和高可用的消息队列系统Uber Engineering.运输异步任务。我们将我们的任务队列命名为一个英雄运营商鸽子,希望这个系统与容易容错,允许优步关键任务业务逻辑组件依赖于邮件传递。

Cher Ami是一名美国陆军运载鸽在第一次世界大战中。尽管在腿上被射击,但她成功地交付了一条帮助节省194年生命的信息。
雪洋葱曾经是一个第一次世界大战中的美军信鸽。尽管腿部中枪,她成功地传达了一个信息帮助节省194年

介绍

任务队列在分布式系统中解耦组件,并允许它们以异步方式通信。然后,两个通信各方可以单独缩放,具有负载平滑或节流的附加功能。在复杂的分布式系统中,任务队列至关重要。Cherami填补了相当于的角色简单的队列服务(SQS)在优步基础设施生态系统中。构建我们自己的系统在满足某些独特的产品开发需求的同时,实现了与现有的基础架构更好地集成,如对多个消费者组的支持以及增加可用性,特别是在网络分区期间。

Cherami的用户定义为生产者或者消费者生产商eNqueue任务。消费者是异步接收和处理延期任务的工人流程。Cherami的交付模式是典型的竞争消费者模式,同一消费者组中的消费者接收不相交的任务集(失败案例除外,导致重新交付)。使用此型号,并行工作粉丝向许多工人出发。工人的数量与凯拉米内部内部的任何分区或分配机制无关,并且只需通过添加或移除工人即可上下扩展。如果工人无法执行任务,则另一个工作人员可以重新选择并重试该任务。

Cherami也支持多个消费组,每个消费组接收队列中的所有任务。每个消费组都与a相关联死信队列。超过最大重试计数的任务(例如,“毒药”)在此队列中的土地,以便消费者组可以继续处理其他消息。这些消费者处理功能既可以区分CHERAMI,从大数据摄取和分析中通常使用的简单消息总线(例如,Apache Kafka.),并使Cherami在任务队列用例中有利。

生产者将任务纳入队列A和B.向两个消费者群组队列,这些组都会接收所有任务,分布在各个组内的消费者中。队列B仅馈送一个消费者组。
生产者将任务纳入队列A和B.向两个消费者群组队列,这些组都会接收所有任务,分布在各个组内的消费者中。队列B仅馈送一个消费者组。

在Cherami之前,Uber使用过芹菜队列支持redis.对于所有任务队列使用情况。芹菜和redis的组合可以快速帮助优化的比例,直到一点。缺点?Celery仅限Python,而我们越来越依赖Go和Java来构建更高的性能后端服务。此外,Redis商店是内存支持的,它不像我们所需要的那样耐用或可扩展。

为了优步的未来,我们需要一个长期的解决方案,所以我们建立了Cherami来满足这些需求:

  1. 硬件故障的耐久性,无损和容忍度
  2. 灵活性可用性和一致性(AP VS CP)在网络分区期间
  3. 能够轻松地提高和降低每个队列的吞吐量
  4. 完全支持竞争消费者消费模型
  5. 语言不可知论

为满足这些要求,凯拉米的设计遵循这些设计原则:

  1. 我们选择最终的一致性作为核心原则。这允许高可用性和耐用性,具有我们不提供订购保证的权衡。但是,这意味着我们可以在灾难性失败或网络分区期间继续接受请求,并通过消除对类似的一致元数据存储的需求进一步提高可用性Zookeeper.
  2. 我们选择不支持分区的消费者模式,我们不会将分区公开给用户。这简化了消费者工作者管理,因为工人不需要协调哪个分区来消费。它还简化了配置,因为生产者和消费者都可以独立扩大。

在下面的章节中,我们将进一步阐述Cherami的关键设计元素,并解释我们如何应用设计原则和权衡。

Cherami的主要设计元素

1.故障恢复和复制

为了真正的无损和可用,Cherami必须容忍硬件故障。在实践中,这需要Cherami在不同的硬件上复制每个消息,以便读取消息,但是当硬件瞬时或永久性故障时,凯拉米必须接受新消息。

Cherami的容错是利用消息传递系统的仅附加属性,并在消息传输中使用流水线。消息队列中的每个消息是一个自包含的元素,一旦创建,永远不会被修改。换句话说,仅留言队列。如果包含队列失败的存储主机,我们可以选择不同的存储主机并继续写入它。戒断操作继续可用。

仅附加属性允许队列在硬件故障期间仍可用于发布。
仅附加属性允许队列在硬件故障期间仍可用于发布。

Cherami队列由一个或多个组成范围,它们是队列中的概念子流,独立支持附加消息。扩展由一个名为的角色复制到存储层输入主机。创建范围时,其元数据包含一个不可变的主机信息元组(输入主机和存储主机列表)。在每个存储主机中,可以调用AcceS的复制副本副本,存储主机可以托管许多不同范围的副本。如果单个存储主机发生故障,我们不会丢失消息,因为在其他副本中仍可读取程度。

Cherami处理存储主机故障。
Cherami处理存储主机故障。

制作人连接到特定的输入主机以在属于某些队列的范围内发布。在从生产者接收消息时,输入主机同时地通过a将消息流水送到一定程度的复制品WebSocket.连接,并接收确认(ack.)从相同的连接中的各个副本。

流水线意味着输入主机在编写下一条消息之前,输入主机不等待ACK,并且在输入主机和所有副本之间没有消息重新排序或消息跳过。这也适用于从每个副本返回的ACK;ACKS按照相应写入的顺序。输入主机跟踪所有ACK。只有在接收到相同消息的所有存储主机ACK接收到生成器的输入主机ACK。此最终ACK意味着该消息已持久存储在所有副本中。

在每种程度上,由于流水线属性,消息被命令。这可确保跨所有副本的消息一致,除了存储主机尚未持续到消息的尾部。

输入主机只接收来自所有存储主机的前三条消息的ACK。它将前三条消息ACKS到生产者身份,因为这些消息被保证完全复制。
输入主机只接收来自所有存储主机的前三条消息的ACK。它将前三条消息ACKS到生产者身份,因为这些消息被保证完全复制。

当任何副本发生故障时,输入主机无法从该副本中接收ACK,以便进一步写入。因此,这种程度不再是附加的。如果输入主机发生故障,我们将丢失存储主机中的发机会。在这两种情况下,副本的尾部可能不一致:在所有副本中都没有复制一个或多个消息。要从这种不一致中恢复,而不是尝试扫描和修复尾部,这是一个复杂的操作,我们只是在“密封”的情况下宣布这一范围;它是可读的,但不再允许写入。

密封之后,Cherami为这个队列创建了一个新的区段,一个信号通道通知生产者重新连接并发布到新的区段中。如果一个队列只包含一个打开的区,那么密封它将使该队列暂时不可用,无法在创建新区之前短时间内发布。为了避免在失败期间出现发布延迟峰值,队列通常会设置最小的区段数量,以便在密封一个区段并创建一个新区段时可以继续发布。

我们选择使用密封作为恢复机制,因为它很容易实现。这里的权衡是可以发生重复的。重复项的原因是,在失败后,副本尾部将包含未粘连到发布者的消息,如果输入主机失败,则无法确定unch unch。因此,在读取路径中,我们必须提供所有内容,包括这些unched消息。出版商通常在失败时重试留言,因此这些消息中的一些可以在新的范围内重新发布,这导致消费者接收重复。

2.缩放写道

Cherami内的范围是共享的 - 没有子流。Cherami在每种程度上观察吞吐量。作为对特定队列的写入负载增加,有些范围超过其吞吐量限制,Cherami会自动为该队列创建其他范围。新扩展名接收部分写入负载,减轻现有范围的负载。

写的缩放

作为写入负载减少,Cherami密封一些范围,而不用新的范围替换它们。在这种方式,Cherami减少了在开放范围内需要的一些开销(内存,网络连接和其他维护)。

自动缩放上下

3.消费处理

同一消费者组中的消费者从相同的队列中获得任务,但可能从一个或多个范围收到。当消费者收到消息并成功处理它时,消费者与ACK回复Cherami。如果Cherami在某些配置的时间后没有收到ACK,则它将重新递送邮件以重试。当消费者崩溃时,消费者的ACK可以延迟或丢失,当一个下游依赖性不可用时,当单个任务需要太长时,或者由于僵局而被困扰时。消费者也可以负责否认,或,消息,触发立即重新交货。NACKS允许消费类别处理某些成员无法处理的任务(例如,由于本地故障,消费者组的部分/滚动升级到新的任务模式)。

由于不同的消费者可以采取不同的时间来处理消息,因此ACKS以不同的顺序到达Cherami,而不是复制品提供的订购。一些消息传递系统每条消息存储读/未读状态(也称为可见状态)。但是,要做到,我们需要在磁盘上更新这些状态(随机写入),并处理每个消费者组中的每组执行此操作的复杂性。

Cherami采取了不同的方法。在每个消费群体中,对于每个范围,我们都维持一个ack抵消,这是一个消息序列号,所有消息都已被搁置。我们有一个叫做的角色输出主机消费者连接到以获得交付。输出主机顺序地从存储主机读取消息,使它们保持在内存中。它可以跟踪飞行中的信息(传送到消费者,但尚未诊断)并在可能的情况下更新ACK偏移量。输出主机也跟踪定时和NACK,以便根据需要将消息重新加为另一个消费者。在Cherami中,多个消费者组可以同时消耗一定程度,因此多个输出主机可能读取相同的程度。

输出主机处理来自工人的乱序ack。
输出主机处理来自工人的乱序ack。

此外,该系统被配置为将每个消息重新释放有限的次数。如果达到重新交货限制,则该消息将发布到a死信队列并且该消息被标记为已ack,以便ack偏移可以前进。这样,就不会有“毒丸”消息阻塞队列中其他消息的处理。消费者组所有者可以手动检查DLQ中的消息,然后用两种方式之一处理它们:清除它们或合并它们。清除它们将删除消息,当消息无效或没有值时(例如,它们对时间敏感),清除它们是合适的。所有者可以将它们合并回用户组,当用户软件已经修复以处理以前无法处理的消息,或者瞬时故障条件已经消退时,这是合适的。

4.存储

Cherami中的消息持久地存储在磁盘上。在存储主机上,我们选择了RocksdB.作为用于性能和索引功能的存储引擎,我们使用单独的RockSDB实例使用共享LRU块缓存。消息存储在数据库中,将序列号的增加作为键,并且消息本身作为值。由于键始终增加,RockSDB优化其压实,以便我们不会遭受写入放大。什么时候输出主机从一定程度上读取消息,只需寻求ack抵消对于消费者组,它是服务,并通过序列号迭代以读取更多消息。

使用RocksDB,我们还可以轻松地实现计时器队列,即每个消息都与延迟时间相关联的队列。在这种情况下,消息只在指定的延迟之后才被传递。对于计时器队列,我们构造密钥以高阶位包含交付时间,以低阶位包含序号。由于RocksDB提供了一个排序迭代器,键按交付时间的顺序进行迭代,而较低位的序列号保证了键的唯一性:

按交货时间顺序迭代键

系统架构

Cherami由几个不同的角色组成。除了我们已经介绍的输入,存储和输出角色,还有控制器,前端。典型的Cherami部署包括每个角色的多个实例:

Cherami系统组件的互动。
Cherami系统组件的互动。

不同的角色可以存在于同一物理主机上,甚至可以链接到单个二进制中。在Uber,每个角色都在单个Docker容器中运行。输入,存储和输出形成系统的数据平面。控制器和前端句柄控制平面功能和元数据操作。

控制器

控制器是宏协调员,智能协调所有其他组件。它主要确定何时创建和在哪里放置(到哪个输入以及哪些存储主机)。它还确定哪个输出主机处理消费者组的消耗。

所有数据平面角色通过RPC调用将加载信息加载到控制器。通过此信息,控制器使得放置决策和平衡负载。这个控制器角色有几个实例,其中一个人使用优步的领导者林蛙用于八卦和一致哈希的库。Ringpop还执行分布式健康检查和成员功能。

前端

前端主机公开TChannel.-节约表演的APIcr队列和消费者群体的运作。它们还会为数据平面路由目的公开API。当生产者想要将消息发布到队列中时,它会调用路由API以发现哪个输入主机包含队列的范围。接下来,生产者使用WebSocket连接连接到这些输入主机,并在已建立的流中发布消息。

同样,当消费者想要从队列中消耗消息时,它首先调用路由API以发现哪个输出主机管理队列的范围的消耗。然后,生产者使用WebSocket连接连接到这些输出主机并拉动消息。当创建新的extents时,Cherami将向生产者和消费者发送通知,以便它们可以连接到新的范围。我们开发了客户端库以简化这些交互。

Cassandra和排队

最后,Cherami存储元数据卡桑德拉,它是单独部署的。元数据包含关于队列、它的所有区段和所有消费者组信息,比如每个消费者组每个区段的ACK偏移量。我们选择Cassandra不仅是因为Cassandra是一个高可用性的数据存储系统,还因为它的可调一致性模型。这种灵活性允许我们提供既能容忍分区又不能保持顺序的队列(AP队列),或者在这样的分区事件中不能在小分区中保持顺序的队列(CP队列)。处理两种类型队列的主要区别在于,区段创建是否需要条件更新操作。

AP队列

对于AP队列,范围创建不需要Cassandra中的Quorum级一致性。发生网络分区时,可以在分区的两侧创建范围。让我们调用分区A和B.分区中的生产者可以在该分区中发布到范围内,分区B中的生产者可以在分区B中发布到范围。因此,网络分区不会阻止写入。对于读取,分区A中的消费者只能从该分区中的范围内消耗,并且对于分区B中的消费者类似的消费者。然而,当网络分区治愈时,消费者能够达到所有范围。这里的权衡是消息最终 - 一致:无法建立一个全局排序,因为可以随时随地创建范围。在我们的实施中,我们在编写元数据时使用Cassandra一致性级别“一个”。

CP队列

对于CP队列,范围创建需要是可直思化的:在网络分区的情况下,我们必须确保只有一个分区可以创建成功以前密封的分区。为确保此项,我们使用Cassandra的轻量级交易,以便在任何原因创建多个程度的同时,只能为CP队列使用一个以上。

凯明,总结

Cherami是一个具有竞争性的消费者消息队列,它持久、容错、高可用性和可扩展性。我们通过跨存储主机复制消息来实现持久性和容错性,通过利用消息队列的仅追加属性并选择最终一致性作为基本模型来实现高可用性。Cherami也是可扩展的,因为设计没有单一的瓶颈。

Cherami的设计和建造只用了我们六个月的时间西雅图工程办公室。目前,Cherami每天在优步工程中运输数百万百万个任务许多microservices,帮助使用案例,例如后行程处理,欺诈检测,用户通知,激励运动以及许多其他用例。

Cherami完全是写在去,一种使高度性能和并发系统软件建立高度乐趣的语言。此外,Cherami使用优步已经开放的几个库:TChannel.对于RPC和林蛙用于健康检查和团体成员资格。Cherami取决于几个第三方开源技术:卡桑德拉对于Metadata Storage,Message Storage的RockSDB,以及GitHub上可用的许多其他第三方Go软件包。我们计划开源凯拉米在不久的将来。

编辑更新2017年1月3日Cherami现在在以下链接下开放:github.com/uber/cherami-server.github.com/uber/cherami-client-go.

徐宁是一个工程经理这篇文章是和工作人员马克西姆·Fateev合作写的软件工程师。两者都是基于的优步的西雅图工程办公室

标题的照片学分:“Paloma.PabloIbañez.,许可cc-by 2.0。图像裁剪用于标头尺寸和颜色校正。

鸽子图片介绍:美国信号兵团通过史密森机构,公共领域。

注释