Spotify如何对Apache Storm进行规模扩展

2015 年 2 月 09 日

【编者的话】Spotify 是一家音乐流媒体服务商,最新的数据显示他们已经有 6000 万用户。Spotify 内部使用 Apache Storm 来构建实时类系统,包括广告定位、音乐推荐以及数据可视化等。本文来自 Spotify 官方技术博客,介绍了 Spotify 公司如何使用 Apache Storm 来构建可扩展的个性化系统的。

Spotify 已经在很多业务中使用 Apache Storm 来构建多种实时系统,包括广告定位音乐推荐以及数据可视化等。其中每一种实时系统都将Apache Storm 与其它不同系统加以结合,例如Kafka、Cassandra、Zookeeper 以及其它数据输入和输出的系统。Spotify 在全球范围内拥有超过五千万的活跃用户,所以在应用构建时需要考虑可扩展性以保证应用的性能以及高可用性。

Thinking Scalability

面对资源线性增长所带来的负载压力提升,可扩展性已经成为软件保持理想性能表现的前提。不过要真正实现可扩展目标,单靠添加资源并对性能进行调整还远远不够。具体而言,可扩展性要求大家对软件方案的设计、质量、可维护性以及性能等方面整体考量。当我们构建应用程序时,首先应该从以下几个方面对可扩展性进行规划(保证应用可扩展性的必要条件):

  • 软件具备良好的架构与较高的质量
  • 软件应该易于发布监控修改
  • 软件性能可以跟得上资源线性提升所带来的额外负载增长。

Storm 中的可扩展性

那么对 Storm 流程加以扩展需要在哪些方面做出努力?下面我将通过自己的实时个性化 Storm 系统作为实例,向大家阐述可扩展能力中的方方面面。

在我们的个性化系统中,使用 Kafka 集群的 Topic(译者注:一个 Topic 可以认为是一类消息,每个 Topic 将被分成多个 partition)来处理不同类型的事件,比如歌曲完成与广告曝光。我们的个性化 Storm 拓扑可以订阅不同的用户事件,并将这些事件与读取自 Cassandra 的实体元数据(例如歌曲流派)相结合,然后将每位用户的事件进行分组,进而通过某种包含聚合与推导机制的算法计算出用户属性。这些用户属性会被写入 Canssandra,最后会被多种后端服务使用以提供个性化的用户体验。

设计与质量

当我们将随着时间推移将更多新功能添加到以上个性化流程当中时,我们的拓扑结构开始变得复杂,并直接导致性能调整与事件流调试的难度越来越高。不过较高的测试覆盖率让我们对自身的代码质量充满信心,因此我们认为自己有能力对拓扑进行快速重构并使其投入正常运作。

拓扑架构

在将复杂拓扑转化为小型可维护拓扑的整个转换周期当中,我们通过实际操作得到了以下启示:

  • 为不同任务流创建小型逻辑拓扑
  • 通过共享库而非共享逻辑的方式提高代码的可重复使用度
  • 保证方法容易被测试
  • 并行批量处理会降低 IO 操作

质量

我们已经使用 Java 开发了自己的流程,并借助 JUnit 对不同计算 Bolt 内的业务逻辑进行了测试。我们还利用 backtype.storm.testing 并通过集群模拟进行了端到端的测试。

可维护性

为了将软件轻松部署在集群内的新主机中并对其运行状况进行监控,我们采取了一系列措施来简化维护。

配置

对外暴露所有可调参数,这可以让我们在不变更任何代码的前提下实现软件调整,同时也让我们能够更轻松地实现小型增量变更并观察其实际影响。我们将 bolt parallelism、source endpoints、sink endpoints 以及其它拓扑性能参数映射到了一个配置文件中。

指标可视化

我们为拓扑指标创建了一套仪表板(dashboard),旨在整体评估其运作状态并进行问题排查。我们采用高级度量指标(详见下图)对整套系统的运行状态加以汇总,因为在面对一套充满了各类指标,但又缺乏重点倾向性的仪表板时,大家往往很难从中找到真正值得关注的信息。

拓扑部署

我们这套个性化流程中的全部计算任务都是幂等的,我们还设计出了自己的部署方案,在事件处理中允许少量的重复以确保部署过程中不会丢失消息。这套方案并不适用于全部用例,特别是在计算任务以事务形式存在的情况下。在以上图表中,各事件由左至右依次排列,其中 t1 到 t8 代表着不同时间戳。

