当开发者通过 API 生产 Twitter 的公共数据时,他们须要取得可靠性、速度和稳定性方面的保障。因而,在不久前,咱们推出了 Account Activity Replay API 帮忙开发者们晋升他们零碎的稳定性。这个 API 是一个数据恢复工具,开发者能够用它来检索最早产生在 5 天前的事件,复原因为各种起因(包含在实时传递时忽然产生的服务器中断)没有被传递的事件。
除了构建 API 来晋升开发者体验,咱们还做了一些优化:
• 进步 Twitter 外部工程师的生产力
• 放弃零碎的可维护性。具体来说,就是尽量减少开发人员、站点可靠性工程师和其余与零碎交互的人员的上下文切换
基于这些起因,在构建这个 API 所依赖的回放零碎时,咱们利用了 Account Activity API 现有的实时零碎设计。这有助于咱们重用现有的工作,并最小化上下文切换累赘和培训工作。
实时零碎采纳了公布和订阅架构。为了放弃架构的一致性,构建一个能够读取数据的存储层,咱们想到了传统的流式技术——Kafka。
背景
两个数据中心产生实时事件,事件被写入到跨数据中心的主题上,实现数据冗余。
但并不是所有的事件都须要被传递,所以会有一个外部应用程序负责对事件进行筛选。这个应用程序生产来自这些主题的事件,依据保留在键值存储中的一组规定来大数据培训查看每一个事件,并决定是否应该通过咱们的公共 API 将消息传递给特定的开发者。事件是通过 Webhook 传递的,每个 Webhook URL 都有一个开发人员负责保护,并有惟一的 ID 标识。
存储和分区
通常,在构建一个须要存储层的回放零碎时,人们可能会应用基于 Hadoop 和 HDFS 的架构。但咱们抉择了 Kafka,次要基于以下两个起因:
• 已有的实时零碎采纳了公布和订阅架构
• 回放零碎存储的事件量不是 PB 级的,咱们存储的数据不会超过几天。此外,执行 Hadoop 的 MapReduce 作业比在 Kafka 上生产数据老本更高、速度更慢,达不到开发者的冀望
要利用实时管道来构建回放管道,首先要确保事件被存储在 Kafka 中。咱们把 Kafka 主题叫作 delivery_log,每个数据中心都有一个这样的主题。而后,这些主题被穿插复制,实现数据冗余,以便反对来自数据中心之外的重放申请。事件在被传递之前通过去重操作。
在这个 Kafka 主题上,咱们应用默认的分区机制创立了多个分区,分区与 WebhookId 的散列值(事件记录的键)一一对应。
咱们思考过应用动态分区,但最终决定不应用它,因为如果其中一个开发人员生成的事件多于其余开发人员,那么这个分区蕴含的数据将多于其余分区,造成了分区的不平衡。
相同,咱们抉择固定数量的分区,而后应用默认分区策略来散布数据,这样就升高了分区不平衡的危险,并且不须要读取 Kafka 主题的所有分区。重放服务基于申请的 WebhookId 来确定要读取哪个分区,并为该分区启动一个新的 Kafka 消费者。主题的分区数量不会发生变化,因为这会影响键的散列和事件的散布。
咱们应用了固态磁盘,依据每个时间段读取的事件数量来调配空间。咱们抉择这种磁盘而不是传统的硬盘驱动器,以此来取得更快的处理速度,并缩小与查找和拜访操作相干的开销。因为咱们须要拜访低频数据,无奈取得页面缓存优化的益处,所以最好是应用固态磁盘。
为了最小化存储空间,咱们应用了 snappy 压缩算法。咱们晓得大部分解决工作都在生产端,之所以抉择 snappy,是因为它在解压时比其余 Kafka 所反对的压缩算法 (如 gzip 和 lz4) 更快。
申请和解决
在咱们设计的这个零碎中,通过 API 调用来发送重放申请。咱们从申请音讯体中获取 WebhookId 和要重放的事件的日期范畴。这些申请被长久化到 MySQL 中,相当于进入了队列,直到它们被重放服务读取。申请中的日期范畴用于确定要读取的分区的偏移量。消费者对象的 offsetForTimes 函数用于获取偏移量。
重放服务解决每个重放申请,它们通过 MySQL 互相协调,解决数据库中的下一个须要重放的记录。重放过程定期轮询 MySQL,获取须要被解决的挂起作业。一个申请会在各种状态之间转换。期待被解决的申请处于凋谢状态(OPEN STATE),刚退出队列的申请处于启动状态(STARTED STATE),正在被解决的申请处于进行中状态(ONGOING STATE),已解决实现的申请将转换到已实现状态(COMPLETED STATE)。一个重放过程只会抉择一个尚未启动的申请 (即处于关上状态的申请)。
每隔一段时间,当一个工作过程将一个申请退出队列后,它会在 MySQL 表中写入工夫戳,示意正在解决以后的重放作业。如果重放过程在解决申请时死掉了,相应的作业将被重新启动。因而,除了将处于关上状态的申请退出队列之外,重放操作还将读取处于已开始或正在进行中的、在预约义的分钟数内没有心跳的作业。
在读取事件时会进行去重操作,而后事件被公布到消费者端的 Webhook URL 上。去重是通过保护被读取事件的散列值缓存来实现的。如果遇到具备雷同散列值的事件,就不传递这个事件。
总的来说,咱们的解决方案与传统的将 Kafka 作为实时、流式零碎的用法不一样。咱们胜利地将 Kafka 作为存储系统,构建了一个 API,在进行事件复原时晋升了用户体验和数据拜访能力。咱们利用已有的实时零碎设计让零碎的保护变得更加容易。此外,客户数据的复原速度达到了咱们的预期。