写在之前
当 Spotify 的用户打开客户端听歌或者搜索音乐人,用户的任何一个触发的事件都会传送回 Spotify 的服务器。事件传输系统(也即,日志收集系统)是个有趣的项目,确保全球用户使用 Spotify 客户端所产生的事件都完全、安全的发送到数据中心。在本系列文章中,将讲述 Spotify 在这方面所做过的工作,而且会详细解说 Spotify 新的事件传输系统架构,并回答“为什么 Spotify 会基于 Google 云平台管理建立自己的新系统?”。
在第一篇文章里,讲述了 Spotify 旧事件传输系统是如何工作的,以及一些经验总结。在本篇文章,将阐述 Spotify 新事件传输系统的设计,并解释为什么 Spotify 公司选择 Google 云发布/订阅组件作为所有事件的传输机制。
Spotify 新事件传输系统的设计
从旧事件传输系统运行过程中获取的经验给我们设计新事件传输系统提供了很大的帮助。新事件传输系统选用创新的复杂设计,比如每台事件生产机器都进行文件结束标记的传播和确认;旧系统中对于失败的情况不能进行自动恢复,对于大面积失败的情况,每个生产日志的机器进行手动人工干预会耗费很大的操作成本。在新系统中进行了简化处理,每台日志生产机器都会把自己的事件移交到一小部分机器上,选择的这些机器是在网络上尽可能离下一步处理更近的机器。
还有一个注意的地方是,需要一个事件传输系统或者队列来保障事件传输的稳定性,以及持久化在队列中未传输完的消息。我们应该做到生产者以较高的频率来转移事件到最近的其它生产者机器上,并要求低延迟的回传收到的确认信息。
另外变化的地方是,每个事件类型有自己的频道(channel)或者 topic;在事件处理的早期就转化成结构化的数据。把转化数据格式的工作放在事件生产者的好处在于缩短数据清洗(ETL)任务的时间。建立独立的 topic 是为了提高后续在实时处理的效率。
新事件传输系统设计成可以和现有旧系统并行运行,生产者端和消费者端的接口都可以匹配当前系统。这样可以在切换系统前进行新系统的性能等校验。
(点击放大图像)
图 1 新事件传输系统的设计
新事件传输系统主要包含四个组件:File Tailer,Event Delivery Service,Reliable Persistent Queue 和 ETL job。
- File Tailer 功能比旧系统中的 Produce 有所收缩,它跟踪日志文件的尾部监控新事件的插入,并转发新事件信息到 Event Delivery Service 组件,然后 File Tailer 得到事件传输完成的确认消息。
- Event Delivery Service 组件从 Tailer 接收事件数据,转化他们成最终需要的结构化数据格式并转发到队列 Queue 中。Event Delivery Service 是一个 RESTful 风格的微服务,用 Apollo 微服务框架和 Helios 自动化部署平台开发,使得各服务间解耦。
- 队列 Queue 是新事件传输系统的核心,对爆发增长的数据可以快速扩展。为了应对 Hadoop 集群可能出现的故障,需要能稳定保存数天的消息。
- 清洗任务(ETL job)能够保证稳定、可重复运行,并能从队列 Queue 导出事件到 HDFS 上的小时桶(Spotify 的数据是按小时分桶)。ETL job 需要保证桶的所有数据被消费完才可以把一个桶的数据传输到下游消费者。
在图 1 中,你可以看到一个标有“Service Using API directly”的图框,它是 syslog 的 API,当新事件传输系统上线后,“Service Using API directly”可以直接与 Event Delivery Service 通信。
选择可靠的持久化队列 Queue
Kafka 0.8
建立一个可靠的持久化队列系统处理 Spotify 事件是一个艰巨的任务。事件传输系统是数据架构的一个基础部件,它的安全性、稳定性非常重要。我们首选是 Kafka 0.8。
全球范围内有很多公司成功的使用了 Kafka 0.8,它在当前系统中是个极大的改善。Kafka 0.8 提供了可靠的持久化存储。 Mirror Maker 项目提供各数据中心的镜像, Camus 项目用来导出 Avro 格式的事件到 HDFS 的小时桶。
(点击放大图像)
图2 使用Kafka 0.8 作为持久化的事件传输系统设计
为了测试Kafka 0.8 是否与预期一样工作,Spotify 发布了测试系统,如图2。在Event Delivery Service 嵌入简单的Kafka Producer 是非常容易的。为了确保事件传输系统能够正确的端对端传输,从Event Delivery Service 到HDFS,我们在持续集成和传输中嵌入各种测试。
遗憾的是,这种设计的系统启动不久就出现生产者阻塞,唯一稳定的组件是Camus(由于此次测试并没有push 大量的数据,所以并不能压测出Camus 的性能如何)。
Mirror Maker 让人头疼,刚开始假设它会稳定的镜像不同数据中心的数据,但是实际情况并非如此。如果目的端集群出现状况,Mirror Maker 会丢失数据但却向源集群报告数据已成功镜像(这个 Bug 在 Kafka 0.9 中修复了)。Mirror Maker 偶尔会出现“脑裂”,这是数据中心见的镜像过程将停止。
Kafka Producer 也会出现严重的稳定性问题。如果同一个集群的一个或者多个 broker 被移除或者重启,它将进入一种不能恢复的状态。当出现这种情况时,Kafka Producer 不能生产任何事件,唯一的解决方案是重启整个服务。
以上的问题没有解决,我们将有许多工作要做才能进行上线,定义 Kafka Broker 和 Mirror Maker 的发布策略,对所有系统组件都要做容量模型和计划,并要对性能指标进行监控。
Spotify 进入一个十字路口,我们需要投入巨大的投资和修复 Kafka?还是再试试其它的?
Google 云发布/订阅
当我们在 Kafka 上进行徘徊的时候,其它 Spotify 团队开始实验 Google 云产品。他们发现一个有趣的产品 Google 云发布/订阅,它似乎可以满足我们的基本需求:可靠的持久化队列,能保存未消费的数据长达 7 天,提供应用级别的可靠性,和至少消费一次(at-least-once)的语义。
在满足基本需求外,Google 云发布/订阅带来了额外的惊喜:
- 全球化可用性:作为一个全球化的服务,Google 云发布/订阅能在 Google 云适用的所有地区使用;
- 简单化的 REST 风格的 API:如果不喜欢 Google 提供的客户端库,你可以自己开发;
- 处理操作是 Google 提供:无需你关心容量、发布策略或者监控和警报等。
基于 Apache Kafka 的方案虽不是完美,但也凑合。并且有了之前的经验理论上讲也可以解决所有出现的问题。迁移一个服务意味着我们不得不去相信其它组织的操作,Google 云发布/订阅是作为一个 Beta 版推广的,除了 Google 本身没有其它公司使用。
考虑到这些,我们需要对 Google 云发布/订阅进行压测,看下是否满足所有的要求。
Producer 载荷压测
首先提上日程的是 Google 云发布/订阅能否处理预期的负载。当前负载峰值大约 700k 事件/每秒,为了考虑今后的增长和可能出现的灾难性崩溃后的恢复,我们设定压测负载值为 2M 事件/每秒。我们从每个数据中心发布 2M 事件/每秒的事件,所有的请求会打到同一区域的 Google 云发布/订阅机器。我们假设 Google 云服务区域是独立的,每个区域可以处理等量的数据,理论上,如果我们能 push 2M 的信息到单个区域,那总共可以发布 number_of_zones * 2M 信息。我们希望在服务不降级到情况下,Google 云发布/订阅能够在生产者端和消费者端都能长时间处理这种负载。
测试初期遇到一个问题:Google 云发布/订阅 Java 客户端不能很好的工作。后来 Spotify 开发了自己的库,为了更好的利用资源,采用Java 异步。在开发新的客户端库同时,我们已经开始进行高负载压测了。数据经过路由按7:3 的比例分发给两个Google 发布/订阅topic。为了达到2M 事件/每秒的负载,我们用29 台机器来跑Event Seervice。
(点击放大图像)
图3 所有数据中心每秒发送到Google 发布/订阅组件到成功请求数
(点击放大图像)
图4 所有数据中心每秒发送到Google 发布/订阅组件到失败请求数
(点击放大图像)
图5 Event Service 机器网络进出量
Google 发布/订阅组件通过压测,我们在没有任何服务降级的情况下发布 2M 事件,并且 Google 发布/订阅后台几乎未收到服务器错误。
(点击放大图像)
图6 Google 云可视化监控所有的发布到Google 发布/订阅组件的消息
(点击放大图像)
图7 Google 云可视化监控每个topic 发布到Google 发布/订阅组件的消息
Consumer 稳定性测试
第二个测试关注点在消费端。我们进行了 5 天的高负载端对端系统延迟测试,测试期间发布平均 800k 事件/每秒。为了模拟真实情况下的负载变化,发布速率随着一天的时间变化。为了验证并发使用多 topic,将所有的数据按 7:3 的比例发布。
Google 发布/订阅组件需要在消息被持久化前进行订阅:不订阅,数据不保存。所有的订阅独立存储数据,并且一个订阅不限制消费者个数。Consumer 在服务器端调度,所有服务器公平分配所有的请求数据。
(点击放大图像)
图8 消费者测试
在本次测试中,创建了一个订阅,一个小时后进行消费数据。在测试期间中等延迟——包括日志恢复,大概有20 秒,并且没有发现任何日志丢失。
最后的决定
基于以上的测试,我们决定使用Google 发布/订阅组件,其有低延迟和一致性,唯一的缺点是容量限制。简而言之,使用Google 发布/订阅组件比Kafka 0.8 更适合新的事件传输系统。
(点击放大图像)
图9 采用Google 发布/订阅组件后的事件传输系统设计
下一步
事件在Google 发布/订阅组件中安全地持久化后,接下来是导入到HDFS。接下来会讲解Google 的另外一个服务Dataflow。
感谢杜小芳对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论