使用 Kapacitor 对 InfluxDB 数据进行统计处理

InfluxDB + Telegraf + Grafana 算是一套不错的数据收集、视觉化工具,相比于更为常见的 ELK 架构,它有着自己的特点。查询功能总得来说虽不如 ES 强大,但是优点是——已经够用了,而且它的性能不错,存储的数据占用空间也相比 ES 小。

简单介绍这三个工具:

  • InfluxDB InfluxData 的一款时序数据库,它是这套工具的核心;
  • Telegraf 则是 InfluxData 自己开发的一个数据收集工具,相比于 Logstash 或者 Mozilla 的 Heka,它有了更多的内置实用 input 插件,例如 sysstat, Mongodb, MySQL, 日志文件 grok 和跟踪 … 使用 Telegraf 可以非常容易的监控起系统和各种常见服务的各种参数,基本无需再安装其它专用工具。
  • Grafana 则是一个数据视觉化工具(“画图”的),支持多种数据后端,包括 InfluxDB,Elasticsearch,Zabbix 等。

实际上,InfluxData 是有一个自己的 TICK 架构的(Telegraf + InfluxDB + Chronograf + Kapacitor),其中 Chronograf  是用来画图的,但是看起来似乎还不成熟,不如使用 Grafana 好。Kapacitor 则是一个专门处理 InfluxDB 里面的数据的工具,用它可以对收集到的数据进行统计、触发报警等。。。

好了,说了这么多,其实是想借一个实例说下怎样用 InfluxData 的 Kapacitor 来对 InfluxDB 的数据进行加工处理。

假设:已经搭建好了 InfluxDB + Telegraf + Grafana 这套工具,web 日志收集存储在了 InfluxDB,我们想查看一些统计数值,比如每日独立访问 IP 这样的有价值的数据,我们现在该怎么做?

一般来说,有以下几个选项:

  1. 在 Grafana 里面,我们新建一个图表,每次查看时实时统计出所有的每日独立 IP。但是这个方法实在不妥,web 日志量很大,如果每次一查看那个图表就把 InfluxDB 里面的数据全部过一遍,还要计算、分组,实在太低效,性能太糟糕。对于每日独立 IP 这种数据来说,我们其实并不需要它是实时的,只要每天统计一次即可。
  2. 我们写个 Shell 或者强大一点,Python 脚本去从 InfluxDB 查询出独立 IP 数,再把结果存回 InfluxDB,再把它放到 crontab 里面每天运行,然后 Grafana 建立的图表可以直接查这个新的 measurement。这种方法远比第一种要好很多,但是仍然是需要写脚本的。
  3. 我们可以利用专门的工具——Kapacitor 来做这件事。下面就详细说下如何取每日独立 IP 数。

按照官方的简介,Kapacitor 是一款“Time-Series Data Processing, Alerting and Anomaly Detection”,也就是说——数据处理、报警、异常探测它都能做。

Kapacitor 的安装、配置都很简单,推荐从官网下载 rpm/deb 包安装,然后修改 /etc/kapacitor/kapacitor.conf 文件,在 InfluxDB 连接配置里面设置好账号、密码,就可以启动 kapacitor 服务了。

Kapacitor 有一个后台服务,用户需要做的事就是编写 TICKscript 脚本,然后把这个脚本加载。

来看这个计算每日独立 IP 的脚本,ip.tick:

batch
    |query('''
        SELECT count(distinct(clientip)) AS uip
        FROM "telegraf"."retentionPolicy"."log_measurement"
    ''')
        .period(1d)
        .cron('0 0 * * *')
    |influxDBOut()
        .database('logstats')
        .measurement('site_uip')
        .tag('kapacitor', 'true')

其中,batch 指令表示这是一个对数据进行一次批量处理的任务; |query() 表示这是数据查询的处理节点,我们在 query() 里面直接写入 InfluxDB 的类SQL 查询语句即可,但并没有加入 WHERE 条件对查询时间进行限定,这是为何?因为后面的 .period() 指定了查询范围为一天;然后再利用 .cron() 这个属性,我们可以告诉 Kapacitor 每天零点都执行一次这个查询。|influxDBOut() 则是将查询出来的数据存入 InfluxDB,它的几个属性也比较好理解,不再详说。

保存文件后,执行以下命令:

$ kapacitor define log_uip -tick ip.tick -type batch \
   -dbrp telegraf.retentionPolicy
$ kapacitor enable log_uip

就定义和启用了这个 Kapacitor 任务。接下来还可以用 kapacitor show log_uip 或者 kapacitor list tasks 等命令查看任务状态。看到了吧,这样比脚本加 crontab 还是要简单不少的。(kapacitor 命令的参数含义可以用 help 子命令或者这个文档链接查看。)

在它运行过后,我们就可以在 Grafana 里面取 logstats 数据库里面 site_uip 的 uip 出来进行画图了。

总结:这篇文章只是用一实例展示了 Kapacitor 的功能之一,实际上 Kapacitor 的功能远不止这些,(报警等功能,因为我的环境早有这种功能,没有使用过。)tick 脚本的语法也比上面展示出来的部分强大很多,其数据处理节点也很丰富。总得来说,InfluxData 的这套 TICK 架构似乎还在快速发展的起步阶段(尤其是除 InfluxDB 外的那三部分),但是其实用性已经有一定吸引力了。

