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 设置一个服务需要四个步骤:
- 编写一个 Thrift 文件,定义 API。其中应该包含结构体、异常和方法,用来描述服务的功能。可以查看 Thrift 的接口描述语言文档(Interface Description Language,简称 IDL),特别是结尾处的例子。
- 使用 Thrift 文件作为代码生成器的输入,生成你需要的语言代码。对于基于 Scala 和 Finagle 的项目,John 推荐 Scrooge 。
- 实现 Thrift IDL 中的 Scala 特性。这就是服务真正要实行的功能。
- 为 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,这非常有帮助。因为当我们开发完成后,他们马上就能得到免费的跟踪功能。
评论