这些日子,说 Heroku 处在风口浪尖一点都不为过,先前 InfoQ 就 Heroku 大用户 Rap Genius 的不满做了报道,虽然 Heroku 在官方博客上做了说明,但 Rap Genius 和众多网友并不买账,问题仍在延续,不妨让我们对整件事件做个回顾,看看能够从中得到什么启示。
事件回顾
2 月 13 日,Rap Genius 在其网站上发布了一篇文章,讲述了他们在Heroku 上遇到的问题,并做了分析。问题的导火线是之前为了解决一些JavaScript 的小问题,Rap Genius 在线上做了些ab 基准测试,结果却发现有些页面的响应时间远比Heroku 和New Relic 所报告的时间长。以静态的版权页面为例,Heroku 提供的平均响应时间是40ms,而Rap Genius 测试所得的时间却是6330ms。
Rap Genius 的同学在自己分析的同时,也和 Heroku 的工程师在进行沟通,最后确定问题的原因是处在 Heroku 的路由策略上,关键是 Heroku 的文档并未正确地描述其路由策略(在各个时期的不同文档中,对“智能路由”的描述都是只有在 Dyno 可用时才会将请求路由过去),但实际情况是路由采用了随机分配的策略,不考虑 Dyno 当时的情况,导致大量请求都在 Dyno 上排队,没有得到及时处理。更不幸的是 Heroku 的日志和监控并没有很好地反应实际情况。
这篇文章在 Hacker News 和 Reddit 上一经转发,也获得了不少关注,有网友就在评论中讨论了各种路由算法。在 Rap Genius 的文章中,他们用 R 做了个模拟,原本只需 75 个 Dyno 的一个应用,使用随机路由则需要 4000 个以上的 Dyno。
面对这一情况,Heroku 的危机公关并不成功,虽然 Oren Teich(Heroku 的 GM)及时在 Hacker News 和官方博客上发表声明,承认了他们在 Ruby on Rails 应用上的性能问题,并做了一些承诺;第二天,官方博客上如约发表了具体的技术分析及后续改进的计划。但这些举措都没能让用户满意,暂且不论 3 年来为此多收的费用,至少 Heroku 并没有写出真实的情况:
- 模糊了本次事件的时间——Rap Genius 的共同创始人 Tom Lehman 在 2 月 5 日就已经和 Heroku 的 CTO Adam Wiggins 讨论这个问题了,而且在 2 月 8 日发给他了模拟结果,因此 Heroku 并不是在 2 月 13 日才知道这件事情的。更有人早在 2011 年 2 月 17 日就写过博文反应这个问题,还在 Google Group 里进行了讨论,Oren Teich 还亲自答复过,所以 Heroku 早就知道文档和系统实现不符合,存在性能问题。
- 模糊了本次事件的影响范围——Heroku 的文中只说了 Bamboo 上的 Rails 应用的性能受到影响,Tom Lehman 调侃到为什么 Cedar 的性能没有受到影响,是因为它用 Thin(Cedar 上默认的 Web 服务器)跑 Rails 的性能就没好过。
问题剖析
在 Heroku 的技术分析里详细描述了造成问题的原因。
Bamboo 是早期的 Heroku 运行时技术栈,运行于 Debian 5.0 之上,提供了两套 Ruby VM——Ruby Enterprise Edition 2011.03 1.8.7-p334 和 MRI 1.9.2-p180,只支持 Ruby on Rails 2.x,使用 Thin 作为 Web 服务器。后来,随着技术的发展,Heroku 将默认的 Bamboo 换成了 Cedar ,从仅支持 Rails 演化为了支持多平台多语言(Ruby on Rails 的默认服务器同样为 Thin)。而问题也正是从这次大的系统架构升级开始的。
Thin 本质上是单线程的 Web 服务器,基于 EventMachine 来做多路复用,通过回调实现请求的并发处理。但要让 Rails 应用充分发挥 Thin 的优势,在编写应用时也要按照 EventMachine 的写法加以修改,对所用的库也有要求。所以,不管是 Bamboo 还是 Cedar,默认情况下还是单线程的,不能很好地支持并发请求处理。如果在 Cedar 中选择 Unicorn ,那么情况则会有所不同。
因为使用 Thin 的 Dyno 无法并发处理请求,所以最初 Routing Mesh 的路由策略是选择只有当 Dyno 可用时才会将请求分配给它。但是由于 Cedar 支持多语言多平台,因此 Routing Mesh 的策略也做了调整,改为了更加通用的随机分配,暂时无法处理的请求在 Dyno 对应的队列中排队,不考虑 Dyno 的负载。对于 Java 应用,这个策略简单有效,但是对于单线程服务器中运行的未经优化的 Rails 应用,这就很成问题了。
更致命的是 Heroku 的文档并没有反映出这一变化,而监控也没能体现这一排队的耗时。原本 Heroku 打算让 Bamboo 的用户继续使用原来的 Bamboo 路由,直到他们做好准备迁移 Cedar。可惜的是随着流量的增加,Heroku 往路由集群里加入越来越多的节点,慢慢的使用新策略的节点占比越来越高,于是 Bamboo 就渐渐地出现了性能的下降。至于 Cedar 上用 Thin 运行的 Rails 应用,自打其上线起,就因随机路由策略而性能不佳,因此 Tom Lehman 的调侃也不无道理。
经验教训
本次事件,作为云服务提供商的 Heroku 有不可推卸的责任,其 CTO 和 COO 在很久前就知情的情况下,没有有效修正该问题,对用户造成了巨大的影响。对于一家云服务提供商而言,在进行大的架构升级前应该进行充分的评估及后续的追踪;在发现问题后,应该及时修正,减少对用户的影响。除了关键的技术实力,还有很多其他细节需要考虑,比如相关的文档和客户支持,本次 Heroku 在这些方面也都做的有欠妥当——文档没有正确反映实际情况,虽然 CTO 和 COO 亲自应答这一问题,但没有实际的后续动作。
在文档方面,同为 PaaS 的 Cloudfoundry 就比较透明,一方面因其开源特性,使用者可以完全了解其运行机制,另一方面有大量的材料能帮助大家更好地使用它。同样以路由机制为例,EMC 中国研究院的颜开撰写的《新版CloudFoundry 揭秘》中详细说明了其路由的实现:nginx 结合Lua 脚本,由Lua 分析请求,直接转发给对应的Droplet,转发是随机的,但是请求中增加了Cookie 信息,由此实现粘性会话,尽量将请求转发到同一个Droplet 上。正是由于这些材料,Cloudfoundry 的使用者可以更好地正确使用Cloudfoundry 的公有云。
在客服方面,好的客服能及时帮助客户解决问题,更能带给客户好的使用体验,而糟糕的客服,则有可能造成客户流失。之前,国内的盛大云因客服的问题遭到投诉,最终用户放弃盛大云,转用其他产品。可见,要做好一个云服务平台,光有技术是远远不够的。
虽说使用PaaS 可以降低使用者的运维成本,无需再维护各种基础设施,同时提供适当的冗余,保证可用性。但是,这并不意味着使用者自己不需要下功夫,正确的架构必不可少,还需要一定的投入。
例如,本次Rap Genius 在路由的问题上吃亏,一定程度上也是由于其Web 请求的处理时间过长,才造成了队列中请求积压。对于耗时较长、占用资源的任务应该异步化,而非置于同步的Web 请求中,交由后台专门的Worker 处理更为合适(比如Heroku 上的Worker Dyno 就比Web Dyno 更适合这种任务)。eBay 在其分享中也多次提到了任务的异步化,可见异步化对于一个高负载高可用系统是何其重要。
要在故障之初就发现端倪,良好的监控是必不可少的,虽然各家云服务提供商在其产品中都有监控功能,但用户还是应该自己定制适当的监控。除了对服务端的监控,也不能忽视了用户端的监控,如果平时就能察觉用户访问的时间存在异常,那么早就可以发现问题。百姓网在Velocity 大会上就分享过他们的经验,从用户的点击开始获取用户的真实体验。此外,还可以使用 Google Analytics ,国内的监控宝也能对站点进行有效的监控。
随着网站的不断发展,集群也在不断壮大,此时应该将故障视为常态,即使在云端,故障也是不可避免的,AWS 在过去的一年里发生的重大故障就不下三次, 2011 年那次著名的 US-EAST-1 故障甚至还让“天网”的攻击没有发生。在这方面 Netflix 就做的非常领先,他们的“猴子军团”在业内非常有名,通过日常自己制造故障来验证系统能否在真正的故障到来时自行恢复或者降级,降低对用户的影响。
对于云服务的使用者,如果要达到很高的可用度,必须在架构上就将容错和容灾纳入考虑之中,比如节点失效、网络中断、机房断电,甚至是 AWS 可用区整个故障。公有 PaaS 在这方面为用户提供了一些支持,但是在 AWS 可用区故障容灾方面应该还没有很好的支持,比如跨可用区的数据冗余,主从如何选择都是需要考虑的问题,需要用户自己来继续完善。
要做好一个云服务平台需要投入很多的精力与成本,而要用好这个平台,在其中上搭建站点为最终用户创造更多价值也并绝非易事,希望这次 Heroku 的事件能给大家带来一些启示,不知读者朋友您又有何经验可以分享?
评论