在我们的部署方案当中,我们需要确保 Storm 集群可以同时运行两套个性化拓扑。在 t1 时间点上,集群运行的是个性化拓扑的 v1 版本。当我们准备好发布该个性化拓扑的 v2 版本时,我们会构建并将 v2 提交至该集群。在 t4 时间点上,我们的集群正在同时运行这两个版本。每套拓扑都会使用一个唯一的 Kafka consumer groupId,从而确保 topic 内的全部信息都被交付给这两套版本。在此阶段中信息会经过两次处理,但由于计算的幂等属性,这并不会造成任何问题。在 t5 时间点上,我们停用 v1 版本,这意味着该版本将不再消费来自 Kafka 集群的事件。接下来,我们对 v2 版本的运行图表进行监控,并确保所有指标都处于正常范围之内。如果一切顺利,我们会移除 v1 版本,并在 t8 时间点上让集群仅运行 v2 版本。不过在 t7 时间上,如果指标图表显示异常状况,我们会激活 v1 版本并使其延续停用时的状态,继续消费来自 Kafka 的事件。此时我们还将停用 v2 版本,这从本质上讲正是我们打造的回滚机制。拥有安全的回滚机制能帮助我们在不断推出小型高频度变更的同时,将相关风险控制在最低程度。

监控与警报

我们会对集群、拓扑、Source、Sink 指标加以监控,并为其中的部分高级指标设定警报机制。这是为了避免冗余报警令管理员身心俱疲,甚至忽略掉真正重要的关键性警报。

性能

随着时间的推移,我们已经监控到不同的系统瓶颈与相关的问题,也通过一系列调整将性能维持在理想水平。要获得与预期相符的性能表现,大家还需要选择正确的硬件方案。

硬件

我们最初将自己的拓扑运行在一套共享式 Storm 集群当中,但随着时间推移,我们发现繁忙的拓扑导致其资源匮乏,并由此引发资源瓶颈。有鉴于此,我们开始使用一套独立的 Storm 集群,其实这并不算什么难事。现在我们的集群每天要处理超过 30 亿个事件。整套集群中包含 6 台主机,每台主机配备 24 个计算核心、双线程以及 32GB 内存。即使是使用这套小型集群,我们仍然获得了良好的运行状态。而且在部署过程中需要并行运行 2 个个性化拓扑版本时,其处理强度与最大利用率仍然相去甚远。未来我们还会考虑在自己的 Hadoop 集群上将 Storm 与 YARN 加以结合,从而带来更理想的资源利用率与弹性扩展能力。

吞吐能力与延迟水平

为了获得理想的吞吐能力与延迟水平,我们需要对 source 与 sink 参数进行调节。此外我们还做出了其它一系列调整,包括缓存、并行性以及并发性等等,详见下文。

Source 和 Sink 调整

Kafka 调整

  • 我们配置了 rebalancing.max.tries 文件以尽可能减少常见的 rebalance 错误
  • 在每个版本中为不同 Kafka Spout 采用不同 group id,从而确保新拓扑版本在部署过程中遵循冗余信息处理原则。

Cassandra 调整

  • 为不同 TTL 采用不同表。由于无需使用,我们还设置 gc_grace_period=0 以有效对包含 TTL 的行组进行读取修复禁用。
  • 使用 DateTieredCompactionStrategy 处理临时性数据。
  • 从 Storm 拓扑到 Cassandra,对开放连接数量加以控制。
  • 配置 Snitch 以确保调用路由的正确性。

并发问题

Storm 中的 OutputCollector 并非线程安全,也无法保证面向多线程的安全访问——例如对异步处理流程中的Future 进行回调。我们利用java.util.concurrent.ConcurrentLinkedQueue 对ack/emit Storm tuples 的调用进行安全保存,并在bolt 内方法执行之初对其加以刷新。

并行调节

我们从 Strata 2014 大会的 Storm 主题演讲中得到了灵感,进而对拓扑当中的并行机制作出调节。以下几项指导性意见在我们的实例中带来相当出色的表现:

  • 每拓扑每节点 1 worker
  • 对于 CPU 绑定类任务,每计算核心 1 executor
  • 对于 IO 绑定类任务,每计算核心 1 至 10 executor
  • 计算总体并行可能性并将其划分为低速任务与调整任务。低速任务并行性较高,调整任务并行性较低。

