John Oskarsson是Twitter的一名研发人员。最近,他撰写的一篇博客中提到了Twitter后台的软件栈,其中包括:服务支持、服务监控、服务协调、跟踪系统、负载测试、服务发现、Hadoop作业等诸多方面。这篇博客也被Twitter工程团队的官方博客引用。
文中提到:
出于性能和成本等多种原因,Twitter在工程方面付出了巨大努力,将网站后台拆散为更小的基于JVM的服务。其额外作用是,我们可以以开源方式开放由此产生的多个应用库和其他工具。
尽管已经有很多信息介绍这些项目,但是对于他们口中这非官方的的“Twitter Stack”,到目前并没有全面介绍。这是John的初衷。
要指出的是:这里所有的相关信息,都是关于开源项目的。
……
不过,下面这些项目,虽然不全是Twitter自己构想出来的,而且其他公司也有类似方案。我还是认为这些软件十分强大,基于它们构成的平台,足以开发新服务。
我将会从Scala语言的角度讲解这些项目,但是其中很多用Java也是没有问题的。
John首先提到了Finagle。这是服务核心之一。Finagle抽象出了RPC系统的底层核心功能,大大降低了服务开发人员需要处理的复杂性。让开发人员可以专心编写业务逻辑,而不是处理分布式系统的底层细节。网站本身使用这些服务完成运维操作,或是获取产生HTML的数据。在Twitter里面,内部服务都使用Thrift协议,但是Fingale支持其他协议,包括Protocol buffers和HTTP。
使用Finagle设置一个服务需要四个步骤:
似乎这与平常使用Thrift没有区别,但是Finagle中有很多改进,比如出色的监控支持、跟踪,Finagle还能让开发人员以异步方式编写服务。同时,Finagle也可用来作为客户端,处理超时、重试和负载均衡。
当一个重要的Finagle Thrift服务运行起来之后,需要保持它一直正常运行,对这个服务的监控就很重要。Ostrich可以很容易地暴露服务的多种指标。John在文中列出了具体的代码示例。
Ostrich运行一个http管理接口,可以暴露指标以及其他功能。只需要访问一个特定的json文件,就能得到当前的指标快照。在Twitter,每个服务的状态都会从Ostrich读取出来,然后经过内部的观察和分析栈,提供漂亮的图表、警告和其他机制。
Ostrich还可以处理服务的配置,能够以平滑的方式关闭所有组件。具体截图可以参考该演示幻灯。
Ostrich和Finagle组合起来,可以提供很好的服务水平指标。然而,面向服务的架构存在一个问题:很难得到一个请求通过整个软件栈的全面性能概览。比如,你需要改善某个特定外部api访问点的性能,这时候,使用Zipkin,你就能以可视化的表现方式,知道满足该请求的时间都用在了哪里。可以将其看做后台的Firebug或者Chrome开发人员工具。Zipkin是基于Google Dapper论文实现的跟踪系统。
Mesos是一个“集群管理器,在分布式应用和框架之间提供高效的资源隔离和共享”。
Mesos的核心是一个开源的Apache孵化项目。在其上,你可以运行调度器,处理特定技术,比如Storm和Hadoop。其背后理念是:同样的硬件应该可以用作多种用途,降低资源浪费。
除在Mesos上使用Storm之外,Twitter还部署了一些基于JVM的服务到内部的Mesos集群上。配置之后,它可以处理多样化的机架,如果一台服务器宕机,它可以重新调度。
Mesos带来的限制有其正面效益:强制满足多种良好的分布式系统实践。比如:
在将新服务部署到生产环境之前,应该要检查它在负载下的表现。这就是lago(之前的Parrot)的作用——负载测试框架,易于使用。
Apache项目,完成各种分布式系统之间的协调。Twitter用它来发现服务。Finagle服务会在ZooKeeper中使用ServeSet库注册,可以参考finagle-serversets。客户端只要说它们希望与“A数据中心中供B服务使用的生产集群”通信,ServerSet实现就会确保提供最新的主机列表。当新的计算资源加入后,客户端会自动收到通知,并开始在所有服务器之间进行负载均衡。
Scalding是一个Scala库,方便开发Hadoop中的作业。开发人员不需要编写底层的map和reduce功能,用Scalding可以像写自然的Scala语言一样开发。
虽然已经有很多可以编写Hadoop作业的工具,但如果项目使用Scala开发,用Scalding编写Hadoop作业还是很方便的。其语法接近Scala集合开发库中使用的语法,而且使用Scalding,开发人员以同样代码可以处理上T的数据。
使用JVM处理对时间敏感的请求,垃圾回收机制带来的暂停会有很不好的影响。如果运气不好,GC暂定可能会在错误的时间出现,导致某些请求性能骤降,甚至超时。最坏的情况甚至会导致宕机。
要应对GC问题,首先要做的是调整JVM启动参数,以配合要执行的服务。John建议读者阅读Attila Szegedi的这些幻灯片。此前Attila也曾在2011年的QCon杭州上做过相关分享。
使用jvmgcprof,可以减少服务产生的垃圾,将GC带来的问题最小化。在启动服务时使用jvmgcprof,就能达到这个目的。用Ostrich跟踪服务的指标,并以之告诉jvmgcprof哪个指标表明工作结束。John在文中列出了一个具体的例子,并建议大家阅读jvmgcprof的Readme文件。
在总结中,John指出:
虽然无甚奇特之处,但我们还是发现:有一个公关栈令我们受益匪浅。一个团队完成的改进和bug修复能够让其他人受益。当然也有不好的一面,有些时候,引入的bug会导致其他服务出问题。不过,举个例子,当开发Zipkin时,知道其他人都在使用Finagle,这非常有帮助。因为当我们开发完成后,他们马上就能得到免费的跟踪功能。