Heroku私自修改路由产生的恶果
更新时间:2013/2/25 点击:1553次
2月14日,流行云计算平台服务商之一的Heroku遭到了一家小公司的“炮轰”,更为转折的是,在不到24小时,其所直接指向的问题已经被Heroku总经理Oren Teich所承认,并郑重道歉。一时之间,美国云计算领域哗然,并在业内掀起了有关PaaS平台技术讨论的热潮。
Rap Genius:高成本低性能的背后“故事”
2月14日,Rap Genius的James Somers发布了一篇“有血有肉”的 技术博文,内容直指“Heroku取代最初通过智能将请求分配到下一dyno的方式,对请求进行随机分配;用户将面对50倍圈钱或者是6秒的平均响应时间。”
这一涉嫌“欺诈收费”的内容通过一组对比数字直白地呈现了出来:
高成本:20000美金/月的成本支付1500万访问人数/月
低性能:6秒的平均响应时间(通过使用Heroku的随机路由)
看懂了么?(2010年时Facebook平均响应时间是1秒,Twitter和Myspace分别是2.93秒和3.61秒。)这只是其中的一组数据而已。为了让大家更明白,CSDN云计算频道特别将其博文进行了全文翻译:
低性能:6秒的平均响应时间(通过使用Heroku的随机路由)
看懂了么?(2010年时Facebook平均响应时间是1秒,Twitter和Myspace分别是2.93秒和3.61秒。)这只是其中的一组数据而已。为了让大家更明白,CSDN云计算频道特别将其博文进行了全文翻译:
为了理解这个诡异的响应,首先要知道想象中Heroku的工作方式。
当你把应用程序部署到Heroku时,你事实上是将其部署到一组不同的dyno中(虚拟Ubuntu服务器,基于AWS)。比如一个Rails应用程序,每个dyno只同时服务一个请求;每月的花费为36美元,如果你 购买New Relic插件的话,开销将达到79.2美元/月。
当用户通过你的网站发出请求时,请求首先通过Heroku的Router(它们称之为“routing mesh”),Router将把请求分配给特定的dyno。表面上看Router的作用是负责各个dyno中的负载均衡,这样的话不会出现:其它dyno处于闲置时,某个dyno却不停的工作。如果在某个阶段所有的dyno都处于工作状态,Router将会把请求组成队列 —— 一旦某个dyno可用,Router将会给队列中的请求按顺序分配 dyno。
这也是Heroku在其“How it Works”页阐述的:
2009版文档:
智能路由:routing mesh将检查每个dyno的有效性并进行做出相应的负载平衡。一旦(只有)dyno可用,Router就会给其分配对应的请求。如果一个dyno被一个长期运行的请求占用,避免造成同一个dyno上的工作积压,后续请求将会被分配给其它dyno。
2013版文档看起来神秘了一些:
2013版文档看起来神秘了一些:
智能路由:routing mesh将追踪所有dyno的位置、运行网络进程,并把相应的HTTP请求分配到相应的dyno。
然而在这个文档的其它地方,把之前旧版本解释的一清二楚:
然而在这个文档的其它地方,把之前旧版本解释的一清二楚:
Heroku.com堆栈只支持单线程请求,即使你的应用程序必须交叉处理多个请求,routing mesh永远都不会将一个以上的请求分配到同一个dyno上。
下面是一行典型的输出日志:
下面是一行典型的输出日志:
2013-02-13T20:47:22+00:00 heroku[router]:at=info method=GET path=/annotations/for_song_page?song_id=1373 host=rapgenius.com fwd=“129.138.136.18” dyno=web.112 queue=0 wait=0ms connect=2ms service=5983ms status=200 bytes=64144
Heroku日志版本甚至不包括dyno等待队列中花费时间的记录,可以看到等待队列长度queue=0(也始终是0),因为等待队列其实并不存在(设想)!那么完全可以推测Router队列中等待的时间就是服务所花费的时间(service=5983ms)。
Heroku日志版本甚至不包括dyno等待队列中花费时间的记录,可以看到等待队列长度queue=0(也始终是0),因为等待队列其实并不存在(设想)!那么完全可以推测Router队列中等待的时间就是服务所花费的时间(service=5983ms)。
在New Relic下同样如此:当它记录“Request Queuing”时,同样说的是在Router中花费的时间。对于Rap Genius来说,访问少时每个请求只有微不足道10ms。
Dyno Level的排队
这也是Heroku工程师讨论“请求会在dyno级别队列中等待”让我们如此诡异的原因,我们认为这永远都不会发生。“根据你的规模进行智能负载分配”就是你不该在其工作时发送请求到dyno!并且即使所有的dyno都处于工作状态,让Router接收请求也比直接发送到dyno来的更好一点。
如果你有幸找着了正确的文档,你将会发现Heroku置换了它的“智能负载分配”;使用了随机负载分配替换了曾今作为平台基础的智能负载分配:在网络处理中,routing mesh为HTTP请求使用了一个随机负载分配算法。
再次强调:
在2010年中期,Heroku重设计了routing mesh的路由方式;取代之前将请求分配给第一个可用dyno,将请求随机分配给dyno —— 不管目标dyno是否空闲。
这项决定并没有公开。而大部分的Heroku文档仍在明确或含蓄的称其还使用原来的方式。“时间花费在dyno等待队列中”在其正式日志中是无处可见的,同样在其非常昂贵的分析伙伴New Relic中也是无 处可见的。关键问题还在于这项改变并没有体现在它的价格中 —— 从Heroku发布后单个dyno的价格一直为36美元/月。
为什么会一再强调?因为随机对请求进行分配是愚蠢的!
就像在银行取号时,排到你时系统并不把你分配给第一个空闲的柜台,它会把你分到一个已经有客户在办理业务的柜台。那么你多久才会结束你的业务离开银行?那么柜员轮空的时间又有多少?如果你是这家银行的所有者,有一天管理员在未通知你的情况下,将取号系统换成一副色子,而在工作报告中却说一切照旧 —— 他不会告诉你客户们因为等那个单一的柜台花费了多久时间,非常糟糕? !
在Heroku之前称之为“智能路由”的体系中,一个dyno就会起到一个dyno的作用。你多购买一个,就是多购买一个性能以及并发性。事实上Heroku将并发性设计的与为你应用程序购买的dyno数量相等。
但是现在一切都改变了,因为路由系统不再智能。当你随机的分配请求 —— 我们将其称为“幼稚”的算法,并发性将远低于你购入dyno的数量。因为空闲的dyno只是存在看见请求的可能性(而非必然),而在dyno数量增加时这种可能性会显著的降低。通过购入新的dyno不再是解决负载过重的有效途径,因为你不能保证堆积的请求会发现那个节点。
那么为什么会这样?
很明显在Heroku新的随机路由算法下,你需要购买更多的dyno才会达到智能分配下的效果。那么你需要多购买多少个dyno?如果你的应用程序在旧的模式下只需要10个,那么在新的情况下你需要多少? 20?如果这样,那么Heroku等于多收了1倍的钱,这就是在“Heroku Swindle Factor(HSF)”中你多花费的支出。
想象一下,你认为随机路由机制值多少?HSF又从中获取多少倍利润?2?5?10?!
实际情况是,大型应用程序中,你付出的将是50倍!!!如果你的应用程序在智能路由情况只需要80个dyno,而在随机情况下需要的则是4000个!这样的话,如果你使用的是Rails(或者其它单线程,同 一时间单请求框架),Heroku将赚取你之前50倍的收益。
以上为我们通过模拟(R语言, 点击原博文查看代码)在两个模式下使用Rap Genius应用程序一个模型得出的结论,请求特性如下:
每分钟请求:9000次
平均请求时间:306毫秒
中间请求时间:46毫秒
请求次数呈如下分布:(来自一个真实的212K Rap Genius请求,Weibull分布)
平均请求时间:306毫秒
中间请求时间:46毫秒
请求次数呈如下分布:(来自一个真实的212K Rap Genius请求,Weibull分布)
1% 5% 10% 25% 50% 75% 90% 99% 99.9%
7ms 8ms 13ms 23ms 46ms 255ms 923ms 3144ms 7962ms
下面你将看到一分钟的模拟情况,首先是幼稚的路由世界,需要注意的是,随着时间的增长,请求堆积在个别的dyno中:
7ms 8ms 13ms 23ms 46ms 255ms 923ms 3144ms 7962ms
下面你将看到一分钟的模拟情况,首先是幼稚的路由世界,需要注意的是,随着时间的增长,请求堆积在个别的dyno中:
原图点击查看(可能需翻墙)
现在再看智能路由:障碍永远都不会发生,因为dyno在一个请求处理结束之前永远都不会遇见另一个请求。请求响应的速度与Rails处理的速度相同:
原图点击查看
下面是我们最终的统计结果:
如果Heroku使用的是智能理由,拥有75个dyno的应用程序可以零等待的每分钟处理9000个请求。但是使用一个“幼稚”的路由,同样的应用程序、同样多的dyno、同样速度接入请求、同样分布的请求次 数,将得到62%的排队率,平均排队时间为2.763秒。每个请求在排队中消耗的时间将是应用程序处理时间的6倍。
因为新购入dyno被使用频率越来越低,增加dyno获得的并发性越来越少,你必须增加大量的dyno来减少排队概率。实际上每减少一半的排队请求,你必须增加一倍的dyno数量。然而即使你增加了1倍的 dyno,请求排队所用的平均时间仍然会长于1秒。
为了把排队时间减少到可以接受的程度(小于10毫秒),你必须给你的应用程序配备4000个dyno,也就是50倍你在智能理由中使用的可分配dyno数量。
当然你可能真的将dyno数量提升到4000个,因为:第一,你将花费30万美元/月;第二,Postgres不可以同时处理那么多的链接。
所以唯一的解决方案就是让Heroku回归之前的智能路由。然而他们声称那种情况不利于扩展,并且会给新型并发应用程序(比如那些基于Node.js和Tornado)带来很多复杂性。但是Rails一直都是Heroku 的重点经济来源,而Rails也不是多线程。
然而一个为无阻塞、事件触发式实时用用程序服务器(比如节点或者类似)设计的路由层—— 如果将所有dyno能力都看作相同,那么将会给其带来与Rails同样的处境,这里情况刚好相反:可用的节点 会得到使用,而其它的节点在变成可用的之前是没有任何意义的。比起这些应用程序,更糟糕的是:Heroku不适合任何Rails应用程序。
我们尝试说服Heroku回到之前的智能路由上,但是他们不认为现在的机制存在问题。
Heroku对于这次延迟事件的反应及后续措施
在上面的博文中,Rap Genius控诉Heroku偷偷改变路由机制以实现其圈钱行为。不到24小时的时间,该文章在 Reddit和 Hacker News获得了超高关注,以至于Heroku总经理Oren Teich不得不在Heroku官方博客中做出公开表态。
Heroku:郑重道歉并将公布技术说明
Heroku总经理Oren Teich表示:“我们的一个客户提出了一个重要的问题。是的,在过去的三年中,Ruby on Rails应用程序确实造成了性能的退化。但我们无法解释产品的工作原理,这是我们大社区的失败。我郑重道歉,并承诺解决这个问题。”
在应用程序的网络请求队列中,快速提供更多的可见性
提升我们的文档和网站来准确反映产品
提供工具来理解以及提升应用程序的性能
与客户密切合作,制定长期解决方案
当然除下以上内容,Oren Teich还宣布在2月16日发布一个深入的技术审查。
提升我们的文档和网站来准确反映产品
提供工具来理解以及提升应用程序的性能
与客户密切合作,制定长期解决方案
当然除下以上内容,Oren Teich还宣布在2月16日发布一个深入的技术审查。
2月16日Heroku的“深入技术审查”
Heroku的产品经理Jesper如约在其官方博客上发布了简单的技术细节,并称这次暴发的问题将成为"Heroku前进的动力"。
同样Jesper承认了延时问题的存在并致歉,然而他指出延时问题与随机路由机制无关 ——新的路由机制只存在Cedar上,而Bamboo上仍然使用着很早版本的路由机制。Jesper用简单的例子剖析了其根结所在:
自2009年Heroku启动的Bamboo堆栈仅支持Ruby语言,Rails框架和Thin服务器。当然Bamboo不支持并发,单线程同时只能服务于一个请求。为了支持这个架构,Heroku的HTTP路由被设计成在路由层支持请求队列,使其能有效的为可用dyno分布请求。
取代为每个应用程序准备全局请求队列,Bamboo单路由范围的队列;虽然没有全局队列有效,但是在集群较小时仍会工作良好。
为了更好的理解,请看下图:
请求通过Router 1、2、3发往两个dyno:除下一个需要5000毫秒的请求,大多数的请求只需要花费50毫秒。在上图中可用看到:花费6000秒的请求流入Router 1,然后被分配给dyno 1。因为只是单Router范围(非全局)的队列,在处理完成前Router 1不会给dyno 1分配请求;然而当请求继续涌入时,因为缺乏通信,Router 2、3在得到dyno 2与3正忙时,仍然会给dyno 1分配请求。这样就会出现下图的情况:
无需置疑,dyno 1上将出现大量请求积压。随着Router数量的增加,路由的效率将变的低下。这就是Rails应用程序在Bamboo堆栈上发生的问题。在Bamboo的历史上,集群一直都很小,这样的话看起来一直非常智能。而新的路由机制运用于2011年启动的Cedar堆栈,为了支持HTTP特点(长轮询和分块响应等)、多线程和多进程(类JVM、Node.js、Puma等)以及用于优化可靠性和扩展性的无状态体系结构,Cedar使用了随机路由模式。
Jesper:是什么导致了Bamboo路由的退化
理论上Bamboo用户在准备迁往Cedar之前,Bamboo堆栈可以继续良好运行。不幸的是,事与愿违:随着传输增加,Heroku不得不向路由集群中加入更多的节点;单点请求队列渐渐失去作用,直至演变成随机路由。Jesper强调,这种演变与Bamboo的路由编码无关,更与Cedar的新路由机制无关。
为了改善服务,将采取以下措施:
改善文档,与Heroku产品同步
移除Heroku及合作伙伴New Relic错误与混淆的报告
增加请求队列对应用程序响应时间的指标
增加改善提高延时和请求队列的附加工具
在Cedar上完善对Rails并发请求的支持
尽管Heroku承认错误的态度得到了很多人的支持,然而Rap Genius显然对这些解决方案并不看好,称这个问题早在 2011年2月就提出过。并作出了针对回应:
移除Heroku及合作伙伴New Relic错误与混淆的报告
增加请求队列对应用程序响应时间的指标
增加改善提高延时和请求队列的附加工具
在Cedar上完善对Rails并发请求的支持
尽管Heroku承认错误的态度得到了很多人的支持,然而Rap Genius显然对这些解决方案并不看好,称这个问题早在 2011年2月就提出过。并作出了针对回应:
既然在Cedar中支持对Rails的并发请求,Cedar又是那么为什么不使用类Unicorn的服务器作为默认的服务器?
因为dyno只有512M内存,如果使用Unicorn,根据你的应用程序每个dyno你可能只会获得2个Unicorn,低于4个将不能获得合理的吞吐量。
而Hacker News网友同样认为并发只是暂时缓解问题,当规模扩大时,延时的问题又会变的糟糕。更有网友觉得既然花那么多钱在PaaS上得不到享受,还不如自己折腾AWS。
因为dyno只有512M内存,如果使用Unicorn,根据你的应用程序每个dyno你可能只会获得2个Unicorn,低于4个将不能获得合理的吞吐量。
而Hacker News网友同样认为并发只是暂时缓解问题,当规模扩大时,延时的问题又会变的糟糕。更有网友觉得既然花那么多钱在PaaS上得不到享受,还不如自己折腾AWS。
写在最后:
中国老语讲:伤筋动骨一百天。这次重大危机对Heroku,甚至是对所有云计算服务提供商而言都是一件大事。毕竟,在美国,索赔机制那是相当“发达”的。另一方面,也是对国内云计算服务企业的一记警钟。文章最后,我们蓦然发现一件事情:中国诸多巨无霸企业在升级及进行各类操作时,也很少会知会相关应用使用者。这个,该如何来说呢?(包研/审校)
Update1:
看到博友评论,有些朋友认为“Heroku只是改了路由算法,导致Rails应用性能不佳,但又没更新文档和告知用户,并不是变向圈钱。”但不要忘记,Rap Genius究竟为此支付了多少宝贵的成本(20000美金/月的成本支付1500万访问人数/月 )?而且只有这一家遭受了不白损失么?
如果有其他观点,欢迎@CSDN云计算。我们也希望更多朋友加入讨论,还原事实。
最后,为尊重大家的意见,我们在标题中加上“?”
Update2:
经过与诸位业内朋友的讨论,将标题调整为“ 用户揭露Heroku私自修改路由造成高支出”。