Graphite

Calvin Xiao edited this page Apr 9, 2014 · 87 revisions

1. 介绍

Graphite是一个Github项目,没有自己的独立域名网站,文档也host在专门存放文档的网站上。

Graphite 专注于两个最简单的任务: 作为一个便捷且Scalable的Network Service,以精度随时间递减的方式存储Metrics数据,并支持以丰富的函数获取它们,以图片或者JSON的格式。

Graphite基于Python,由三部分组成:

  • Carbon:基于Twist的TCP Server,负责接收数据,用简单到死的文本协议,因为够简单,所以Yammer Codahale Metrics、Netflix Servo、StatsD、Collectd、Logstash,人人都有一个到Graphite的插件。
  • Whisper: 类库,将数据存储成RRD(round-robin-databas)文件,数据粒度随时间递减,可以存储更长时间的其据。其继承者是Ceres,不再固定文件大小。
  • Graphite-Web:基于Django的Web应用,支持Restful的URL获取图片或JSON数据,URL里可以带各种有用的函数

基于Python的单点性能可能不高,但Graphite有Cache、HA、Scalable方面的专门设计。

2. Install Graphite 0.9.12 on Windows

3. Functions & URL

3.1 URL示例

Image: http://localhost:8080/render?from=-10minutes&target=system.loadavg_1min&width=500&height=300
Json: http://localhost:8080/render?from=-10minutes&target=system.loadavg_1min&format=json
CSV: http://localhost:8080/render?from=-10minutes&target=system.loadavg_1min&format=csv

3.2 重要函数

Graphite的魅力在于它有非常多让人眼花缭乱的函数,每当有什么需求时,都可以先尝试翻看一下它的函数列表。这里稍微列举一些。

1. url上的通配符:
url中可以带有星星或选择符,会选择出多个counter来,也就诞生了一系列其他需求:

&target=rest.getUser.count.*          
&target=rest.get*.count.succeeded     
&target=rest.getUser.count.{succeeded,failed}
&target=rest.server[1-20].getUser.count    

注意,因为url中的每个.代表了实际存储文件的一个目录层次,graphite的代码也是用unix的fnmatch一层层目录的推进匹配,所以不能用rest.* 选出 rest.getUser.count

  • 将星星代表的多个counter通过某个方式选出一个值来,比如 maxSeries,sumSeries,averageSeries。sumSeries(rest.getUser.count.*),将星代表的counter相加成一个值。
  • groupByNode,url中有两颗星星,将第1颗星星代表的多个Series组合,而第2颗代表的则分开来显示。
    groupByNode(ganglia.by-function...cpu.load5,2,"sumSeries"),第一颗星代表服务器,第2颗星代表服务器,此函数将显示每台Server上所有函数的CPU总和。averageSeriesWithWildcards,sumSeriesWithWildcards也有类似效果。
  • aliasByNode,将星星中的counter的名称做Alias,比如aliasByNode(rest.getUser.count.*, 1),alias就是succeeded或failed。更高级是aliasSub,可以用正则表达式取出counter名称里的某部分做alias。
  • exclude,单独剔除某个Counter,比如exclude(servers*.threads,"server02")
  • 只显示某些counter,比如highestAverage,highestMax,只显示值最高(还可以是最低)的几个counter,又或者currentAbove,currentBelow,以last值(还可以是max,min,avg值)做条件过滤。
  • asPercent,以几个counter相对的百分比来显示。

2. 图形增强:

  • legendValue(rest.getuser.latency, 'avg', 'max'),图形下面显示数据的最大小值、平均值(可以是last, avg, total, min, max)。
  • threshold(60, "latency 60ms threshold","red"),在图形里显示警戒线。
  • consolidateBy(rest.getuser.latency, 'max'),如果看一周的曲线,图上的每个点包含了多个datapoint的数据,不用本函数,默认行为是算平均值,可以用本函数设为取max,min,sum等。

3. 其他:

  • summarize(rest.getuser.latency, "1min"),按1分钟进行聚合而不是原来的10秒。聚合的方式默认是sum,也可以是avg,max,last。
  • 将自增长的Counter转化为TPS: perSecond(rest.getuser.totalCount),但当前版本此函数好像不存在,一个解决方式是:scaleToSeconds(deriviative(rest.getuser.totalCount),1) ,第一个函数先算出两个点之间的差值,第二个平均到每秒。
  • integral(rest.getUser.count),显示所有datapoint的逐渐累积值。
  • hitcount, 将rate换算成totalCount,能跨越各种时间长度。
  • Timeshift, 可以比较当前数值和一周前的数值(两条线): &target=alias(summarize(rest.getuser.count, "1min"), "today")&target=alias(summarize(rest.getuser.count, "1min"),"1w"), "last week")。
  • movingAverage, 几个datapoint的移动平均值。