当你的程序依赖于 MySQL 查询缓存…

前两天升级和迁移了一个 MySQL 环境,因为:

  1. 某天我发现原来的环境居然是个 RAID 0,尽管已经有个 Slave 环境了,但是把数据放在毫无冗余的磁盘上面实在是有太大风险。
  2. 系统需要扩展了,接手这个项目时候,程序和数据库都在一个机器,从性能、安全各个方面来说都不是很好的方案。
  3. MySQL 5.6 相比 5.5 在查询优化上面有很大的提升。
  4. 事实上刚好还有个 mongodb 需要升级版本,可以一起下线后升级。

OK,于是就找了个凌晨,下线、升级了,初看起来一切正常。

但是到了第二天,问题出现了。开发说从 MySQL 同步数据到 mongodb 非常慢,只有原来的 1/36!

于是都开始怀疑是 mongodb 的问题,难道是程序逻辑有问题?写新版本的 mongodb 没有对参数什么的?但是得到否定的答复。

。。。。。。

折腾了好一会儿,看了 mongodb 的各种文档之后,看了一下一直在跑着的 mongostat 输出,发现是有出现过高并发的写入(但不是出现问题的同步造成的),至少,这说明了——mongodb 升级后的写入功能本身是没有问题,事实上,观察到的结果是有提升。排除了 mongodb 的问题,心想是否可能 MySQL 的数据库问题,看了一眼慢日志,结果确实发现有大量的同一个超过 1s 的查询,时间点和同步一致。

问了一下开发,他们说这个查询应该会被缓存住,第一下很慢,但是后面就很快了!好吧,我看了一眼 MySQL 的查询缓存:

mysql> show status like 'Qcache%';

哈!全部是0,查询缓存被关闭了!

再查了一下 MySQL 的参考文档

query_cache_type
...
This variable defaults to OFF as of MySQL 5.6.8, ON before that.

MySQL 在 5.6 之前的版本是默认开启查询缓存的,而 5.6 开始却默认关闭了它!

于是,设置 query_cache_type=1 之后,重启 MySQL,同步一切正常了。

总结:MySQL 做了这个改动,但是却没有在它们的升级文档中给出说明;开发把程序一个重要的功能依赖于这个数据库缓存,也没有说明。谢谢你们一起给我出了难题!当然,更重要的一个事情是——升级之前没有彻底的测试过!!!所有人都想当然了,这是流程规范的问题。

Kibana 4 仍然不支持“其它”聚合

因为 Kibana 3 早就停止开发和维护了,也不支持新版本的 Elasticsearch,现在无论愿不愿意其实都该使用 Kibana 4 了,但是对于我来说 Kibana 4 有很大的问题——无法在饼图里面添加 Top N 之后,把其它的东西统统归入“其它(other)”值里面去。缺乏这个功能,很多时候都导致图表的实际意义并不大了,因为没有正确显示某个值的百分比。

Github 上面早有 issue 提交和跟踪了,但是还没有解决。

Kibana 4 另外的一个失望之处是矢量地图被抛弃了。

只能希望慢慢修复好吧。。。

Varnish 位于负载均衡后端的缓存清理

假设有如下的架构:
LB proxy pass to Varnish
我们有时清理缓存会遇到问题:虽然 Varnish 上面的缓存清理机制配置好了,但是使用客户端访问到的那个 URL 却无法清理缓存!具体一点来说是比如客户端使用”http://www.example.com/some/url” 访问到最前端的负载均衡(比如是使用 Nginx),那么当我们使用 PURGE 方法在 Varnish 服务器上面清除”http://www.example.com/some/url”这个 URL 的时候,会发现尽管 ban 规则添加了,但是客户端访问到的仍然是旧的资源;不过使用 Ctrl+f5 却能清除缓存,但这种方法在有多个 Varnish 缓存服务器的情况下不是一个好的清除缓存的方法。
出现这种情况的原因可能是负载均衡上面把 URL 做了修改了,比如 URL 重写、Host 头替换等。这个时候客户端发送过来的”http://www.example.com/some/url”到了 Varnish 服务器可能就变成了访问的是”http://www.example.com/some/other/url”或者”http://other.example.com/some/url”。这个时候自然是无法再使用客户端可见的 URL 来清理缓存,因为缓存服务器上面存在的是另外一个 URL 的缓存!
不过这个问题还是比较容易解决的。思路是是一定要把客户端访问的那个 URL 通过某种方式传递给缓存服务器,然后缓存服务器可以识别到那个前端 URL,并且使用那个客户端 URL 作为清除缓存的标志。
假设最前端是 Nginx 在做调度,我们可以通过以下步骤实现:
1. Nginx proxy pass 到后端的时候添加两个 HTTP 头,比如 X-Forwarded-Host 和 X-Forwarded-Url,分别保存用户请求的原始 Host 头和 URL,这两个会在 Nginx 请求后端的时候被发送。

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Url  $request_uri;

