《天空之城》助Twitter刷新纪录,新架构功不可没_最新动态_新闻资讯_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 新闻资讯 > 最新动态 > 《天空之城》助Twitter刷新纪录,新架构功不可没

《天空之城》助Twitter刷新纪录,新架构功不可没

 2013/8/26 12:57:01    程序员俱乐部  我要评论(0)
  • 摘要:英文原文:new-tweets-per-second-record-and-how背景补充:日本网民一直都有在电视节目播出的同时,在网络平台上吐槽或跟随片中角色喊出台词的习惯,被称作“实况”行为。宫崎骏监督的名作动画《天空之城》于2013年8月2日晚在NTV电视台迎来14次电视重播。当剧情发展到男女主角巴鲁和希达共同念出毁灭之咒“Blase”时,众多网友也在推特上同时发出这条推特,创造了每秒推特发送数量的新纪录。根据推特日本官方帐号
  • 标签:Twitter 架构
class="topic_img" alt=""/>

  英文原文:new-tweets-per-second-record-and-how

  背景补充:日本网民一直都有在电视节目播出的同时,在网络平台上吐槽或跟随片中角色喊出台词的习惯,被称作“实况”行为。宫崎骏监督的名作动画《天空之城》于 2013 年 8 月 2 日晚在 NTV 电视台迎来 14 次电视重播。当剧情发展到男女主角巴鲁和希达共同念出毁灭之咒“Blase”时,众多网友也在推特上同时发出这条推特,创造了每秒推特发送数量的新纪录。

