Heroku 的教训:糟糕的负载均衡 + RoR 单线程 = 糟糕的性能

阅读数:5271 2013 年 2 月 16 日

话题:Ruby on Rails云计算语言 & 开发

两天前,Heroku 最大的用户之一 Rap Genius(这是一家面向嘻哈音乐迷的网站)在其官方网站上发布了一篇博客,表达了对 Heroku 极大的不满,因为 Heroku 的路由系统(Heroku routing mesh)性能太差。该文章很快被推送到RedditHacker News上,引起了极大反响。

之后,Heroku 的 COO Oren Teich 很快在官方网站上更新了一篇声明,表示这是一个累积了三年的性能问题,所有运行在 Bamboo 上的 RoR 应用在 Heroku 经历扩展的同时,性能一直在下降。Teich 表达了歉意,并承诺会在第二天发布一篇深入的技术报告,说明问题的原因。同时,Teich 还表示 Heroku 将会提高用户应用 Web 请求队列的透明度,提供更好的文档、工具和沟通机制。

今天,Heroku 的产品经理 Jesper 如约在网站上发布了相关的技术细节

Rap Genius 方面的问题描述

Rap Genius 目前用户数量超过 1500 万,每月投入到 Heroku 上的成本约 2 万美元。十天前,Rap Genius 的开发人员在一系列 AB 基准测试中发现,自己的版权页面——一个纯静态页面——的平均响应时间居然达到了 6330ms。更大的问题在于,Heroku 的后台和 Heroku 的监控合作伙伴 New Relic 报告的数据是 40ms,相差了两个数量级。

于是他们去问 Heroku。Heroku 的工程师说,两个数值不同的原因在于“应用的请求需要在 dyno 这一层排队”。请求被处理的速度很快,时间都花在排队上了。(注:dyno 是 AWS 上的虚拟机。目前 Heroku 有两种活跃的堆栈,Bamboo 是老堆栈,运行 Debian 5.0;Cedar 是新堆栈,运行 Ubuntu 10.04。)

但是,在 Heroku 后台提供的日志功能当中,完全没有“dyno 这一层的排队等待时间”这一数值。

都是 routing mesh 的错

Rap Genius 的开发人员们四处翻查文档,终于发现了一个情况:Heroku 在 2010 年年中重新设计了 routing mesh,新系统的路由规则是:通过一个随机算法将请求分发给 dyno——无论 dyno 是否空闲。

Heroku 在 2009 年的文档表示,在智能路由规则下,请求只有在 dyno 可用的时候才会被路由到该 dyno,如果一个 dyno 上运行了一个长任务,路由会把请求提交至另一个 dyno,而不是继续排队。

于是,Rap Genius 方面认为,现在的延时问题都是 Heroku 偷偷修改了路由规则所导致的。

文章里按照两个路由规则做了一个模拟,结论是:在智能路由模式下用 80 台 dyno 可以搞定的并发请求数,在随机路由模式下需要 4000 台 dyno!

“天真的”随机路由模式,老旧且不一致的文档,监控度量的缺失,造成了 Rap Genius 方面的极大不满。他们希望 Heroku 能够将路由模式切换回智能模式。

Heroku 方面的说明

Heroku 的回应文章承认了延时问题的存在,对自己没能早些发现这一问题表示道歉。但是,Heroku 表示延时问题跟 2010 年的新路由规则无关,并简单介绍了 Bamboo 和 Cedar 上的路由机制。

2009 年开始启动的 Bamboo 堆栈仅支持 Ruby 语言,Rails 框架和 Thin 服务器。Bamboo 本身不支持并发,一个进程一次只能处理一个请求。所以在请求分发上,为支持 Bamboo 的架构,Heroku 设计了 HTTP Router,在路由层为请求消息进行排队,然后再分发给 dyno;每个路由自己建立自己的应用队列,并不存在全局的应用队列。

也就是说,2009 年文档上描述的“请求仅会被路由到空闲 dyno 上”的智能路由模式,其实仅针对单一路由,并非针对整个路由系统。在整个路由系统中,一直存在“dyno 层的排队”这一事件。在路由集群较小的情况下,这一事件发生的几率较低,所以整个集群看上去还比较智能。

新的路由机制针对在 2011 年开始运作的 Cedar 堆栈。由于 Cedar 希望支持 HTTP 轮询和区块响应(chunked response),支持 JVM、Node.js、Unicorn、Puma 等可以多线程、多进程的运行时,支持无状态架构,满足伸缩性需求,所以 Heroku 的工程团队为 Cedar 选择了随机路由模式。这一模式仅针对 Cedar,而不针对 Bamboo 上的老用户。

按照 Heroku 的说法,他们最初设想的是 Bamboo 用户会逐渐将自己的应用迁移到 Cedar 上面去,这样就可以保持一个较小的路由集群。然而现实情况是,Bamboo 上的应用一直在不断扩展,导致 Heroku 团队不得不往 Bamboo 的路由集群中不停地增加节点,这导致智能路由的效率越来越差。用 Jesper 的话来说,当智能路由集群的节点过多时,这个集群的工作方式基本上等同于一个随机的路由集群。

而 Cedar 用户感受到的延迟,则源于 Rails 无法处理并发请求,而大量 RoR 应用被按照官方文档的指导部署在了 Thin 服务器上——这是个单线程的服务器。Heroku 表示将应用迁移至 Puma 或 Unicorn 这样的并发 Web 服务器上能够缓解这个问题,他们会提供更多的文档和支持帮助用户完成此类迁移。

相关评论

Heroku 承认错误的态度得到了不少旁观者的称赞,但是 Heroku 针对延时问题提出的行动方案,却完全没能让 Rap Genius 方面满意。Rap Genius 方面针对 Heroku 的技术总结文章又展开了回应,做出如下评价:

  1. Heroku 真的刚刚知道这个问题么?2011 年 2 月他们就知道了吧
  2. Heroku 针对 Rails 的默认 Web 服务器是 Thin,无论在 Bamboo 还是 Cedar 上都是如此。为什么不把 Unicorn 设置为默认服务器?害怕占用太多内存么?
  3. Heroku 提出了行动方案,但完全不是解决方案。

Hacker News 上也普遍存在两种质疑:

  1. 即使可以并发,也只是暂时缓解延时的问题;当规模继续扩大时,延时的问题又会变糟糕。
  2. 多花钱用 PaaS 就是为了不折腾。如果多花钱还免不了折腾,还不如直接用 AWS。

对于 Heroku 遇到的路由延时问题,你有什么看法?大规模的应用适合放在 PaaS 上面跑么?欢迎讨论!


2 月 21 日更新:通过和虚拟座谈会的嘉宾进行前期沟通,发现 Rap Genius 原文中用于描述 Heroku routing mesh 智能路由算法的论文“The Power of Two Choices in Randomized Load Balancing”并非是 Heroku 的实际实现,而是 Rap Genius 根据 Heroku 对智能路由系统的功能描述而假设的一种智能路由的规则,并按照此规则进行模拟实验,得出“Heroku Swindle Factor”这个数值。为了减少误解,现在在本文中将该论文链接撤下。

后续的相关深度内容:


感谢马国耀对本文的审校。