花钱的年华

江南白衣,公众号:春天的旁边

Graphite的百万Metrics实践之路

| Filed under 技术

Graphite作为Metrics界的大哥

  • 它是RRDTool的Network Service版,和RRD一样支持Metrics的精度递减,比如一天之内10秒一条,7天之内聚合到1分钟一条,一年之内聚合到1小时一条。
  • 它支持丰富的查询函数,从简单的min/max/avg/sum 到 rate、top N等等,以Restful API提供。
  • 它有整个生态圈的插件支持,而且简单的TCP Plain Text协议,自己来插入数据也很简单。
  • 它有漂亮的DashBoard -- Grafana
  • 它有完整的HA和Scalable 方案 -- Carbon-Relay
  • 它有数据预聚合方案 -- Carbon-Aggregator

上面的一切,都只靠配置文件就能搞定, 不需要编一行代码。

但是,等你在服务器、应用的监控之外,真的将海量的业务Metrics也交给它,想不写一行代码就赚回一个看起来不错的报表系统时,问题来了。

 

百万Metrics......

每个Metrics在硬盘里都是一个文件。metrics的个数,最大估算值是所有维度大小的乘积,维度的增加让文件的个呈量级的增加。

写入的时候,依赖于Graphite自己很自豪的架构:Cluster Sharding分摊每台服务器上的Metric个数以及Carbon-Cache里的延时聚合批量写入同一个文件的机制持,对百万Metrics的支持并不困难。(唯一问题就是如果Carbon-Cache崩了,内存中未写入的数据会丢失)

但查询的时候......如果Dashboard里看的并不是某一个Metrics,而是某些维度下的一个合计,每次查询可能动不动都要打开上万个文件,就是件痛苦的事情。

解决方法之一是使用Carbon-Aggregator,像我们在报表系统里常做的那样,先做一些维度下的聚合。

比如,计数器只在每台Web服务器的内存里累加,metrics nam就是 traffic.server1.userlogin.count,因为我们只关心总用户登录数,就将20台server的数据聚合在一起,形成traffic.all.userlogin.count再发给carbon-cache,而原来属于每台server的记录则丢弃掉不再往后传,这样我们就少了20倍的metrics数量。

又比如 我们有时候会专门看某个app的使用情况,有时候又想看全部app的情况,则可以既新增一条allApp的聚合数据,也保留每一条app的数据。

好,铺垫完了。

 

读取百万Metrics时的真正问题

真正的问题1:carbon-aggregator是python写的基于Twisted的应用,见鬼的GIL问题,居然只能用到单核CPU。有时候单CPU核根本不够用。在Java里完全没想过会遇到的问题,别人说多核编程的时候总是很茫然,本来就多核的呀。。。。

真正的问题2:有些无法预先聚合的场景,比如要从2000个app中找出使用量最大的5个,则必须打开2000个app对应的全部文件。

真正的读取问题3: Sharding之后,查询时需要聚合多台服务器的结果,比如数据分布在三台机器上,第一台机命中了100个metrics,平均值是3;第二台命中了20个,平均值是2;第三台命中了10个,平均值是1。
首先决不能把三个平均值做平均。像MysqlCluster那种会做执行计划分析的,会只要求每台机上传sum 和 count 一共6条数据。但Graphite做不到这一点,只能让每台机把命中的metrics即130条数据都传上来。在0.9.12版中,如何批量上传数据还是个问题,在始终不肯发布的0.9.13 Snapshot版里此问题总算解决了。
但计算聚合时,再次遇到Python的单CPU核问题......

Graphite作者在宣布支持百万Metrics的时候,好像很少考虑这些问题。
 

要抛弃Graphite么?

现在,坚定一下继续留在Graphite的决心。能掀桌子把Graphite干掉吗? 一个选型的借鉴是Grafana作者合伙创业开的公司Raintank,它提供一个关于Metrics的SAAS平台,看the promising KairosDBinfluxdb: first impressions这两篇博客,也没太好的能下定决心的选择。

1. OpenTSDB
基于HBase,不支持RRD风格的数据精度递减,函数有限比如根本就没有Top N这种功能,运维复杂。