Bolt 缓存处理

为了维持 Bolt 当中用户属性计算拥有良好的状态,我们需要从外部与内存两类缓存机制当中做出选择。我们更倾向于使用内存缓存方案,这是因为外部缓存往往会带来网络 IO 负担,产生不必要的延迟并增加新的故障点。不过说到内存内缓存,我们并没有持久化或者限制内存资源。事实上,我们不太在意持久化,因此只需要着手处理内存限制问题即可。我们最终选择了 Guava 的 Expirable Cache,我们可以在其中定义元素及过期数量上限,从而控制缓存的整体规模。总而言之,这套方案帮助我们在自有集群当中实现了内存的优化利用。

我们通过一整套方案来扩展 Storm 系统,有了这个系统的支持,我们可以为那些不断增加的活跃用户提供更多的新功能,同时,Storm 系统也可以毫无压力地继续为我们提供高可用的服务。

编后语

《博文共赏》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。

原文英文链接: https://labs.spotify.com/2015/01/05/how-spotify-scales-apache-storm/


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2015 年 2 月 09 日 00:394492

评论

发布
暂无评论
发现更多内容

MySQL DDL详情揭露

Simon

MySQL

2020深圳站-GIAC全球互联网架构大会PPT分享

高翔龙

架构 分布式系统 服务治理 大型网站演变 全链路压测

Python 核心技术与实践 input&output

Bonaparte

世界集成电路发展概况

老壳有点爽

芯片 集成电路 IC 芯片营销

Golang领域模型-六边形架构

奔奔奔跑

golang 领域驱动设计 微服务拆分 架构设计

MySQL备份与恢复场景示例

Simon

MySQL

Linux指令简述&vim引入(1)

老壳有点爽

vim Linux 脚本

Vim小技巧(2)

老壳有点爽

vim Linux 脚本语言

看门狗 | 分布式锁架构设计方案-01

高翔龙

redis 分布式锁 RedLock WatchDog

看门狗 | 分布式锁架构设计方案-02

高翔龙

redis 分布式锁 Jedis RedLock

集成电路工艺基础介绍

老壳有点爽

芯片 集成电路 IC 工艺 制程

魅力非凡的半导体电路行业

老壳有点爽

芯片 集成电路 IC 芯片营销

强势入局,区块链专利将成为银行下一个战场?

CECBC区块链专委会

区块链 金融 银行

芯片行业的主要生意模式

老壳有点爽

芯片 集成电路 IC

sed语言学习技巧(1)

老壳有点爽

vim 编程语言 sed 脚本语言

Verilog 的debug技巧(1)

老壳有点爽

芯片 集成电路 IC Verilog 电路

区块链之物流产业上链解决痛点

CECBC区块链专委会

区块链 供应链

成都信息工程大学的区块链工程成全国首个区块链工程本科专业

CECBC区块链专委会

区块链技术 区块链工程

数字后端工程师发展六阶段

老壳有点爽

芯片 集成电路 IC 数字电路工程师

模拟电路设计工程师发展九段

老壳有点爽

集成电路 IC 芯片设计 模拟电路

sed 语言学习技巧(2)

老壳有点爽

vim sed 脚本语言

PySpark RDD 基础运算和操作总结

是老郭啊

spark pyspark RDD

物理实现(Physical Implementation)

老壳有点爽

芯片 集成电路 IC 物理设计 PI

中国大陆芯片行业发展概况

老壳有点爽

芯片 集成电路 IC

最初芯片国产化是怎么来的?

老壳有点爽

芯片 集成电路 国产化 替代

国产数据库的经济民族

郭华

数据库 商业

数字电路后端设计流程

老壳有点爽

设计 flow 集成电路 IC

IC设计流程及工具

老壳有点爽

芯片 集成电路 IC IC设计流程及工具

集成电路设计概括

老壳有点爽

芯片 集成电路 IC

芯片行业基本生态:设计生产封装的行业分工

老壳有点爽

芯片 集成电路 IC

半导体行业个人理解

老壳有点爽

芯片 半导体 集成电路 IC

Spotify如何对Apache Storm进行规模扩展-InfoQ