Twitter 研发人员 John Oskasson 分析 Twitter 后台软件栈

阅读数:7065 2013 年 2 月 12 日

话题:JVMApacheDevOps语言 & 开发架构

John Oskarsson是 Twitter 的一名研发人员。最近,他撰写的一篇博客中提到了 Twitter 后台的软件栈,其中包括:服务支持、服务监控、服务协调、跟踪系统、负载测试、服务发现、Hadoop 作业等诸多方面。这篇博客也被 Twitter 工程团队的官方博客引用。

文中提到:

出于性能和成本等多种原因,Twitter 在工程方面付出了巨大努力,将网站后台拆散为更小的基于 JVM 的服务。其额外作用是,我们可以以开源方式开放由此产生的多个应用库和其他工具。

尽管已经有很多信息介绍这些项目,但是对于他们口中这非官方的的“Twitter Stack”,到目前并没有全面介绍。这是 John 的初衷。

要指出的是:这里所有的相关信息,都是关于开源项目的。

……

不过,下面这些项目,虽然不全是 Twitter 自己构想出来的,而且其他公司也有类似方案。我还是认为这些软件十分强大,基于它们构成的平台,足以开发新服务。

我将会从 Scala 语言的角度讲解这些项目,但是其中很多用 Java 也是没有问题的。

支持服务——Finagle

John 首先提到了Finagle。这是服务核心之一。Finagle 抽象出了 RPC 系统的底层核心功能,大大降低了服务开发人员需要处理的复杂性。让开发人员可以专心编写业务逻辑,而不是处理分布式系统的底层细节。网站本身使用这些服务完成运维操作,或是获取产生 HTML 的数据。在 Twitter 里面,内部服务都使用 Thrift 协议,但是 Fingale 支持其他协议,包括 Protocol buffers 和 HTTP。

使用 Finagle 设置一个服务需要四个步骤:

  1. 编写一个 Thrift 文件,定义 API。其中应该包含结构体、异常和方法,用来描述服务的功能。可以查看Thrift 的接口描述语言文档(Interface Description Language,简称 IDL),特别是结尾处的例子。
  2. 使用 Thrift 文件作为代码生成器的输入,生成你需要的语言代码。对于基于 Scala 和 Finagle 的项目,John 推荐Scrooge
  3. 实现 Thrift IDL 中的 Scala 特性。这就是服务真正要实行的功能。
  4. 为 Finagle 服务构建器提供上述实现的实例,还有用来绑定的端口,还有其他启动服务需要的任何设定。

似乎这与平常使用 Thrift 没有区别,但是 Finagle 中有很多改进,比如出色的监控支持、跟踪,Finagle 还能让开发人员以异步方式编写服务。同时,Finagle 也可用来作为客户端,处理超时、重试和负载均衡。

服务监控——Ostrich

当一个重要的 Finagle Thrift 服务运行起来之后,需要保持它一直正常运行,对这个服务的监控就很重要。Ostrich可以很容易地暴露服务的多种指标。John 在文中列出了具体的代码示例。

Ostrich 运行一个 http 管理接口,可以暴露指标以及其他功能。只需要访问一个特定的 json 文件,就能得到当前的指标快照。在 Twitter,每个服务的状态都会从 Ostrich 读取出来,然后经过内部的观察和分析栈,提供漂亮的图表、警告和其他机制。

Ostrich 还可以处理服务的配置,能够以平滑的方式关闭所有组件。具体截图可以参考该演示幻灯

跟踪系统——Zipkin

Ostrich 和 Finagle 组合起来,可以提供很好的服务水平指标。然而,面向服务的架构存在一个问题:很难得到一个请求通过整个软件栈的全面性能概览。比如,你需要改善某个特定外部 api 访问点的性能,这时候,使用Zipkin,你就能以可视化的表现方式,知道满足该请求的时间都用在了哪里。可以将其看做后台的 Firebug 或者 Chrome 开发人员工具。Zipkin 是基于 Google Dapper 论文实现的跟踪系统。

集群管理——Mesos

Mesos是一个“集群管理器,在分布式应用和框架之间提供高效的资源隔离和共享”。

Mesos 的核心是一个开源的 Apache 孵化项目。在其上,你可以运行调度器,处理特定技术,比如 Storm 和 Hadoop。其背后理念是:同样的硬件应该可以用作多种用途,降低资源浪费。

除在 Mesos 上使用 Storm 之外,Twitter 还部署了一些基于 JVM 的服务到内部的 Mesos 集群上。配置之后,它可以处理多样化的机架,如果一台服务器宕机,它可以重新调度。

Mesos 带来的限制有其正面效益:强制满足多种良好的分布式系统实践。比如:

  • 服务所有者不应该对作业的寿命有任何假定,Mesos 调度器可以随时将作业移动到新的主机上。
  • 作业不应该写入本地磁盘,因为不能保证持久性。
  • 部署工具和配置不应使用静态服务器列表,因为 Mesos 会假定部署到动态环境。

负载测试——lago

在将新服务部署到生产环境之前,应该要检查它在负载下的表现。这就是lago(之前的 Parrot)的作用——负载测试框架,易于使用。

协调分步式系统——ZooKeeper

Apache 项目,完成各种分布式系统之间的协调。Twitter 用它来发现服务。Finagle 服务会在 ZooKeeper 中使用 ServeSet 库注册,可以参考finagle-serversets。客户端只要说它们希望与“A 数据中心中供 B 服务使用的生产集群”通信,ServerSet 实现就会确保提供最新的主机列表。当新的计算资源加入后,客户端会自动收到通知,并开始在所有服务器之间进行负载均衡。

开发 Hadoop 作业——Scalding

Scalding是一个 Scala 库,方便开发 Hadoop 中的作业。开发人员不需要编写底层的 map 和 reduce 功能,用 Scalding 可以像写自然的 Scala 语言一样开发。

虽然已经有很多可以编写 Hadoop 作业的工具,但如果项目使用 Scala 开发,用 Scalding 编写 Hadoop 作业还是很方便的。其语法接近 Scala 集合开发库中使用的语法,而且使用 Scalding,开发人员以同样代码可以处理上 T 的数据。

JVM 调优——javmgcprof

使用 JVM 处理对时间敏感的请求,垃圾回收机制带来的暂停会有很不好的影响。如果运气不好,GC 暂定可能会在错误的时间出现,导致某些请求性能骤降,甚至超时。最坏的情况甚至会导致宕机。

要应对 GC 问题,首先要做的是调整 JVM 启动参数,以配合要执行的服务。John 建议读者阅读 Attila Szegedi 的这些幻灯片。此前 Attila 也曾在 2011 年的 QCon 杭州上做过相关分享

使用 jvmgcprof,可以减少服务产生的垃圾,将 GC 带来的问题最小化。在启动服务时使用 jvmgcprof,就能达到这个目的。用 Ostrich 跟踪服务的指标,并以之告诉 jvmgcprof 哪个指标表明工作结束。John 在文中列出了一个具体的例子,并建议大家阅读jvmgcprof 的 Readme 文件

在总结中,John 指出:

虽然无甚奇特之处,但我们还是发现:有一个公关栈令我们受益匪浅。一个团队完成的改进和 bug 修复能够让其他人受益。当然也有不好的一面,有些时候,引入的 bug 会导致其他服务出问题。不过,举个例子,当开发 Zipkin 时,知道其他人都在使用 Finagle,这非常有帮助。因为当我们开发完成后,他们马上就能得到免费的跟踪功能。