SoundCloud研发团队Sean Treadway谈SoundCloud架构演变

SoundCloud是一个新兴的社会化音乐创建和分享平台,前不久,他们研发团队的Sean Treadway在SoundCloud的博客上谈到了SoundCloud的架构演变。

Sean开门见山指出:

扩展是一个奢侈的问题,它与组织架构的关系远超与具体技术实现的关系。在每个变化阶段,我们都会预测用户的下一个数量级,从数千开始,我们的设计现在支持数亿用户。我们识别出瓶颈,解决它们的方法也很简单:在基础设施中加入明确的集成点,并以分而治之的方式处理各个问题。

识别扩展点,将其转为一系列更小的问题;有良好定义的集成点;这些方法让我们能够以有机而系统的方式增长。

产品初期

SoundCloud最开始的架构简单而直接:

互联网->Web层(Apache)->应用层(Rails)->数据层(MySQL)

Apache支持图片、风格和行为资源,由MySQL支持的Rails提供一个环境,几乎所有的产品都在其中有model,可以快速完成路由和呈现。大多数团队成员都可以理解这种模型,交付的产品与我们现在的产品很类似。

我们有意没有在这时处理高可用问题,也知道到时可能面临哪些问题。 此时我们版本脱离private beta,面向公众发布了SoundCloud。

我们的主要成本是机会成本,只要是阻碍了我们开发SoundCloud产品理念的东西,都被规避了。

在早期,我们有意确保构建的不仅是一个产品,而是一个平台。从一开始,我们的公开API与网站同步开发。现在,我们与第三方集成者使用的API完全相同,并以之推动网站开发。

后来,SoundCloud用Nginx替换了Apache,主要原因有两个:

Rails应用服务器运行在多个主机之上,而Apache处理多个虚拟主机的配置和路由很繁琐,特别是要保持开发和生产环境之间的同步时;

为了更好地提供连接池和基于内容的路由配置,以便管理、分配接收到的web请求、缓存向外的响应,并可以空出一个应用服务器,尽快处理后续请求。

负载分布与排队理论

接下来,SoundCloud发现有些负载耗去的时间比其他要多几百个毫秒,一些较快的请求必须要等较慢的请求处理完成。2008年,他们研发架构时,Rails和ActiveRecord中的并行请求处理还不够成熟。他们也开发了一些并行请求处理的代码,但是为了不占用更多时间去检查依赖,他们使用的方法是:

在每个应用服务器进程上运行一个并行进程,并在每个主机上运行多个进程。

