虫洞:Facebook上的pub-sub系统

互联网 2019-02-25 16:03:22

简介

pub-sub系统能够有效地解决系统中通信变化的问题。每当某个数据被更改时,发布者就会通知订阅者。在一个使用数据库来存储状态的经典应用程序中,数据的生产者可以写入数据库。为了收集这些更新,订阅者会在一段时间内以运行陈旧数据的成本为代价和以牺牲数据库本身的性能开销为代价。Pub-sub系统通过一般引入中间代理来解决这个问题。发布者向经纪人发布关于变更的信息,并向经纪人订阅注册表,以获得变更通知。

在Facebook,许多应用程序都依赖于这样的更改通知。Newsfeed可能想要接收最新的更新,朋友可能想要访问通知内容。此外,即使是内部系统,如索引修改或缓存失效,也可以通过了解底层数据的变化或使数据无效而受益。这显然会在内部生成大量的数据。这个数据是存储在多个集群内部分片的,如RocksDB,HDFS,MySQL,使用基于代理的系统将意味着代理需要支持Facebook的内部系统和规模非常高的体积也会有大规模复制。为了解决这些限制,Facebook提出了一个名为“虫洞”的新系统。

在虫洞中,订阅者直接在数据系统中注册。所有的数据系统都维护事务日志。虫洞使用这些日志来识别更新,然后将这些更新传播给订阅者。这是非常独特的,并且完全消除了对中间代理的需求,但这意味着发布者可能需要专门针对不同的数据系统。在这篇文章中,我们将讨论虫洞的高层建筑。

在Facebook上对一个pubsub系统的挑战

你可以想象,很多数据都是在Facebook上生成的。为了使其具有可靠性,这些数据在数据中心之间共享和复制。此外,在facebook有许多数据系统——Tao优化读取重图数据和读取重负荷工作负载的memcache。这样的系统使用激进的缓存来卸载数据库访问。在写操作时,这些缓存副本就会失效,或者如果数据库上有索引,那么就需要重新构建。此外,一个pub-sub系统可能需要支持以下内容:

·相同的碎片在交付中要进行更新

·至少一次交付——没有丢失任何更新,但是可能会交付一些副本。

·不同的消费者可能有不同的存储空间和消费需求。

·与任何大型分布式系统一样,它需要具有容错能力,软件和硬件异常是正常的。

体系结构

带有发布者和订阅分片之间多对多映射的虫洞架构