2. Kairosdb
基于Cassandra,8个月没更新了,似乎很不活跃。因为是从OpenTSDB fork出来的,所以不支持RRD与函数有限的问题依然在。

3. InfluxDB
用Go语言编写,是我最看好的一个alternative。但看好它快一年半了,还是没有成熟。最过份是,0.8版与还没发布的0.9版之间,居然几乎是重写。关于Cluster的文档也没写好让人不清楚底细。而且依然不支持RRD,只支持数据超过某个时间段就直接删除。

所以,近期还是留在Graphite吧,继续讨论优化吧。

 

可选的优化方法列表

两份有用的参考资料:

还有一个增强信心的案例,Booking.com(携程网的山寨对象),经过改造的Graphite,支持90台Server,50TB数据的方案。

1. 使用SSD

因为要写入和读取大量不同的文件,用SSD无疑会快很多。
但SSD的价格贵,而且Booking.com的视频里也说。在频繁读写下,一块SSD盘的寿命也就一年半。

2. 使用pypy代替python
看官方数据speed.pypy.org,twisted_tcp中比CPython快3倍,一快解千愁,可缓解很多问题了。

Python把py文件编译成字节码(pyc文件),再交给PVM执行,就像Java的JVM一样。但PVM没有JIT,每次都要把字节码翻译成机器码再执行,而JVM可以把解释后机器码保存下来。pypy提供了JIT,让Ruby社区一片羡慕。

3. 接受现实,减少数据精度

比如最初的一天之内就不要10秒一条记录了,降到30秒或一分钟,大大减少relay,aggregate, 写入的压力和文件的大小。

4. 使用Carbon-Agggrator方案做预聚合

Aggregator能减少不必要的存储,预聚合某些维度,见前。

5. 处理Carbon-Aggrator/Carbon-Relay的单CPU瓶颈

方法1: 前面提到的pypy应该有帮助。

方法2: 如果瓶颈是在carbon-replay,Booking.com用C重写的carbon-c-relay (但暂时用链表存储所有要聚合的Metrics,Metrics数量多时也会搞爆CPU的问题,作者在改),或者这个用Go重写的carbon-relay-ng,都是很活跃很值得尝试的项目。

方法3: 如果不想有任何改变,那多开几个carbon-aggregator,每个aggreator只负责少量的规则,甚至sharding出几个aggreator来完成同一条规则(如果该规则允许分区的话),aggregator会为每个要在聚合后吐出来的Metrics建立一个任务,用LoopingCall不断调用,所以要吐出的Metrics数量进行拆分。部署结构越来越复杂了。。。

6. Sharding

Sharding能减少每个节点上要读写文件的数量,Graphite作者预订的方案。

7. 处理Graphite-Web聚合Sharding时的单CPU瓶颈

暂时无法解决。只能期待前面pypy的提升。

Booking.com提供全套用go重写的方案,carbonserver, carbonzipper,carbonapi一起连用,它们都是重新实现了Graphite各部件的功能,但改动好像太大了,暂不推荐。

8. 最后一招,后台预查询预cache数据
可能有些慢查询要30秒才出结果,那就预先在后台查询一次,cache在Graphite-Web说连接的Memcached里。不过这时就只支持一些固定的时间段,不能支持last 15 minutes这种相对时间了。

其他优化

1. 将Whisper存储换成其他backend方案

用Cassandra的graphite-cyanite,Vimeo的同学写的用Influxdb的graphite-influxdb, 但看起来都不成熟也没多少用户,不敢试。

2. 将Graphite-Web换成Graphite-Api

Graphite-Api与Graphite-Web相比,少了用户管理,数据库与PNG渲染,只保留最核心的Query功能,而且有自己的一套plugin架构,比如raintank就写了个Kairosdb的backend

小结

InfluxDB真正超车之前,我们仍将尝试各种优化,继续实现我们不写一行代码,光靠配置获得一个不错的报表系统的愿望。

 
文章持续修订,转载请保留原链接:http://calvin1978.blogcn.com/articles/graphite.html

发表评论

您的电子邮箱不会被公开。

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>