转载

如何深入理解 StatsD 与 Graphite ?

众所周知,StatsD 负责收集并聚合测量值。之后,它会将数据传给 Graphite,后者以时间序列为依据存储数据,并绘制图表。但是,我们不知道,基于 http 访问的图表在展示时,是基于每秒钟的请求数,每次留存的平均请求数还是其它。让我们就以此为目标,来一探究竟吧!本文系OneAPM 工程师编译整理。

如何深入理解 StatsD 与 Graphite ?

StatsD

为了全面了解 StatsD 的工作原理,我阅读了它的源码。之前我就耳闻 StatsD 是一种简单的应用,但读过源码后才发现它竟如此简单!在 主脚本 文件只有300多行代码,而 Graphite 的后端代码 只有150行左右。

StatsD 中的概念

在 这个文档 中,列出了一些需要理解的 StatsD 概念。

Buckets

当一个 Whisper 文件被创建,它会有一个不会改变的固定大小。在这个文件中可能有多个 "buckets" 对应于不同分别率的数据点,每个 bucket 也有一个保留属性指明数据点应该在 bucket 中应该被保留的时间长度,Whisper 执行一些简单的数学计算来计算出多少数据点会被实际保存在每个 bucket 中。

Values

每个 stat 都有一个 value,该值的解释方式依赖于 modifier。通常,values 应该是整数。

Flush Interval

在 flush interval (冲洗间隔,通常为10秒)超时之后,stats 会聚集起来,传送到上游的后端服务。

测量值类别

计数器

计数器很简单。它会给 bucket 加 value,并存储在内存中,直到 flush interval 超时。

让我们看一下生成计数器 stats 的源码,该 stats 会被推送到后端。