3.3 URL参数

  1. 时间
    关于时间各种定义见URL文档。可以定义绝对时间,比如from=04:00_19890601。可以定义一些奇葩的写法,比如&from=6pm+today等于今天六点以后,&from=january+1等于1月1号以后,&from=monday等于离现在最近的周一以后。 from默认是24小时以前,until默认是now。

  2. noCache=true
    结果默认会按local_settings.py中的DEFAULT_CACHE_DURATION 缓存60秒,如果要每10秒刷新看一次最新值,应设置noCache=true。 另外,如果有配置memcached,缓存在memcached中,否则在Graphite-WebApp里。

  3. drawNullAsZero=true
    没有流量,没有数据点插入时,在Graphite里数据是Null,图中的曲线会有断点,设置url参数drawNullAsZero=true会把断点变成0。另外函数中也有keepLastValue(),让null等于最后一次的值,或者transformNull(webapp.pages.*.views,-1),可以让默认值为-1。

4. Carbon & Whisper

####4.1 端口与协议

  • Carbon默认在2003端口启动TCP的,接收plain text数据,可配成UDP。
  • Carbon默认在2004端口,接收以Python的Pickle格式的数据。
  • Carbon默认在7002端口,支持Cache数据的查询。

####4.2 数据写入流程 Metric数据来临时,将写入不同的whisper文件,carbon.conf中有MAX_UPDATES_PER_SECOND=500的限制,如果海量数据来临时,数据就不会立刻写到文件里,而是被Buffer起来。然后系统会按照谁有最多的没写入的data point排序,利用Whisper比RRDTool可以一次写入多个data point的优势,一次写入多个data point到一个数据文件。

当Graphite-Web画图时,不但会去读whisper文件,还会通过7002端口,查询Carbon中没来得及写入文件的Buffer。

如果数据来临的间隔低于Graphite刷新的间隔,Graphite只会取最后一个值。比如retentions = 10s:1d,而客户端每5秒发来一条数据,前一条数据会被丢弃。

####4.2 数据压缩流程 数据的分布定义storage-schemas.conf中,默认是按10秒一个数据的方式,存一天的数据。一天前的数据就没了。

[default_1min_for_1day]
pattern = .*
retentions = 10s:1d

Statsd建议改成从现在到6个小时前每10秒一个数据,从7个小时前到7天前每1分钟一个数据,7天前到5年前每10分钟一个数据,5年前的数据丢弃。

[stats]
pattern = ^stats.*
retentions = 10s:6h,1min:7d,10min:5y

将10秒的数据降为1分钟数据时,默认是算平均值,但也可以按合计值,最大值,最小值等,一切尽在storage-aggregation.conf。

[sum]
pattern = \.count$
xFilesFactor = 0
aggregationMethod = sum

[default_average]
pattern = .*
xFilesFactor = 0.5
aggregationMethod = average

####4.3 Whisper文件操作 因为文件不是顺序写,所以硬盘使用SSD的性能更好。 C:\Python27\Scripts\Whisper-* 脚本,可以对Whisper文件 查看、更新、Info、 在retention更改后重新更改大小。

5. DashBoard

5.1 overview