虫洞由一个数据存储组成,它被扩展以满足facebook的需要。每一个这样的数据存储,MySQL或TAO都运行着一个发布者。这是虫洞出版社。该发布者知道使用数据生产者编写的事务日志的内容。然后,发布者将这种本地数据存储格式转换为标准化的键值格式,最后将其交付给订阅者。发布者通过读取zookeeper(配置文件来查找该数据存储的用户。订户或应用程序获得一个客户端虫洞库,它就在zookeeper注册。订阅者的一个有趣的方面是,对于一个给定的应用程序,有多个订阅者,他们被分配来处理发布者的一些总体数据的分片。发布者将特定分片的所有权分配给特定的订阅者,然后将分片的更新发送给相应的订阅者。对于给定的分片,所有更新总是在相同的订阅者上。发布者确保来自给定分片的所有更新都是按顺序发送给订阅者的。

在这一点上引入一些术语是很好的,这将在后面的部分中很有用。

Datastore为数据集存储了一个分片集合。

Flow:由发布者发送给订阅者的每一个分片的更新流。它在每次更新中包含分片编号。

Datamarker:它表示订阅者最后成功确认的流量。这是指向给定数据存储的事务日志的指针。出版商存储数据,以确保至少一次向订阅者发送数据。

读者:将读取从事务日志中读取到发布者的组件。下一节将详细解释。

Caravan 和 leader:一个读者和相关的流程被称为“Caravan。因为多个读者可以同时从交易日志中读取数据,所以读过最远的读者就叫做“领头的Caravan”。

向订阅者交付更新

发布者阅读zookeeper文件,以发现哪些应用程序对更新感兴趣。对于每一个分片和应用程序都会创建了一个更新流,即Flow。一旦处于稳定状态,读者就会从最当前的位置读取信息并发送更新。读者以一种有效的方式帮助管理事务日志访问。当恢复正在进行时,从将多个数据标记中读取数据。为了避免对磁盘进行颠簸,读者可以更有效地管理数据访问。希望这能解释Caravan的概念。

此外,出版商试图不让订户不堪重负。因此,发布者可以使用加权随机选择将给定的Flow发布给一个轻加载的订户。此外,订阅者可以更新zookeeper文件,将负载转移到其他订阅者。一旦发布者建立了Caravan和订阅者之间的流程,那么所有的更新久都通过TCP发送,以获得可靠的订单交付。虫洞还通过相同的连接多路传输许多更新,以减少连接开销。

Multiple Caravan和慢速消费者

一般来说,主要的Caravan从最当前的时间来满足大部分的更新和阅读。但是,无论是在恢复过程中,还是在缓慢的消费者中,都需要大量的Caravan来发送旧的更新。将Caravan分配给流量取决于输入/输出负载和延迟需求。如果有大量的Caravan,他们可以任意地访问磁盘并造成严重的输入/输出负载。但是他们在相同的流中打开了更快速的更新。另一方面,如果有较少的Caravan数量,则可以通过对磁盘访问进行优化,从而更好地优化输入/输出负载。但这可能会导致延迟时间的增加——特别是对于以后的更新或最近的更新。虫洞允许Caravan根据这两个参数进行分割和合并。通常情况下,非领头的Caravan会以更快的速度阅读,这样他们就能赶上领头的Caravan。还有一些配置限制,可以通过限制阅读速度、商队数量等来避免过多的磁盘使用。

过滤

一个很好的特性是应用程序/订阅者指定发布者要过滤的内容。这可以通过使用简单和/或基于过滤规则的配置来完成。发布者不会将这些更新发送给订阅者应用程序。

与发布者故障转移的可靠交付

facebook的大部分数据都是为了可靠性而进行的地理复制。订阅者可以注册一个数据集的多个副本。当出版商面临着像机器艰难的失败时,另一个出版商需要帮助恢复。在这种模式下,虫洞至少保证了从某些数据集的所有更新的交付。这带来了一些挑战。

当一个发布者经历一个永久的失败时,通常所有存储在同一台机器上的datamarkers也变得不可用。同时,出版商也不会为了避免开销而相互沟通。为了克服这些限制,虫洞使用zookeeper来储存数据。

考虑一下地理复制数据库的下图。有两个出版商P1和P2。P1最初拥有A1的流量。当P1更新A1用户时,它在zookeeper中存储datamarkers。此外,每个发布者都有一个对应的临时节点,其他发布者也可以看到。分布式协作中的临时节点一旦它的所有者离开,就会消失。在这种情况下,一旦P1死亡,P1短暂节点就会消失,P2将通过Zookeepr API获得它的通知。现在P2可以接管流的所有权,并通过存储的datamarkers开始发布到A1。这里还有一个实现细节,那就是datamarkers是指向事务日志的指针,而这些事务日志不能被P2直接使用。因此,虫洞增加了逻辑偏移量来帮助解决这个问题。

发布服务器故障转移由正在监视临时zookeeper节点的备份发布服务器监视

一些操作上的改进包括使用分布式部署模型,在同一台机器上使用监视器,而不是使用集中式部署模型。此外,所有的配置都是动态的,这样它并不总是需要重新启动新配置才能生效。

类似的pub-sub系统

大多数类似的pub-sub系统,如Kafka,RabbitMQ使用中间代理。这些经纪人可以根据消费者的速度存储和转发消息。这在Facebook中是不需要的,因为它们的数据存储已经保存了一个可靠的日志上。此外,数据的规模使得一个中间代理的开发同样具有挑战性,并且可能增加额外的延迟。

Google的Thialfi只适用于版本号,用于数据的失效。虽然虫洞可以用于数据重新填充,但是它会发送数据更新。如果中间人失败,Kafka可能会丢失数据,而且比虫洞的速度慢。Apache对冲基金不会与太多的出版商和订阅者进行扩展。对于facebook来说,各种消息队列实现的伸缩性不佳,因为它限制了地理复制,或者它们不仅仅是通用的。

结论

尽管更复杂,但虫洞提供了一种非常独特和有效的机制,可以使用事务日志在数据密集型和高吞吐量环境中实现pub-sub系统,我发现它与传统的pub-sub系统实现的方式非常不同。

相关资讯Relevent