for (key in counters) {   var value = counters[key];   var valuePerSecond = value / (flushInterval / 1000); // calculate "per second" rate    statString += 'stats.'+ key + ' ' + valuePerSecond + ' ' + ts + "/n";   statString += 'stats_counts.' + key + ' ' + value  + ' ' + ts + "/n";    numStats += 1; } 

首先,StatsD 会迭代它收到的所有计数器,对每个计数器它都会分配两个变量。一个变量用于存储计数器的 value,另一个存储 per-second value。之后,它会将 values 加至 statString,同时增加 numStats 变量的值。

如果你使用默认的 flush interval(10秒),并在每个间隔通过某个计数器给 StatsD 传送7个增量。则计时器的 value 为 7,而 per-second value 为 0.7。

计时器

计时器用于收集数字。他们不必要包含时间值。你可以收集某个存储器中的字节数、对象数或任意数字。计时器的一大好处在于,你可以得到平均值、总值、计数值和上下限值。给 StatsD 设置一个计时器,就能在数据传送给 Graphite 之前自动计算这些量。

计时器的源码比计数器的源码要稍微复杂一些。

for (key in timers) {   if (timers[key].length > 0) { var values = timers[key].sort(function (a,b) { return a-b; }); var count = values.length; var min = values[0]; var max = values[count - 1];  var cumulativeValues = [min]; for (var i = 1; i < count; i++) {     cumulativeValues.push(values[i] + cumulativeValues[i-1]); }  var sum = min; var mean = min; var maxAtThreshold = max;  var message = "";  var key2;  for (key2 in pctThreshold) {   var pct = pctThreshold[key2];   if (count > 1) {     var thresholdIndex = Math.round(((100 - pct) / 100) * count);     var numInThreshold = count - thresholdIndex;      maxAtThreshold = values[numInThreshold - 1];     sum = cumulativeValues[numInThreshold - 1];     mean = sum / numInThreshold;   }    var clean_pct = '' + pct;   clean_pct.replace('.', '_');   message += 'stats.timers.' + key + '.mean_'  + clean_pct + ' ' + mean           + ' ' + ts + "/n";   message += 'stats.timers.' + key + '.upper_' + clean_pct + ' ' + maxAtThreshold + ' ' + ts + "/n";   message += 'stats.timers.' + key + '.sum_' + clean_pct + ' ' + sum + ' ' + ts + "/n"; }  sum = cumulativeValues[count-1]; mean = sum / count;  message += 'stats.timers.' + key + '.upper ' + max   + ' ' + ts + "/n"; message += 'stats.timers.' + key + '.lower ' + min   + ' ' + ts + "/n"; message += 'stats.timers.' + key + '.count ' + count + ' ' + ts + "/n"; message += 'stats.timers.' + key + '.sum ' + sum  + ' ' + ts + "/n"; message += 'stats.timers.' + key + '.mean ' + mean + ' ' + ts + "/n"; statString += message;  numStats += 1; }  } 

如果在默认的 flush interval 内,你将下列计数器 values 传给 StatsD:

  • 450
  • 120
  • 553
  • 994
  • 334
  • 844
  • 675
  • 496

StatsD 将会计数下面的 values:

  • mean_90 496
  • upper_90 844
  • sum_90 3472
  • upper 994
  • lower 120
  • count 8
  • sum 4466
  • mean 558.25

Gauges

一个 guage 代表着时间段内某点的任意 vaule,是 StatsD 中最简单的类型。你可以给它传任意值,它会传给后端。 Gauge stats 的源码只有短短四行。

for (key in gauges) {     statString += 'stats.gauges.' + key + ' ' + gauges[key] + ' ' + ts + "/n";   numStats += 1; } 

给 StatsD 传一个数字,它会不经处理地将该数字传到后端。值得注意的是,在一个 flush interval 内,只有 gauge 最后的值会传送到后端。因此,如果你在一个 flush interval 内,将下面的 gauge 值传给 StatsD:

  • 643
  • 754
  • 583

会传到后端的值只有583而已。该 gauge 的值会一直存储在内存中,直到 flush interval 结束才传值。

Graphite

现在,我们已经了解数据是怎样从 StatsD 传出来的,让我们看看它在 Graphite 里是如何存储并处理的。

总览

在 Graphite 文档里,我们可以找到 Graphite 概览 ,此概览总结了 Graphite 的两个要点:

  • Graphite 存储数值型带有时间序列的数据。
  • Graphite 按需绘制图表。

Graphite 由三部分组成:

  • carbon :监听时间序列的数据的后台程序。
  • whisper :一个简单的数据库库,用来存储时间序列数据。
  • webapp : Django webapp,使用 Cairo 来根据需要呈现图形。

Graphite 当做时间序列数据的格式如下:

<key> <numeric value> <timestamp>   

存储方案

Graphite 采用可配置的存储方案用以定义所存数据的留存率。它会给数据路径匹配特定的模式,从而决定所存数据的频率和来历。

以下配置示例截取自 StatsD 文档。

[stats] pattern = ^stats/..* retentions = 10:2160,60:10080,600:262974 

该示例表明,匹配上述样式的数据都会套用这些留存。留存的格式为 frequency: history。所以,该配置允许我们将10秒钟的数据存储6个小时,1分钟的数据存储1周,10分钟的数据存储5年。

在 Graphite 显示计时器

了解了这么多,我们来看看一个简单的 ruby 脚本,该脚本能收集 HTTP 请求的时间。

#!/usr/bin/env ruby  require 'rubygems' if RUBY_VERSION < '1.9.0' require './statsdclient.rb' require 'typhoeus'  Statsd.host = 'localhost' Statsd.port = 8125  def to_ms time   (1000 * time).to_i end  while true   start_time = Time.now.to_f    resp = Typhoeus::Request.get 'http://www.example.org/system/information'    end_time = Time.now.to_f    elapsed_time = (1000 * end_time) - (to_ms start_time)   response_time = to_ms resp.time   start_transfer_time = to_ms resp.start_transfer_time   app_connect_time = to_ms resp.app_connect_time   pretransfer_time = to_ms resp.pretransfer_time   connect_time = to_ms resp.connect_time   name_lookup_time = to_ms resp.name_lookup_time    Statsd.timing('http_request.elapsed_time', elapsed_time)   Statsd.timing('http_request.response_time', response_time)   Statsd.timing('http_request.start_transfer_time', start_transfer_time)   Statsd.timing('http_request.app_connect_time', app_connect_time)   Statsd.timing('http_request.pretransfer_time', pretransfer_time)   Statsd.timing('http_request.connect_time', connect_time)   Statsd.timing('http_request.name_lookup_time', name_lookup_time)    sleep 10 end 

让我们看看该数据生成的 Graphite 图。该数据来自 2 分钟前,而 elapsed_time 则来自前面的脚本。

图像生成

Render URL

下面图片的 Render URL

/render/?width=586&height=308&from=-2minutes⌖=stats.timers.http_request.elapsed_time.sum 

Graphite 生成的图片

该图片简单地描绘了 http 请求在一段时间内的 elapsed_time 值。

如何深入理解 StatsD 与 Graphite ?

JSON-data

Render URL

下面 JSON-data 的 Render URL

/render/?width=586&height=308&from=-2minutes⌖=stats.timers.http_request.elapsed_time.sum&format=json 

来自 Graphite 的 JSON-output

在下面的结果中,我们可以查看来自 Graphite 的源数据。这些数据来自12个不同的数据点,也即 StatsD 10 秒 flush internal 的两分钟。Graphite 绘制数据就是如此简单。

此外,借助 JSONLint ,JSON-data 的数据显示更加美观。

[  {   "target": "stats.timers.http_request.elapsed_time.sum",   "datapoints": [    [     53.449951171875,     1343038130    ],    [     50.3916015625,     1343038140    ],    [     50.1357421875,     1343038150    ],    [     39.601806640625,     1343038160    ],    [     41.5263671875,     1343038170    ],    [     34.3974609375,     1343038180    ],    [     36.3818359375,     1343038190    ],    [     35.009033203125,     1343038200    ],    [     37.0087890625,     1343038210    ],    [     38.486572265625,     1343038220    ],    [     45.66064453125,     1343038230    ],    [     null,     1343038240    ]   ]  } ]  

在 Graphite 绘制 gauge 图像

下面的简单脚本能将 gauge 传送给 StatsD,模拟用户注册的过程。

#!/usr/bin/env ruby  require './statsdclient.rb'  Statsd.host = 'localhost'   Statsd.port = 8125  user_registrations = 1  while true     user_registrations += Random.rand 128    Statsd.gauge('user_registrations', user_registrations)    sleep 10 end   

图像显示——用户注册数量

Render URL

下面图片的 Render URL

/render/?width=586&height=308&from=-20minutes⌖=stats.gauges.user_registrations 

来自 Graphite 的图片

另一个简单的图片,展示总的注册数。

如何深入理解 StatsD 与 Graphite ?

图片显示——每分钟的用户注册数

使用 Graphite 的衍生函数,可以获得每分钟的用户注册数量。

Render URL

下面图片的 Render URL

/render/?width=586&height=308&from=-20minutes⌖=derivative(stats.gauges.user_registrations) 

来自 Graphite 的图片

该图片所用的数据跟之前的图片一致,但是使用了衍生函数从而显示每分钟的注册率。

如何深入理解 StatsD 与 Graphite ?

结论

深入了解 StatsD 与 Graphite 的工作原理,能让我们更加明白 StatsD 所传送的数据种类,如何传送,以及怎样更有效地根据 Graphite 读取数据。

原文地址: https://blog.pkhamre.com/understanding-statsd-and-graphite/

OneAPM 是应用性能管理领域的新兴领军企业,Cloud Insight 能帮助企业用户和开发者轻松实现:监控各项基础组件以及对数据进行聚合、过滤和筛选的功能,致力于打造一个更为强大的数据管理平台。想阅读更多技术文章,请访问OneAPM 官方博客。

正文到此结束
Loading...