Castle in the Sky

  根据推特日本官方帐号,当地时间 8 月 2 日晚 11 时 21 分 50 秒,因为“Blase 祭”的影响,推特发送峰值达到了 143,199 次/秒。这一数字高于此前推特发送峰值的最高纪录,2013 年日本时区新年时的 33,388 次/秒。更高于拉登之死(5106 次/秒)、东日本大地震(5530 次/秒)、美国流行天后碧昂斯宣布怀孕(8868 次/秒)。(前两段摘编自微漫画

  下图是峰值发生的邻近时间段的访问频率图,我们通常每天的推文数是 5 亿条,平均下来每秒大概产生 5700 条。这个峰值大概是稳定状态下访问量的 25 倍!

  在这个峰值期间,用户并没有感觉到暂时性的功能异常。无论世界上发生了什么,Twitter 始终在你身边,这是我们的目标之一。

  “新的 Tweets 峰值诞生:143,199 次 Tweets 每秒。通常情况:5 亿次每天;平均值 5700Tweets 每秒”

  这个目标在 3 年前还是遥不可及的,2010 年世界杯直接把 Twitter 变成了全球即时沟通的中心。每一次射门、罚球、黄牌或者红牌,用户都在发推文,这反复地消耗着系统带宽,从而使其在短时间内无法访问。工程师们在这期间彻夜工作,拼命想找到并实现一种方法可以把整个系统的负载提升一个量级。不幸的是,这些性能的提升很快被 Twitter 用户的快速增长所淹没,工程师们已经开始感到黔驴技穷了。

  经历了那次惨痛的经历,我们决定要回首反思。那时我们决定了要重新设计 Twitter,让它能搞定持续增长的访问负载,并保证平稳运行。从那开始我们做了很大努力,来保证面临世界各地发生的热点事件时,Twitter 仍能提供稳定的服务。我们现在已经能扛住诸如播放“天空之城”,举办超级碗,庆祝新年夜等重大事件带来的访问压力。重新设计/架构,不但使系统在突发访问峰值期间的稳定性得到了保证,还提供了一个可伸缩的平台,从而使新特性更容易构建,其中包括不同设备同步消息,使 Tweets 包含更丰富内容的 Twitter 卡,包含用户和故事的富搜索体验等等特性。其他更多的特性也即将呈现。

  下文将详述我们做了的事情。我们学到了很多。我们改变了我们的工程师组织架构。并且,在将来的数个星期,我们将发布更多的文章对本文提到的一些主题进行深入讲解。

  开始重新架构

  2010 年世界杯尘埃落定,我们总览了整个项目,并有如下的发现

  • 我们正运行着世界上最大的 Ruby on Rails 集群,我们非常快速的推进系统的演进–在那时,大概 200 个工程师为此工作,无论是新用户数还是绝对负载都在爆炸式的增长,这个系统没有倒下。它还是一个统一的整体,我们的所有工作都在其上运行,从管理纯粹的数据库,memcache 连接,站点的渲染,暴露共有 API 这些都集中在一个代码库上。这不但增加了程序员搞清整个系统的难度,也使管理和同步各个项目组变得更加困难。
  • 我们的存储系统已经达到阈值–我们依赖的 MySQL 存储系统是临时切分的,它只有一个单主节点。这个系统在消化/处理快速涌现的 tweets 时会陷入麻烦,我们在运营时不得不不断的增加新的数据库。我们的所有数据库都处于读写的热点中。
  • 我们面临问题时,只是一味的靠扔进更多的机器来扛住,并没有用工程的方式来解决它–根据机器的配置,前端 Ruby 机器的每秒事务处理数远没有达到我们预定的能力。从以往的经验,我们知道它应该能处理更多的事务。
  • 最后,从软件的角度看,我们发现自己被推到了一个”优化的角落“,在那我们以代码的可读性和可扩展性为代价来换取性能和效率的提升。

  结论是我们应该开启一个新工程来重新审视我们的系统。我们设立了三个目标来激励自己。

  • Twitter 一直都需要一个高屋建瓴的建构来确保性能/效率/可靠性,我们想要保证在正常情况下有较好的平均系统响应时间,同时也要考虑到异常峰值的情况,这样才能保证在任何时间都能提供一致的服务和用户体验。我们要把机器的需求量降低 10 倍,还要提高容错性,把失败进行隔离以避免更大范围的服务中断–这在机器数量快速增长的背景下尤为重要,因为机器数的快速增长也意味着单体机器故障的可能性在增加。系统中出现失败是不可避免的,我们要做的是使整个系统处于可控的状态。
  • 我们要划清相关逻辑间的界限,整个公司工作在一个的代码库上的方式把我们搞的很惨,所以我们开始尝试以基于服务的松耦合的模式进行划分模块。我们曾经的目标是鼓励封装和模块化的最佳实践,但这次我们把这个观点深入到了系统层次,而不是类/模块或者包层。
  • 最重要的是要更快的启动新特性。以小并自主放权的团队模式展开工作,他们可以内部决策并发布改变给用户,这是独立于其他团队的。

  针对上面的要求,我们构建了原型来证明重新架构的思路。我们并没有尝试所有的方面,并且即使我们尝试的方面在最后也可能并像计划中那样管用。但是,我们已经能够设定一些准则/工具/架构,这些使我们到达了一个憧憬中的更靠谱的状态。

  The JVM VS the Ruby VM

  首先,我们在三个维度上评估了前端服务节点:CPU,内存和网络。基于 Ruby 的机器在 CPU 和内存方面遭遇瓶颈–但是我们并未处理预计中那么多的负载,并且网络带宽也没有接近饱和。我们的 Rails 服务器在那时还不得不设计成单线程并且一次处理一个请求。每一个 Rails 主机跑在一定数量的 Unicorn 处理器上来提供主机层的并发,但此处的复制被转变成了资源的浪费(这里译者没太理清,请高手矫正,我的理解是 Rails 服务在一台机器上只能单线程跑,这浪费了机器上多核的资源)。归结到最后,Rails 服务器就只能提供 200~300 次请求每秒的服务。

  Twitter 的负载总是增长的很快,做个数学计算就会发现搞定不断增长的需求将需要大量的机器。

  在那时,Twitter 有着部署大规模 JVM 服务的经验,我们的搜索引擎是用 Java 写的,我们的流式 API 的基础架构还有我们的社交图谱系统 Flock 都是用 Scala 实现的。我们着迷于 JVM 提供的性能。在 Ruby 虚拟机上达到我们要求的性能/可靠性/效率的目标不是很容易,所以我们着手开始写运行在 JVM 上的代码。我们评估了这带来的好处,在同样的硬件上,重写我们的代码能给我们带来 10 倍的性能改进–现今,我们单台服务器达到了每秒 10000-20000 次请求的处理能力。

  Twitter 对 JVM 存在相当程度的信任,这是因为很多人都来自那些运营/调配着大规模 JVM 集群的公司。我们有信心使 Twitter 在 JVM 的世界实现巨变。现在我们不得不解耦我们的架构从而找出这些不同的服务如何协作/通讯。

  编程模型

  在 Twitter 的 Ruby 系统中,并行是在进程的层面上管理的:一个单个请求被放进某一进程的队列中等待处理。这个进程在请求的处理期间将完全被占用。这增加了复杂性,这样做实际上使 Twitter 变成一个单个服务依赖于其他服务的回复的架构。基于 Ruby 的进程是单线程的,Twitter 的响应时间对后台系统的响应非常敏感,二者紧密关联。Ruby 提供了一些并发的选项,但是那并没有一个标准的方法去协调所有的选项。JVM 则在概念和实现中都灌输了并发的支持,这使我们可以真正的构建一个并发的编程平台。

  针对并发提供单个/统一的方式已经被证明是有必要的,这个需求在处理网络请求是尤为突出。我们都知道,实现并发的代码(包括并发的网络处理代码)是个艰巨的任务,它可以有多种实现方式。事实上,我们已经开始碰到这些问题了。当我们开始把系统解耦成服务时,每一个团队都或多或少的采用了不尽相同的方式。例如,客户端到服务的失效并没有很好的交互:这是由于我们没有一致的后台抗压机制使服务器返回某值给客户端,这导致了我们经历了野牛群狂奔式的疯狂请求,客户端猛戳延迟的服务。这些失效的区域警醒我们–拥有一个统一完备的客户/服务器间的库来包含连接池/失效策略/负载均衡是非常重要的。为了把这个理念深入人心,我们引入了”Futures and Finagle”协议

  现在,我们不仅有了一致的做事手段,我们还把系统需要的所有东西都包含进核心的库里,这样我们开新项目时就会进展飞速。同时,我们现在不需要过多的担心每个系统是如何运行,从而可以把更多的经历放到应用和服务的接口上。

  独立的系统

  我们实施了架构上的重大改变,把集成化的 Ruby 应用变成一个基于服务的架构。我们集中力量创建了 Tweet 时间线和针对用户的服务–这是我们的核心所在。这个改变带给组织更加清晰的边界和团队级别的责任制与独立性。在我们古老的整体/集成化的世界,我们要么需要一个了解整个工程的大牛,要么是对某一个模块或类清楚的代码所有者。

  悲剧的是,代码膨胀的太快了,找到了解所有模块的大牛越来越难,然而实践中,仅仅依靠几个对某一模块/类清楚的代码作者又不能搞定问题。我们的代码库变得越来越难以维护,各个团队常常要像考古一样把老代码翻出来研究才能搞清楚某一功能。不然,我们就组织类似“捕鲸征程”的活动,耗费大量的人力来搞出大规模服务失效的原因。往往一天结束,我们花费了大量的时间在这上面,而没有精力来开发/发布新特性,这让我们感觉很糟。

  我们的理念曾经并一直都是–一个基于服务的架构可以让我们并行的开发系统–我们就网络 RPC 接口达成一致,然后各自独立的开发系统的内部实现–但,这也意味着系统的内部逻辑是自耦合的。如果我们需要针对 Tweets 进行改变,我们可以在某一个服务例如 Tweets 服务进行更改,然后这个更改会在整个架构中得到体现。然而在实践中,我们发现不是所有的组都在以同样的方式规划变更:例如一个在 Tweet 服务的变更要使 Tweet 的展现改变,那么它可能需要其他的服务先进行更新以适应这个变化。权衡利弊,这种理念还是为我们赢得了更多的时间。

  这个系统架构也反映了我们一直想要的方式,并且使 Twitter 的工程组织有效的运转。工程团队建立了高度自耦合的小组并能够独立/快速的展开工作。这意味着我们倾向于让项目组启动运行自己的服务并调用后台系统来完成任务。这实际也暗含了大量运营的工作。

  存储

  即使我们把我们板结成一坨的系统拆开成服务,存储仍然是一个巨大的瓶颈。Twitter 在那时还把 tweets 存储在一个单主的 MySQL 数据库中。我们采用了临时数据存储的策略,数据库中的每一行是一个 tweet,我们把 tweet 有序的存储在数据库中,当一个库满了我们就新开一个库然后重配软件开始往新库中添加数据。这个策略为我们节省了一定的时间,但是面对突发的高访问量,我们仍然一筹莫展,因为大量的数据需要被串行化到一个单个的主数据库中以至于我们几台局部的数据库会发生高强度的读请求。我们得为 Tweet 存储设计一个不同的分区策略。

  我们引入了 Gizzard 并把它应用到了 tweets,它可以创建分片并容错的分布式数据库。我们创造了T-Bird (没懂啥意思,意思是我们的速度快起来了?)。这样,Gizzard 充当了 MySQL 集群的前端,每当一个 tweet 抵达系统,Gizzard 对其进行哈希计算,然后选择一个适当的数据库进行存储。当然,这意味着我们失去了依靠 MySQL 产生唯一 ID 的功能。Snowflake 很好的解决了上述问题。Snowflake 使我们能够创建一个几乎可以保证全局唯一的 ID。我们依靠它产生新的 tweet ID,作为代价,我们将没有“把某数加 1 产生新 ID”的功能。一旦我们得到一个 ID 我们靠 Gizzard 来存储它。假设我们的哈希算法足够好,从而我们的 tweets 是接近于均匀的分布于各个储存的,我们就能够实现用同样数量的数据库承载更多的数据。我们的读请求同样也接近平均的分布于整个分布式集群中,这也增加了我们的吞度量。

  可观察性和可统计性

  把那坨脆弱的板结到一起的系统变成一个更健壮的/良好封装的/但也蛮复杂的/基于服务的应用。我们不得不搞出一些工具来使管理这头野兽变得可能。基于大家都在快速的构建各种服务,我们需要一种可靠并简单的方式来得到这些服务的运行情况的数据。数据为王是默认准则,我们需要是使获取上述的数据变得非常容易。

  当我们将要在一个快速增长的巨大系统上启动越来越多的服务,我们必须使这种工作变得轻松。运行时系统组开发为大家开发了两个工具:Viz 和 Zipkin。二者都暴露并集成到了 Finagle,所以所有基于 Finagle 的服务都可以自动的获取到它们。

 stats.timeFuture ("request_latency_ms") {

  // dispatch to do work 
  }

  上面的代码就是一个服务生成统计报告给 Via 所需做的唯一事情。从那里,任何 Viz 用户都可以写一个查询来生成针对一些有趣的数据的时间/图表,例如第 50% 和第 99% 的 request_latency_ms。

  运行时配置和测试

  最后,当我们把所有的好东西放一起时,两个看似无关的问题摆在面前:第一,整个系统的启动需要协调多个系列的不同的服务,我们没有一个地方可以把 Twitter 这个量级的应用所需要的服务弄到一起。我们已经不能依靠通过部署来把新特性展现给客户,应用中的各个服务需要协调。第二,Twitter 已经变得太庞大,在一个完全封闭的环境下测试整个系统变得越来越困难。相对而言,我们测试自己孤立的系统是没有问题的–所以我们需要一个办法来测试大规模的迭代。我们接纳了运行时配置。

  我们通过一个称作 Decider 的系统整合所有的服务。当有一个变更要上线,它允许我们只需简单开启一个开关就可以让架构上的多个子系统都和这个改变进行几乎即时的交互。这意味着软件和多个系统可以在团队认为成熟的情况下产品化,但其中的某一个特性不需要已经被激活。Decider 还允许我们进行二进制或百分比的切换,例如让一个特性只针对x%的用户开放。我们还可以先把完全未激活并完全安全的特性部署上线,然后梯度的开启/关闭,知道我们有足够的自信保证特性可以正确的运行并且系统可以负担这个新的负荷。所有这些努力都可以减轻我们进行团队之间沟通协调的活动,取而代之我们可以在系统运行时做我们想要的定制/配置。

  (译注:Twitter 官博文章后面还有一段内容,不过有点“吹牛”嫌疑,就不翻译了。有兴趣的朋友,请自己去看。)

发表评论
用户名: 匿名