为了全面了解 StatsD 的工作原理,我阅读了它的源码。之前我就耳闻 StatsD 是一种简单的应用,但读过源码后才发现它竟如此简单!在 主脚本 文件只有300多行代码,而 Graphite 的后端代码 只有150行左右。
在 这个文档 中,列出了一些需要理解的 StatsD 概念。
当一个 Whisper 文件被创建,它会有一个不会改变的固定大小。在这个文件中可能有多个 "buckets" 对应于不同分别率的数据点,每个 bucket 也有一个保留属性指明数据点应该在 bucket 中应该被保留的时间长度,Whisper 执行一些简单的数学计算来计算出多少数据点会被实际保存在每个 bucket 中。
每个 stat 都有一个 value,该值的解释方式依赖于 modifier。通常,values 应该是整数。
在 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:
StatsD 将会计数下面的 values:
一个 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:
会传到后端的值只有583而已。该 gauge 的值会一直存储在内存中,直到 flush interval 结束才传值。
现在,我们已经了解数据是怎样从 StatsD 传出来的,让我们看看它在 Graphite 里是如何存储并处理的。
在 Graphite 文档里,我们可以找到 Graphite 概览 ,此概览总结了 Graphite 的两个要点:
Graphite 由三部分组成:
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年。
了解了这么多,我们来看看一个简单的 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/?width=586&height=308&from=-2minutes⌖=stats.timers.http_request.elapsed_time.sum
该图片简单地描绘了 http 请求在一段时间内的 elapsed_time 值。
下面 JSON-data 的 Render URL
/render/?width=586&height=308&from=-2minutes⌖=stats.timers.http_request.elapsed_time.sum&format=json
在下面的结果中,我们可以查看来自 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 ] ] } ]
下面的简单脚本能将 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/?width=586&height=308&from=-20minutes⌖=stats.gauges.user_registrations
另一个简单的图片,展示总的注册数。
使用 Graphite 的衍生函数,可以获得每分钟的用户注册数量。
下面图片的 Render URL
/render/?width=586&height=308&from=-20minutes⌖=derivative(stats.gauges.user_registrations)
该图片所用的数据跟之前的图片一致,但是使用了衍生函数从而显示每分钟的注册率。
深入了解 StatsD 与 Graphite 的工作原理,能让我们更加明白 StatsD 所传送的数据种类,如何传送,以及怎样更有效地根据 Graphite 读取数据。
原文地址: https://blog.pkhamre.com/understanding-statsd-and-graphite/