4

度量是大多数软件的一个基本部分. 度量能够窥探, 监控一个软件系统的在运行时的行为. 并在系统异常的时候进行报警.

Elixir经常被称赞为一个跑的快的语言. 特别是在分布式, 并发应用程序方面. 但是 "快" 如果没有测量指标, 重构会变得异常困难, 也难于判断是否性能得到改善.

下面我们来俺看用什么方式和工具在Elixir应用程序中搜集度量信息.

工具栈概述

我们的统计数据搜集整体架构如下:

图片描述

下面更加深入的探索这个架构和其中的组件.

存储度量数据

现在我们从架构的核心开始: 用于存储度量数据的数据量. 我们使用 InfluxDB 一个开源的时序数据库, 它特别适合存储度量数据. 并且提供一堆非常有用的功能.

InfluxDB 数据库由一组被称为 measurements 的东西组成. 每个 measurements 是一个数据点的集合. 每个数据点由一个时间戳, 一组标记(tags), 和一组值(values)组成.

标签(Tags)用元数据标记measurements. 例如一个measurement常用的标记为host, 它保存了报告该measurement的主机, 在一个measurement中标记是被所有点共享的. 相反 值是和measurement相关的, 例如标识完成一个HTTP请求的时间被称为request_time. 一个数据点可以有多个值, 当一个点只有一个值的时候, 我们通常叫这个值只是一个value, 因为measurement名称足够判断他标识的是哪个值.

在本文的剩余部分, 我们把measurements称作measurementspoints使阅读体验更流畅.

InfluxDB 还提供了丰富的SQL语法来查询数据. 以及重采用数据已优化存储.

InfluxDB 支持一些存储数据的方法: measurements 可以通过HTTP接口或UDP即可上报, 可以编码为JSON或InfluxDB自己的协议. UDP接口天然的有更快的速度, 因为它没有想HTTP协议和TCP协议那样的开销, 但是, 它不能保证数据成功投递到目标. 选择HTTP还是UDP

我们使用 influxdb-relay, 它设置了一个类似负载均衡的架构添加一些复制到统计存储

统计报告

Telegraf

InfluxDB 的开发者为我们提供了另外一个开源的工具叫做 Telegraf. Telegraf 基本上就是一个守护进程, 它没隔N秒从各种数据源收集数据并投递到不同的输出目标. 输入和输出是通过"输入插件""输出插件"进行管理. 这里我们使用两个主要输入员, 以及一个输出目标.

输入

第一个Telegraf要收集的输入源是"系统统计", 包含CPU使用率, 内存使用率, 磁盘使用率, 网络使用率等系统级别的运行信息.Telegraf负责读取这些系统的状态值. 另外一个我们使用的输入是内置在Telegraf中的UDP监听器: 它提供了一个到InfluxDB的UDP接口的一个镜像, 并通过本地Telegraf进行把UDP端口暴露出来(作用是作为一个管道联通本机的UDP接口和InfluxDB的UDP接口)

输出

我们只用一个输出插件: InfluxDB 插件. 该插件很简单: 它把通过输入插件收集到的measurements上报给InfluxDB. 它还提供了一些从没隔输入插件中可以应用于所有measurements的全局标记.(我们使用其来设置host标记给应用程序和系统统计), 以及measurement过滤. 但是最有用的功能是该插件可以通过InfluxDB的HTTP接口通信, 而不是UDP接口: 这保证了统计包能够到达InfluxDB而不会丢失.

聚合

Telegraf 作为一个在我们的应用程序和InfluxDB之间的中间件, 这种架构有几个有点:

  • 我们可以从应用程序中通过UDP接口发送统计数据给Telegraf, 在上报统计数据的时候有极大的速度提升, 另外 Telegraf 是在本地运行的, 并且通过HTTP协议把统计信息发送给InfluxDB, UDP丢包的风险得到极大的降低. measurements 丢失的风险非常低.

  • 可以设置报告给InfluxDB的measurements数量阀值: Telegraf 会每隔N秒(5,或10秒是一个合理的值, 取决于应用程序)把这些值聚合后发送给InfluxDB, 这意味着, 每隔N秒, Telegraf 发送一个统计报告给InfluxDB, 减少了网络流量.

聚合的作用是可以降低报告的平率, 同时保持较高的采样频率. 同时减少因为上报统计数据给InfluxDB产生的网络流量.

Telegraf/InfluxDB 驱动

如果我们需要在Elixir应用程序中使用InfluxDB和Telegraf, 现在没有现成的Elixir的UDP接口可用(只有HTTP接口).

每个Elixir应用程序有其自己的Fluxter模块:

defmodule MyApp.Fluxter do
  use Fluxter
end

上面的代码把MyApp.Fluxter转换为一个UDP连接的监控进程池. 要达到容错的目的, 我们要保证在一个应用程序的 supervision 树下启动这个池.

def start(_type, _args) do
  children = [
    Supervisor.Spec.supervisor(MyApp.Fluxter, []),
    # ...
  ]
  Supervisor.start_link(children, strategy: :one_for_one)
end

最后, 我们通过应用程序配置来配置这个池:

config :fluxter,
  host: "localhost",
  port: 8086

然后, 一旦Fluxter池启动, 我们可以通过下面的方式来报告统计:

def my_operation() do
  MyApp.Fluxter.write("something_done", [my_tag: "foo"], 1)
end

关于 Fluxter 的详细信息, 可以参考 Fluxter文档

Erlang VM统计

对于Erlang虚拟机的统计, 我们使用 vmstats, 一个小巧的Erlang应用程序用于从Erlang虚拟机中搜集统计并报告给一个sink: 一个sink是一个实现了:vmstats_sink行为的Erlang/Elixir模块. 我们使用Fluxter池作为这个sink, 摈并且我们我们再每一个Elixir应用程序中都有一个等同的设置:

defmodule MyApp.Fluxter do
  use Fluxter

  @behaviour :vmstats_sink

  def collect(_type, name, value) do
    write(name, value: value)
  end
end

查看vmstats的README文件了解用它能够做什么.

批处理

我们在短时间内会产生大量的统计报告, 因此我们需要对其进行聚合操作以减少发送给InfluxDB的数据.

{:ok, batch} = MyApp.Fluxter.start_batch("my_operation_success", [host: "eu-west"])
Enum.each(1..1_000_000, fn(_) ->
  my_operation()
  MyApp.Fluxter.write_to_batch(batch, 1)
end)
MyApp.Fluxter.flush_batch(batch)

统计可视化

所有我们搜集到和存储的统计数据如果没有可视化是毫无用处的. 我们使用Grafana作为我们的可视化工具, 它原生支持InfluxDB(可以直接查询InfluxDB数据库), 并且提供一打很有用的功能特性.

我们的仪表盘可视化效果如下:

图片描述

上面我们使用的仪表盘用于监控应用程序的健康状态: 它显示了从系统和Erlang虚拟机搜集到的统计数据, 左上角的下拉菜单可以让我们选择要可视化的应用程序, 只需要点击选择不同的应用程序就可以查看系统和Erlang虚拟机的概述.

Grafana 提供了几个有用的功能: 它可以通过标记进行分组, 在一个图标上显示集群中不同的主机为不同的线条. 它还可以在数据上进行聚合排序操作, 而且外观也是非常容易定制的.

Grafana 是整个架构中一个最简单的部分, 但不是一个关键的部分.

报警

我们主要使用统计来测量我们的应用程序是如何运行的, 但我们还可以用它来做另一件非常有用的事情: 报警.

比如我们可以统计Erlang的进程数量, 当其值偏离平均值太多的时候, 我们知道我们需要查看一下系统到底发生了什么异常的情况.

InfluxDB供应商的另一个产品 Kapacitor 它是一个守护进程, 可以重复的在InfluxDB运行查询, 然后在查询结果上运行一些分析.

Kapacitor 是一个开源框架, 用来处理, 监控和警告时间序列数据. Kapacitor 使用 TICKscript 脚本来定义任务.

Kapacitor 的任务是通过 Kapacitor scripts 来定义Identifier. 它是用 Kapacitor 领域语言编写的任务脚本. 用这种脚本可以做很多事,

stream
  |from()
    .measurement('pushboy_vm_modules')
  |groupBy('host')
  |deadman(15.0, 1m) //
    .id('Pushboy []')
    .message(' is updown')
    .stateChangesOnly()
    .slack()

该脚本检查 vm_modules 度量值, 如果吞吐量降低到每分钟15个点以下触发一个警告. (健康的应用程序每分钟会报告60个点), 你看到最后一行, 通过Slack发送警告给管理员(在Kapacitor的配置中指定). Kapacitor 支持多种通知方式, 包括限于Slack, PagerDuty或者电子邮件.

总结

本文显示了我们如何从Elixir应用程序中收集程序的运行数据, 以及我们如何使用它来监视应用程序的监控状态和性能, 当出现应用程序出现异常的时候我们能够获得警告, 以让我们在早期对应用程序异常情况进行处理, 避免系统发送大的故障导致的经济损失.


developerworks
1.7k 声望266 粉丝