如何深入理解 StatsD 与 Graphite

Python014

如何深入理解 StatsD 与 Graphite,第1张

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 = 1i <counti++) {

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&target=stats.timers.http_request.elapsed_time.sum

三个PHP调用系统命令函数的区别与联系

我们在执行linux系统的shell命令时,会用到PHP调用系统命令函数来实现。那么在这些函数中,主要包括了system(),exec(),passthru()这三个经常用于外部命令调用的函数。

虽然这三个命令都能执行linux系统的shell命令,但是其实他们是有区别的:

system() 输出并返回最后一行shell结果

exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。

passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。

相同点:都可以获得命令执行的状态码

在PHP中调用外部命令,可以用如下三种方法来实现:

用PHP提供的专门函数

PHP提供共了3个专门的执行外部命令的PHP调用系统命令函数:system(),exec(),passthru()。

system()

原型:string system (string command [, int return_var])

system()函数很其它语言中的差不多,这个PHP调用系统命令函数执行给定的命令,输出和返回结果。第二个参数是可选的,用来得到命令执行后的状态码。

例子:

system("/usr/local/bin/webalizer/webalizer")

exec()

原型:string exec(string command [, string array [, int return_var]])

exec()函数与system()这个PHP调用系统命令函数类似,也执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array 可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二个参数时,才可以用第三个参数,用来取得命令执行的状态码。

例子:

exec("/bin/ls -l")

exec("/bin/ls -l", $res)

exec("/bin/ls -l", $res, $rc)

passthru()

原型:void passthru (string command [, int return_var])

passthru ()只调用命令,这个PHP调用系统命令函数不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus (Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。

例子:

header("Content-type: image/gif")

passthru("./ppmtogif hunte.ppm")

六种用ruby调用执行shell命令的方法

碰到需要调用操作系统shell命令的时候,Ruby为我们提供了六种完成任务的方法:

1.Exec方法:

Kernel#exec方法通过调用指定的命令取代当前进程:

例子:

$ irb

>>exec 'echo "hello $HOSTNAME"'

hello nate.local

$

值得注意的是,exec方法用echo命令来取代了irb进程从而退出了irb。主要的缺点是,你无法从你的ruby脚本里知道这个命令是成功还是失败。

2.System方法。

Kernel#system方法操作命令同上, 但是它是运行一个子shell来避免覆盖当前进程。如果命令执行成功则返回true,否则返回false。

$ irb

>>system 'echo "hello $HOSTNAME"'

hello nate.local

=>true

>>system 'false'

=>false

>>puts $?

256

=>nil

>>

3.反引号(Backticks,Esc键下面那个键)

$ irb

>>today = `date`

=>"Mon Mar 12 18:15:35 PDT 2007n"

>>$?

=>#<Process::Status: pid=25827,exited(0)>

>>$?.to_i

=>0

这种方法是最普遍的用法了。它也是运行在一个子shell中。

4.IO#popen

$ irb

>>IO.popen("date") { |f| puts f.gets }

Mon Mar 12 18:58:56 PDT 2007

=>nil

5.open3#popen3

$ irb

>>stdin, stdout, stderr = Open3.popen3('dc')

=>[#<IO:0x6e5474>, #<IO:0x6e5438>, #<IO:0x6e53d4>]

>>stdin.puts(5)

=>nil

>>stdin.puts(10)

=>nil

>>stdin.puts("+")

=>nil

>>stdin.puts("p")

=>nil

>>stdout.gets

=>"15n"

6.Open4#popen4

$ irb

>>require "open4"

=>true

>>pid, stdin, stdout, stderr = Open4::popen4 "false"

=>[26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

>>$?

=>nil

>>pid

=>26327

>>ignored, status = Process::waitpid2 pid

=>[26327, #<Process::Status: pid=26327,exited(1)>]

>>status.to_i

=>256

Ruby可以进行Web开发

Ruby

基本描述:Ruby是一种动态的面向对象的开源语言。Rails上的Ruby则是一种使用Ruby编写的开源网络程序框架,该框架与MVC(模型-查看-控制)结构十分类似。

学习理由:由于简便性,有效性以及让电脑完成任务的能力,近年来,该语言的使用量已经迅速增长。另外的好处是非常容易学习。

Python

基本描述:一种动态面对对象的翻译开源语言。使用动态存储管理。

学习理由:Python是一种高度可读的抽象语言,许多开发人员认为其非常有趣,它的语法简单,因此被Google与学术界大量运用。

JavaScript

基本描述:请不要与Java混淆,JavaScript是一种面对对象的脚本变成语言,它运行在客户端的Web浏览器上。它比Java更简洁,拥有简化的命令,易用的代码,并且无需进行编译。

使用理由:它可置入HTML,被用于大量的网页来验证表格,建立cookie,侦测浏览器以及提高设计。由于其易于学习与使用广泛,已被视为重要的学习背景。