几天前,我不得不设计一个基于海量写入的扇出架构。
对于这个学派的新手来说,我会尝试用非常简单的方式去解释。基于海量写入的扇出架构尝试在写入时使用所有业务逻辑。初衷是为了给每个用户及用例准备好视图;当有人想要读取数据时,他们不必应用复杂的逻辑。于是读取就会变得轻松简单且通常可以保证恒定的读取时间。Twitter就基于海量写入的扇出架构。
不必深入了解这些要求的细节,我在此处列出了简单的摘要:
我们设计的架构涉及三个数据库。MongoDB用于存储传入数据、Redis用于存储专为每个用户设计的数据集、ElasticSearch用于存储需要自由文本或部分文本搜索的文本结果。
对于每个传入的数据集都有业务逻辑决定在Redis中填充哪些数据集(基于社交图连接)以及决定在ElasticSearch中提取和存储哪些东西进行自由文本搜索。
听起来很简单!
鉴于此,我决定使用快速可靠的Apache Kafka作为消息代理,然后使用Storm处理数据并实现基于海量写入的扇出架构。
细节决定成败。这就是我打算在这里分享的内容。在使用Kafka和Storm之前,您应该了解一些关于每个应用的知识。
卡夫卡是一个优雅的消息队列。您可以将其用作发布 - 订阅或广播。它是如何完成它的工作的?
下面是解释相同信息的官方文档:
“消息传统上有两种模式:队列和发布 - 订阅。在一个队列中,消费者池可以从服务器中读取消息且每条消息都发送到其中一个服务器上;在发布 - 订阅模型中,消息被广播给所有消费者。Kafka提供了概括了这两个模型的单一消费者抽象——消费群体。
消费者用消费者组名称标记自己,并且发布到主题的每条消息都被传递至在每个订阅消费者组内的一个消费者实例。消费者实例可以在单一进程中或单一机器上。
若所有消费者实例具有相同的消费者组,那么这就像传统的消费者队列负载均衡一样工作。
若所有消费者实例具有不同的消费者群体,那么它就像发布 - 订阅一样工作,并且将所有消息广播给所有消费者。“
快速总结Kafka的显着特点
了解了这么多信息,我们就可以根据分类来创建主题。对于每种新型数据,我们都将新建主题。例如,如果我们使用Twitter,我们可以创建一个名为“推文”的主题。我们会将所有推文创建数据推送到这个主题中。但是跟随用户是完全不同的用例。根据分类理论,我们将为此创造一个新的主题,称之为“跟随”。所有与用户行为相关的数据都将发送到这个新的“跟随”主题中。
现在让我们看看排序。排序仅在主题的分区内被保证且每个主题可以有多个分区。消息只能转到主题中的一个分区。
鉴于此,我们如何实现持续的排序呢?打个比方,让我们以Twitter为例。如果您有10条推文,而您希望按照相同的时间顺序查看它们。
所以现在给出了两个选项。一个选项是每个主题仅包含一个分区并拥有很多主题。例如,为每个用户提供一个主题。只有这样使用一个分区,您才可以始终保持消息的顺序。但这将产生数以亿计的主题(每个用户一个主题)。
另一种选择是为每个用户分配一个主题和一个分区。通过这种方式您也可以确定顺序,但这意味着一个主题和数亿个分区。
现在我们了解到,这两种方法都不是最佳答案。太多主题或分区导致了性能问题。若您阅读架构的话,很显而易见的是它们都会造成开销进而降低性能。我不会去讨论为什么会发生这种情况,而是告诉您我们是如何解决它的。
每个生产者都可决定使用主题中的哪个分区发送数据。这让我们得以选择固定数量的分区并将用户均匀分配到这些分区上。我们发现平均商品硬件和3节点集群及15000分区是最佳选择。这是经过诸多性能测试和优化的结果。所以我们将用户输入内容均匀分配到15000个分区之中。我们没有为每个用户分配一个分区,而是将固定的一组用户分配到了一个分区。这使我们能确保在没有数百万个分区的情况下进行用户排序。
Storm是一个实时处理引擎。它很像映射归纳,只是它一直处于运行状态。因此它是实时的。如果您需要这样的引擎的话,您可以让平行的工作单元处理数据并在批处理结束时累积数据。Storm中使用的术语是“Bolts(螺栓)”和“Spouts(喷口)”。可配置螺栓和喷口在一个的单元中运行的则称为“Topology(拓扑)”。
但真正的问题是确保一次保证处理。意思是,您该如何保证在Kafka队列内只读取一次消息并成功处理。若正在处理的消息抛出异常而您想再次重新处理该消息又会发生什么情况。
Storm中对螺栓和喷口的抽象称为Trident(三叉戟),就像Pig for Hadoop一样。其具体实现称为“OpaqueTrident(不透明三叉戟)”。不透明三叉戟喷口保证仅处理一次且Storm的最新官方版带来了“OpaqueTridentKafkaSpout(不透明三叉戟Kafka喷口)”特性。我们使用它且只保证一次处理来自Kafka的信息。
另一个重要的问题是解决如何应对失败处理。警告将抛出一个“new FailedException()”。失败异常将不会标记信息为已处理,故信息将会被重新处理。这可以确保当由于网络问题或类似用例而导致与数据库的临时连接丢失时不会丢失消息。但请要小心处理并确保在信息正在被处理的情况下不写入重复数据。
这些是从我们的系统中所学习到的。虽然它是一只野兽,但是若明智地使用将效验如神。
希望能帮助到您。
谢谢,
南