官方的DashBoard不算太好,因此有下列的替代品:

  1. [Grafana] (http://grafana.org/) 和ElasticSearch的Kibana同一个风格,是目前最好的DashBoard。
  2. 其他的Dashboard都不太好,比如Descartes是Graphite的维护者Jason Dixon 做的一个替代官方DashBoard,Ruby编写。三方的Dashboard小结
  3. 自己定制一个动态页面,用URL嵌入Graphite吐出来的图片。
  4. 获取Graphite的Json,在浏览器用D3.js引擎重新渲染,Giraffe是一个典型的实现,Demo,当然,也可以完全自己用D3.js定制。

Grafana既有用户体验良好的Dashboard,也支持用户动态创建编辑保存DashBoard,是最省心省力的选择。

5.2 Grafana

5.2.1 安装

Grafana是一个纯粹的html/js 应用,丢进任何Web容器里即可。不过Grafana需要ajax 查询Graphite-Web内容,会有跨域访问的限制,需要修改Graphite Web的容器支持跨域访问,不过我的做法是直接把Grafana丢进Graphite webapp目录,修改一下webapp\graphite\urls.py文件,比如添加一句 ··· ('^grafana/(?P.*)$', 'django.views.static.serve', {'document_root' : settings.GRAPHITE_ROOT+'/webapp/grafana'}), ···

Grafana目前的版本更新非常迅速,可经常留意。

再然后copy config.sample.js 成 config.js,就可以访问http://graphite/grafana/index.html

5.2.2 Tips

  • 它要求ElasticsSearch来存储多个DashBoard的定义文件,如果实在没有装ElasticSearch,也可以JSON文件的干活,Export出JSON文件(Save ->Advanced->Export Schema),然后上传回它的/app/dashboards目录。
  • 一般人不喜欢黑色,可选择白色风格的DashBoard,在右上角的DashBoard Configuration里选择style为light。
  • 和Kibana一样,在时间轴上拖拉一段时间来细化时间的选择是最炫的演示效果。
  • 如果需要动态的DashBoard,也可以用javascript来实现。Scripted dashboards
  • Templating也是它的一大功能,比如可以快速切换多台服务器,Templating
  • 一些界面相关的function,图形设置里已直接支持,不需要再写成function,比如Legends,Threshold,draw null as zero。
  • Y轴智能单位支持ms和byte的智能转换,比如转换为Kb,Mb,Gb。

6. HA 与 Scalable

Clustering Graphite Blog

Carbon-Relay 支持垂直或者水平的分区,将Metric发送到不同的Carbon-Cache实体上(同名的Metric总是发送到同一个Cache实体)。在carbon.conf的[relay]中配置RELAY_METHOD = consistent-hashing是水平分区,配置RELAY_METHOD = rules,然后配置relay-rules.conf中按Metric Name的正则表达式是垂直分区。
relay-rules.conf示例:

[example]
pattern = ^mydata\.foo\..+
servers = 10.1.2.3, 10.1.2.4:2004  //会复制到所有server上(HA)

Carbon-Relay 如果采用consistent-hashing,还可以在carbon.conf里定义factory=2, 复制数据到两台服务器上的Carbon-Cache,实现HA。

为了使用更多的CPU,一台机器上可以起一个Graphite-Web实例,多个Carbon-Cache实例,而使用同一个Whisper文件目录,因为sharding的原因,两个实例是不会写同一个whiper文件的,此时,Graphite-Web的local_settings.py里的CARBONLINK_HOSTS里要配置多个carbon。

Graphite-Web 先读本地的whisper文件,如果读到了Metric就走正常流程,如果本地没有这个Metric,就通过 local_settings.py里的CLUSTER_SERVERS查询其他Graphite-Web Server。

HA环境下,如果一台机器倒了,再重启之后如何从另一台机器上同步最新数据?Cluster环境下,如果增加后端的Carbon-Cache实例,一致性哈希到每台机上的metric会有变化,如何重新Balance已存在的数据? 幸亏有一个carbonate项目把这个脏活干了,比如carbon-sync, 看这个命令的名字就感觉亲切。

LOCAL_IP="$1"

for h in $(carbon-hosts) ; do  //访问所有host
  (
    ssh $h -- carbon-list |       //列出该host上所有metric
    carbon-sieve -n $LOCAL_IP |   //再列出本机有的metric
    carbon-sync -s $h             //同步重合的metric
  ) &
done

Carbon-Aggregrator,如果数据从多台服务器过来,但你其实只想看个总数,并不关心每台服务器上的计数,又或者数据过来的时间低于Graphite的datapoint的间隔,你又不想丢弃之前的数据,就可以放一个Carbon-Aggregrator在Carbon-Cache前面,类似StatsD的做一次聚合,把聚合后的数据交给Carbon-Cache,大幅降低其IO。不过Carbon-Aggregrator 只支持Sum和Avg两种计算方式。配置文件在carbon.conf的[aggregator]段落和aggregation-rules.conf

<env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests

上例,将多台server过来的数据,做一次60秒的聚合,名字叫<env>.applications.<app>.all.requests。其中<xxx>会用来自动匹配具体的名字,而*就会用来做聚合。

以上内容均为纸上谈兵,待验证。

7. Graphite vs RRDTool

Whisper vs RRDTool

  • Graphite的旧版FAQ这里已经专门提到了两者的比较。
  • Graphite自承RRDTool的单纯写入速度比它的Whisper快2-3倍,读快2-5倍,因为C与Python的区别。
  • 但Whisper因为有时间戳,可以插入不规则发生的数据和已过去的数据。Whisper还可以同时写入批量数据,见上。

Graphite vs RRDTool

  • 对我们项目来说,Graphite提供TCP写入和Http读取的网络服务,当然比RRDTool基于文件操作灵活,RRDTool的rrdcached和rrdcgi还没去做测试。
  • 对我们项目来说,更重要的是支持正则的灵活配置方式,可以更好的配置Dynamic Counter,或者多个节点上的数据输入,在取数据时可以url里带星号来进行汇总,见上。
  • Graphite支持函数,在用途上更有潜力。

8. 用户

  • 大客户:Github、Instagram、豆瓣、Etsy等
  • 云服务:playfairapp statsd + Graphite

9. 参考资料