Sean接下来用到了排队理论中的肯德尔记录法(Kendall's notation)。

我们从web服务器向应用服务器发送一个请求,这个请求过程可以建模为一个M/M/1队列。该队列的响应时间由之前所有的请求决定,因此,如果大幅提升一个请求的平均处理时间,那么平均响应时间也会大幅提升。

由于当时仍然处于机会成本最高的阶段,所以Sean和他的团队决定:在继续开发产品的同时,用更好的请求分发方法来解决这个问题。

我们研究了Phusion旅客方法,也就是在每个主机上使用多个子进程,可是认为这样可能很快会在每个子进程上填满长时间运行的请求。这就像有多个队列,每个队列上有几个工作者,模拟在单个监听端口上的并发请求处理。

这就把M/M/1队列模型变成了M/M/c队列模型,c是子进程数目。

该模型类似于银行中使用的排号系统。

该模型降低的响应时间由c决定,如果有5个子进程,对缓慢请求的处理速度能快5倍。但是,我们当时已经预计未来几个月用户会有10倍增长,而且每个主机上的处理能力有限,因此,只加入5到10个工作者并不足以解决排队阻塞问题。

我们希望系统中不要有等待队列,如果有,队列中的等待时间也要降到最低。如果将M/M/c用到极致,我们自问:“如何才能让c尽可能大?”

为了达到该目的,我们需要确保单个Rails应用服务器每次绝不接收超过1个请求,TCP方式的负载均衡就此出局,因为TCP无法区分HTTP请求和响应。我们也要保证:如果所有的应用服务器都是忙碌状态,请求将会被排队到下一个可用的应用服务器中。这就意味着我们必须保证所有的服务器做到完全无状态。当时,我们做到了后者,但未实现前者。

我们在基础设施中加入了HAProxy,将每个后端配置的最大连接数为1,并在所有主机中加入了后端进程,保证M/M/c在等待时间上的减少,生成HTTP请求队列,当任何主机上的后端进程可以处理时再发送过去。

将HAProxy作为队列负载均衡器,Sean他们就可以把其他组件中复杂的队列设计推到请求管道中处理。此时架构如下图:

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第1张图片

 

Sean全力推荐Neil J. Gunther的书籍《使用Perl::PDQ分析计算机系统性能》,帮助大家复习排队理论,更多了解如何针对HTTP请求队列系统进行建模和度量,并可以深入到磁盘控制器的层面。

走向异步

为了解决用户通知和存储增长方面的问题,SoundCloud决定加入中间层,以有效地解决工作队列的失败处理问题。最后他们选择了AMQP,因为它提供可编程的拓扑,由RabbitMQ实现。

为了不修改网站中的业务逻辑,我们调整了Rails环境,并为每个队列构建了一个轻量级的分发器。队列的命名空间描述了预估的工作次数,这在异步工作者中创建起一个优先级系统,同时不需要向broker中加入信息优先级的处理复杂性,因为每一类工作的分发器进程只处理该类工作中的多个队列。应用服务器中的绝大多数异步工作队列,其命名空间中要么有“interactive”(工作时间小于250ms),要么是“batch”(任何工作时间)。其他命名空间被用于特定应用。

此时他们的架构图如下:

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第2张图片

缓存

当用户达到十万数量级时,Sean发现应用层占用了过多CPU,主要用在呈现引擎和Ruby运行时上。

不过,他们没有用Memcached,而是缓存了大量DOM片段和完整页面。由此引发的失效问题,他们通过维护缓存主键的反向索引解决,使用Memcached,当应用中的model发生改变从而导致失效时,同样需要该方法解决。

我们最高的海量请求,来自于某个特定的服务,它向页面微件(widge)交付数据。我们在Nginx中为该服务创建了特定路由,并为其加入代理缓存。不过我们希望缓存功能能够通用化,做到任何服务都能创建正确的HTTP/1.1缓存控制头,并可以由我们控制的某个中介正确处理。现在,我们的微件内容完全由公开API提供。

此后,为了处理后端部分呈现模板缓存和大多数只读API响应,他们加入了Memcached,并在很晚之后加入了Varnish。此时,他们的架构是:

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第3张图片

通用化

SoundCloud后来的处理模型变成:针对某个领域model,为其状态设定连续处理方式,以便处理后续状态。

为了通用化这个模式,他们利用了ActiveRecord的after-save钩子,加入了ModelBroadcast方式。原则是:当业务领域变化时,事件会丢入AMQP总线中,任何对此变化感兴趣的异步客户端会得到该变化。

把写路径从阅读者中解耦出来,这种技术容纳了我们从未想到过的集成点,为未来的增长和演化提供了更大空间。

以下是示例代码:

after_create do |r|
  broker.publish("models", "create.#{r.class.name}",  r.attributes.to_json)
end

after_save do |r|
  broker.publish("models", "save.#{r.class.name}", r.changes.to_json)
end

after_destroy do |r|
  broker.publish("models", "destroy.#{r.class.name}", r.attributes.to_json)
end

Dashboard功能

数据的快速增长引出了Dashboard功能。在SoundCloud中,用户可以在Dashboard中看到个人和的社会化活动索引,并可以个人化来自自己关注的人制作的音乐片段。SoundCloud一直受困于Dashboard组件带来的存储和访问问题。

读写的路径各自不同,读操作路径需要针对每个用户提供一定时间范围内的顺序读并做优化,写操作路径需要对任意访问做优化,而且一个事件就有可能影响几百万用户的索引。

解决方法是重新排序任意读操作,将其变为顺序方式,并以顺序格式存储供未来的读取使用,这可能会扩展到多个主机上。排序字符串表非常适合用持久化格式,考虑到需要自由分区和扩展,我们选择了Cassandra存储Dashboard的索引。

我们从model广播开始,然后用RabbitMQ做队列完成步骤处理,主要包括三步:扇出(fan-out)、个性化、指向领域模型的外键引用串行化。

  • 扇出会找出一个活动应该传播到的社会化图谱中的区域。
  • 个性化查看发起者和目的用户之间的关系,以及其他注解或过滤索引项目的信号。
  • 串行化把Cassandra中的索引条目持久化,供以后查找使用,并与领域模型做联接,以供显示或API展示。

此时他们的架构如下:

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第4张图片

搜索

SoundCloud的搜索通过HTTP接口暴露数据集操作子集,供查询使用。索引的更新与Dashboard类似,通过ModelBroadcase完成,并使用了由Elastic Search管理的索引存储,在复制数据库方面有提升。

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第5张图片

通知和状态

为确保用户得到Dashboard的通知,SoundCloud在Dashboard的工作流中加入了一个阶段,用来接收Dashboard索引更新的消息。Agent可以通过消息总线得到路由到它们自己的AMQP队列中的事件完成通知。他们的状态和统计通过broker中介集成,但是没有ModelBroadcast,他们会发出在日志的队列中的特定领域事件,然后保存在隔离的数据库集群中,以满足不同时间段快速访问需要。

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第6张图片

未来预期

Sean这样描述他们对未来的规划:

我们已经建立起了明确的集成点,包括供异步写路径的broker中介中,包括向后端服务完成同步读和写的路径的应用中。

随着时间演变,应用服务器的数据库已经承担了集成和功能两方面的职责。产品开发基本尘埃落定,我们现在有信心将功能与集成分开,转移到后端服务中,供应用层还有其他后端服务使用,各自都可以在持久化层中有自己的命名空间。

我们在SoundCloud的研发方式,是识别扩展点,然后分别隔离、优化读路径和写路径,并预估下一个成长阶段的数量级。

SoundCloud研发团队Sean Treadway谈SoundCloud架构演变_第7张图片

最后,Sean指出了SoundCloud以前和现在在架构上的约束:

在产品开发开始阶段,我们读写扩展的限制来自消费者的关注和开发人员的时间。现在,我们的工程方向是I/O、网络和CPU的限制。

你可能感兴趣的:(SoundCloud研发团队Sean Treadway谈SoundCloud架构演变)