(注意:proxy_set_header 指令如果在低级别位置定义,则不会继承高级别位置已经定义的。也就是说如果你在 http 这个配置段加了那两条配置指令,但是 location 配置段有其它的 proxy_set_header 指令存在了,那么 location 段不会继承到这两个 HTTP 头的配置,必须要单独在这里再配置一次。坑爹的 Nginx!)
2. Varnish 上面读取 X-Forwarded-Host 和 X-Forwarded-Url 这两个 HTTP 头,并且把它们作为清除缓存的标志。

sub vcl_backend_response {
        if (bereq.http.X-Forwarded-Host) {
                set beresp.http.X-PURGE-Host = bereq.http.X-Forwarded-Host;
        } else {
                set beresp.http.X-PURGE-Host = bereq.http.Host;
        }

        if (bereq.http.X-Forwarded-Url) {
                set beresp.http.X-PURGE-Url = bereq.http.X-Forwarded-Url;
        } else {
                set beresp.http.X-PURGE-Url = bereq.url;
        }
}

清理缓存的是这条命令:

ban ("obj.http.X-PURGE-Host == " + req.http.Host + " && obj.http.X-PURGE-Url ~ ^" + req.url + "$");

最终我们就都可以用和客户端一样的 URL 去清理缓存了。

Nginx with SSL

因为有了StartSSL 这样的可以免费申请SSL证书的机构,所以我觉得把自己的一些站点放到https 下还是值得去做了的。(至少,总还是会有一部分人会对在非SSL加密下的登录页面感到不舒服吧)

SSL 的性能问题

SSL 增强了安全,但它有些问题。它会导致服务器的资源消耗比http 大(但对现在一般的机器来说似乎已经不再是个问题了)。很多情况下人们对于https 的链接还是有一个感觉——慢,准确点来说应该是初次连接到服务器时会感觉慢,而一旦连接建立后,速度上感觉是与明文的http相差无几的。这主要是因为本地浏览器与服务器端初次建立连接握手时需要进行(非对称)密钥交换,密钥交换的协议和密钥的加密算法、长度都会对这个时间有影响。但是连接建立后,内容是以密钥长度短得多的对称加密的方式在传输,而且现代的浏览器同样也能对https链接的内容进行缓存等操作,所以也就感觉不到什么差别了。

因为慢,所以要对它作些调节。但知道了原因后也就好办了。

  • 密钥交换协议的选择。可以在Nginx 配置文件的ssl_ciphers 指令中添加“!kEDH”禁用先对耗时更长的DH 密钥交换机制,而使用RSA【1】。从1.0.5 版本开始,Nginx 默认的SSL ciphers是“HIGH:!aNULL:!MD5”,我们可以把它改为“HIGH:!aNULL:!MD5!kEDH”。应该看到如下变化:


使用DHE_RSA 进行key 交换

更改后的使用RSA key交换机制

  • 服务器公钥长度的选择。长度越长的服务器公钥一般来说是越安全的,但建立连接的时间也会越长,所以这就需要作些取舍了。像Google 和Facebook 这两家规模巨大但提供了许多全程加密的web 服务的公司就使用了相对“弱”的1024位公钥(openssl s_client -connect domain.com:443 查看),这也许降低了建立连接的安全性,但更重要的是降低了连接建立时间。(BTW,Google 还在传输过程中对Chrome 浏览器使用了比HTTP 更快的SPDY 协议,并且key exchange 机制也是RSA 的。)

其它还有要注意的地方有服务器ssl_session_cache, ssl_session_timeout, keepalive_time 等指令的使用等。

——

一些有用的链接:

【1】. http://matt.io/technobabble/hivemind_devops_alert:_nginx_does_not_suck_at_ssl/ur

http://nginx.org/en/docs/http/configuring_https_servers.html

http://www.imperialviolet.org/2010/06/25/overclocking-ssl.html

http://blog.httpwatch.com/2011/01/28/top-7-myths-about-https/

http://blog.httpwatch.com/2009/01/15/https-performance-tuning/

Iptables default DROP 策略和hitcount

记一下。当使用了默认为DROP 的策略时,iptables 限制连接次数的脚本应该可以这么写:

[sourcecode language=”bash”]
#!/bin/sh

# Flush all existing rules.
iptables -F

iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

iptables -A INPUT -i $INET_IF -p tcp -d $INET_ADDR –dport 22 -m state –state NEW -m recent –update –seconds 300 –hitcount 5 -j DROP
iptables -A INPUT -i $INET_IF -p tcp -d $INET_ADDR –dport 22 -m state –state NEW -m recent –set -m state –state NEW,ESTABLISHED -j ACCEPT

[/sourcecode]

使用default DROP 策略时,–update 应该是要位于–set 之前的。因为之前把iptables 和PF 有些记混了,记成了iptables 也是“最后匹配”的,所以写成了先–set, 然后–update 的顺序,结果测试发现根本就无法达到限制连接数的作用。
PS:之所以限制SSH server 端口的连接次数只是为了让auth.log 简洁一点,算不上什么安全策略。确保SSH server 安全正确的做法应该还是禁止密码验证方式吧。