# 使用 AWS，Scala，Akka，Play，MongoDB 和 Elasticsearch 构建社交音乐服务

> 原文： [http://highscalability.com/blog/2014/3/11/building-a-social-music-service-using-aws-scala-akka-play-mo.html](http://highscalability.com/blog/2014/3/11/building-a-social-music-service-using-aws-scala-akka-play-mo.html)

![](img/7887820329b58171ca35d8132a92ae4c.png)

*这是[前 Roem Hermon](https://twitter.com/margolis20) ， [serendip.me](http://serendip.me) 的首席架构师 [Rotem Hermon](https://twitter.com/margolis20) 的来宾转贴，涉及在进行启动音乐服务后的体系结构和扩展注意事项 。*

[serendip.me](http://serendip.me) 是一项社交音乐服务，可以帮助人们发现朋友分享的美妙音乐，还可以将他们介绍给他们的“音乐灵魂伴侣”，即他们社交圈之外的人，他们在音乐方面有着相似的品味。

Serendip 在 AWS 上运行，并基于以下堆栈构建： [scala](http://www.scala-lang.org/) （和某些 Java）， [akka](http://akka.io/) （用于处理并发性），[播放框架](http://www.playframework.com/)（用于 Web 和 API 前端）， [MongoDB](http://www.mongodb.org/) 和 [Elasticsearch](http://elasticsearch.org/) 。

### 选择堆栈

建立 serendip 的挑战之一是从第一天起就需要处理大量数据，因为 serendip 的主要特征是它收集**并从公共音乐服务在 Twitter** 上共享的每一首音乐。 因此，当我们讨论选择要使用的语言和技术的问题时，一个重要的考虑因素就是扩展能力。

JVM 似乎是我们系统经过验证的性能和工具的正确基础。 它也是许多开源系统（例如 Elasticsearch）的选择语言，这使**使用其本机客户端**-很大的优势。

当我们研究 JVM 生态系统时，scala 脱颖而出，成为一种有趣的语言选择，它允许现代方法编写代码，同时保持与 Java 的完全互操作性。 支持 scala 的另一个论点是 **akka actor 框架，它似乎非常适合流处理基础结构**（确实如此！）。 Play 网络框架才刚刚开始被采用，并且看起来很有希望。 早在 2011 年初我们刚开始时，这些仍然是最前沿的技术。 因此，我们当然非常高兴，到 2011 年底，scala 和 akka 合并成为 [Typesafe](http://typesafe.com/) ，随后不久 Play 就加入了。

**选择 MongoDB 是因为它结合了开发人员的友好性，易用性**，功能集和可能的可伸缩性（使用自动分片）。 我们很快了解到，要使用和查询数据的方式将需要在 MongoDB 上创建很多大索引，这将使我们很快遇到性能和内存问题。 因此，我们一直主要将 MongoDB 用作键值文档存储，还依赖于其原子增量来获取需要计数器的多个功能。
通过这种使用，MongoDB 变得非常可靠。 它也很容易操作，但是主要是因为我们设法*避免使用分片*并使用单个副本集（MongoDB 的分片架构非常复杂）。

为了查询我们的数据，我们需要一个具有完整搜索功能的系统。 在可能的开源搜索解决方案中， **Elasticsearch 成为最具扩展性和面向云的系统**。 它的动态索引架构以及它提供的许多搜索和构面功能使我们可以在其之上构建许多功能，从而使其成为我们体系结构的核心组成部分。

我们选择**自己管理 MongoDB 和 Elasticsearch** ，并且出于两个主要原因，不使用托管解决方案。 首先，我们希望完全控制两个系统。 我们不想依靠其他因素进行软件升级/降级。 其次，我们处理的数据量意味着托管解决方案比直接在 EC2 上直接管理它要昂贵。

### 一些数字

Serendip 的“泵”（处理 Twitter 公众流和 Facebook 用户供稿的部分）**每天大约消化 5,000,000 个项目**。 这些项目通过一系列“过滤器”传递，这些过滤器检测并解析受支持服务（YouTube，Soundcloud，Bandcamp 等）的音乐链接，并在它们之上添加元数据。 泵和过滤器作为 akka actor 运行，并且整个过程由单个 **m1.large EC2 实例**管理。 如果需要，可以通过使用 akka 的远程角色将系统分发到处理器集群来轻松扩展。

在这些项目中，我们每天大约可获得 850,000 个有效项目（即真正包含相关音乐链接的项目）。 这些项目在 Elasticsearch 中（以及在 MongoDB 中用于备份和保持计数器）都已建立索引。 由于每个有效项都意味着更新多个对象，因此在 Elasticsearch 中我们获得的**索引速率为〜40 / sec** 。
我们**在 Elasticsearch 中保留项目（推文和帖子）的每月索引**。 每个月度索引包含**〜2500 万个项目，并具有 3 个碎片**。 群集以 **4 个节点运行，每个节点在 m2.2xlarge 实例**上。 此设置有足够的内存来运行我们需要的数据搜索。

我们的 MongoDB 集群处理更多数据类型，计数器和统计信息更新时，每秒**为 100 个写入/秒，每秒 300 次为**读取。 副本集具有在 **m2.2xlarge 实例上运行的主节点，在 m1.xlarge 实例**上运行的辅助节点。

### 建立一个提要

当我们开始设计 Serendip 的主要音乐供稿的体系结构时，我们知道我们希望供稿是动态的并对用户的动作和输入做出反应。 如果用户给某首歌“加油”或“播放”特定艺术家，我们希望该动作立即反映在提要中。 如果用户“不喜欢”艺术家，则我们不应再次播放该音乐。

我们还希望提要是来自多个来源的音乐的组合，例如朋友共享的音乐，喜爱的艺术家的音乐以及具有相同音乐品味的“建议”用户共享的音乐。
这些要求意味着采用“ [扇出即写](http://www.quora.com/What-is-the-best-storage-solution-for-building-a-news-feed-MongoDB-or-MySQL)”的方法来创建提要是不可行的。 我们需要一个选项，以使用与用户有关的所有信号实时构建提要。 Elasticsearch 提供的功能集使我们能够构建这种实时提要生成。

提要算法包含几种“策略”，用于选择要在每次提要中以不同比率动态组合的项目。 每种策略都可以考虑到最新的用户操作和信号。 将策略的组合转换为对实时数据的几次搜索，这些搜索由 Elasticsearch 不断索引。 由于数据是基于时间的，并且索引是每月创建的，因此**我们始终只需要查询完整数据**的一小部分。

幸运的是，Elasticsearch 可以很好地处理这些搜索。 它还提供了扩展此体系结构的已知途径-**写入可以通过增加分片**的数量进行缩放。 可以通过添加更多副本和物理节点来扩展搜索范围。

查找“音乐灵魂伴侣”（通过音乐品味匹配用户）的过程很好地利用了 Elasticsearch 的**构面（聚合）功能。 作为持续的社交流处理的一部分，该系统通过计算遇到的社交网络用户的顶级共享艺术家（使用对共享音乐的多面搜索）来准备数据。**

当偶然的用户发出信号（通过播放音乐或与提要互动）时，它可以触发对该用户的音乐灵魂伴侣的重新计算。 该算法会根据喜欢的艺术家列表（不断更新）找到最匹配的其他用户，并权衡受欢迎程度，分享数量等其他参数。然后应用另一组算法来过滤垃圾邮件发送者（是的， 是音乐垃圾邮件发送者……）和离群值。

我们发现，此过程可为我们提供足够好的结果，同时又使我们免于需要其他可以运行更复杂的群集或推荐算法的系统。

### 监控和部署

Serendip 正在使用 [ServerDensity](http://www.serverdensity.com/) 进行监视和警报。 这是一款易于使用的托管解决方案，具有不错的功能集和合理的启动价格。 ServerDensity 本机提供服务器和 MongoDB 监视。 我们还大量使用了向其中报告自定义指标的功能，以报告内部系统统计信息。

内部统计信息收集机制为系统中发生的每个操作收集事件，并将其保存在 MongoDB 收集中。 定时作业每分钟一次从 MongoDB 中读取这些统计信息，并将其报告给 ServerDensity。 这使我们可以使用 ServerDensity 监视和警告 Elasticsearch 以及我们的运营数据。

使用 Amazon Elastic Beanstalk 完成服务器和部署的管理。 Elastic Beanstalk 是 AWS 的受限 PaaS 解决方案。 它非常容易上手，尽管它并不是真正功能齐全的 PaaS，但其基本功能足以满足大多数常见用例的需要。 它提供了简单的自动缩放配置，还可以通过 EC2 进行完全访问。

使用驻留在 EC2 上的 [Jenkins](http://jenkins-ci.org/) 实例完成应用程序的构建。 Play Web 应用程序打包为 WAR。 [构建后脚本](https://github.com/rore/beanstalk-upload)将 WAR 作为新的应用程序版本推送到 Elastic Beanstalk。 新版本不会自动部署到服务器，而是手动完成的。 通常将其首先部署到登台环境中进行测试，然后将其批准部署到生产环境中。

### 外卖

总结一下，这是从构建 serendip 中学到的一些重要经验教训，而不是通过任何特殊命令得出的。

1.  **知道如何缩放**。 您可能不需要从第一天开始进行扩展，但是您需要知道系统的每个部分如何扩展以及扩展到什么程度。 如果扩展需要时间，请提前给自己足够的时间。
2.  **准备峰**。 特别是在初创企业的生命中，如果您始终以接近最高的容量运行，那么一个救生员或 reddit 帖子就会使您的系统瘫痪。 保持足够的余量，以便您可以处理突然的负载或准备好快速扩展。
3.  **选择一种不会阻碍您的语言**。 确保要使用的技术使用您的语言的本地客户端，或者至少具有主动维护的客户端。 不要卡在等待库更新。
4.  **相信炒作**。 您需要一种将随您的产品一起增长并且不会过早死亡的技术。 一个充满活力，活跃的社区以及对该技术的一些质疑可以很好地表明其生存。
5.  **不要相信炒作**。 查找有关您正在评估的技术的简报。 他们可以教您有关它的弱点。 但也不要太当真，当事情无法按预期进行时，人们往往会情绪激动。
6.  **玩得开心**。 选择一种使您兴奋的技术。 一个让您认为“哦，太酷了，我该怎么办”。 毕竟，这也是我们的目标。