监控系统,通常被用于监控其他系统的状态。那么,监控系统的状态,怎么来监控?
我们把对监控系统的监控,称为监控系统的自监控。自监控的需求,没有超出监控的业务范畴。同其他系统一样,监控系统的自监控要做好两方面的工作: 故障报警和状态展示。故障报警,要求尽量实时的发现故障、及时的通知负责人,要求高可用性。状态展示,多用于事前预测、事后追查,实时性、可用性要求 较故障报警 低一个量级。下面我们从这两个方面,来介绍小米监控系统Falcon在自监控方面的一些实践经验。
故障报警
故障报警相对简单。我们使用第三方监控组件AntEye,来监控Falcon实例的健康状况。
Falcon各个组件,都会提供一个描述自身服务可用性的自监控接口,描述如下。AntEye服务会定时巡检、主动调用Falcon各实例的自监控接口,如果发现某个实例的接口没有如约返回”ok”,就认为这个组件故障了(约定),就通过短信、邮件等方式 通知相应负责人员。为了减少报警通知的频率,AntEye采用了简单的报警退避策略,并会酌情合并一些报警通知的内容。
# API for my availability
接口URL
/health 检测本服务是否正常
请求方法
GET http://$host:$port/health
$host 服务所在机器的名称或IP
$port 服务的http.server监听端口
请求参数
无参数
返回结果(string)
"ok"(没有返回"ok", 则服务不正常)
AntyEye组件主动拉取状态数据,通过本地配置加载监控实例、报警接收人信息、报警通道信息等,这样做,是为了简化报警链路、使故障的发现过程尽量实时&可靠。AntEye组件足够轻量,代码少、功能简单,这样能够保障单个AntEye实例的可用性;同时,AntEye是无状态的,能够部署多套,这进一步保证了自监控服务的高可用。
在同一个重要的网络分区内,通常要部署3+个AntEye,如下图所示。我们一般不会让AntEye做跨网络分区的监控,因为这样会带来很多网络层面的误报。多套部署,会造成报警通知的重复发送,这是高可用的代价;从我们的实践经验来看,这个重复可以接受。
值得注意的是,原来故障发现功能是Falcon的Task组件的一个代码片段。后来,为了满足多套部署的需求,我们把故障发现的逻辑从Task中剔除,转而使用独立的第三方监控组件AntEye。
状态展示
状态展示,是将Falcon各组件实例的状态数据,以图形化的形式展示出来,方便人的查看。鉴于实时性、可用性要求不高,我们选择Falcon来做自身状态数据的存储、展示(用Falcon监控Falcon,自举了),剩下的工作就是状态数据的采集了。
Falcon的多数组件,都会提供一个查询服务状态数据的接口,描述如下。
# API for querying my statistics
接口URL
/counter/all 返回所有的状态数据
请求方法
GET http://$host:$port/counter/all
$host 服务所在机器的名称或IP
$port 服务的http.server监听端口
请求参数
无参数
返回结果
// json格式
{
"msg": "success", // "success"表示请求被成功处理,其他均是失败
"data":[ // 自身状态数据的list
// 每个状态数据, 都包含字段 Name(名称)、Cnt(计数)、Time(时间),可能包含字段Qps
{
"Name": "RecvCnt",
"Cnt": 6458396967,
"Qps": 81848,
"Time": "2015-08-19 15:52:08"
},
...
]
}
Falcon的Task组件,通过获取状态数据的API接口,周期性的主动拉取Falcon各实例的状态数据;然后,处理这些状态数据,适配成Falcon要求的数据格式;再将适配后的数据,push给本地的Agent;本地的Agent会将这些数据转发到监控系统Falcon。
Task组件,通过配置文件中的collector
项,定义状态数据采集的相关特性,如下
"collector":{
"enable": true,
"destUrl" : "http://127.0.0.1:1988/v1/push", // 适配后的状态数据发送到本地的1988端口(Agent接收器)
"srcUrlFmt" : "http://%s/counter/all", // 状态数据查询接口的Format, %s将被替换为 cluster配置项中的 $hostname:$port
"cluster" : [
// "$module,$hostname:$port",表示: 地址$hostname:$port对应了一个$module服务
// 结合"srcUrlFmt"的配置,可以得到状态数据查询接口 "http://test.host01:6060/counter/all" 等
"transfer,test.host01:6060",
"graph,test.host01:6071",
"task,test.host01:8001"
]
}
Task做数据适配时,将endpoint设置为数据来源的机器名$hostname
($hostname为Task采集配置collector.cluster某条记录中的机器名),将metric设置为原始状态数据的$Name
和$Name.Qps
,将tags设置为module=$module,port=$port,type=statistics,pdl=falcon
($module,$port为Task采集配置collector.cluster某条记录中的模块名和端口,其他两项为固定填充), 将数据类型设置为GAUGE
,将周期设置为Task的数据采集周期。比如,采用了上述采集配置的Task,将会做如下适配:
# 一条原始的状态数据,来自"transfer,test.host01:6060"
{
"Name": "RecvCnt",
"Cnt": 6458396967,
"Qps": 81848,
"Time": "2015-08-19 15:52:08"
}
# Task适配之后,得到两条监控数据
{
"endpoint": "test.host01", // Task配置collector.cluster中的配置项"transfer,test.host01:6060"中的机器名
"metric": "RecvCnt", // 原始状态数据中的$Name
"value": 6458396967, // 原始状态数据中的$Cnt
"type": "GAUGE", // 固定为GAUGE
"step": 60, // Task的数据采集周期, 默认为60s
"tags": "module=transfer,port=6060,pdl=falcon,type=statistics", // 前两个对应于Task的collector.cluster配置项"transfer,test.host01:6060"中的模块名和端口,后两个是固定填充
...
},
{
"endpoint": "test.host01",
"metric": "RecvCnt.Qps", // 原始状态数据中的$Name + ".Qps"
"value": 81848, // 原始状态数据中的$Qps
"type": "GAUGE",
"step": 60,
"tags": "module=transfer,port=6060,pdl=falcon,type=statistics",
...
}
我们只能向监控系统Falcon,push一份状态数据(push多份会有重叠、不利于观察),因此,在每个网络分区中只能部署一个Task实例(同样地,不建议跨网络分区采集状态数据)。单点部署,可用性太差了吧?确实。不过好在,AntEye服务会监控Task的状态,能够及时发现Task的故障,在一定程度上可以缓解 状态数据采集服务 的单点风险。
状态数据入Falcon之后,我们就可以定制Screen页面。如下图,是小米Falcon的状态数据统计页面。定制页面时,需要先找到您关注的counter,这个可以通过dashboard进行搜索,如下图。不同组件、不同counter的具体含义,见附录。
筛选自监控相关的状态指标
定制你的自监控状态数据Screen
有了Falcon自身状态数据的Screen,运维就会变得很方便: 每天早上开始正式工作之前,花10分钟时间看看这些历史曲线,小则发现已经发生的问题,大则预测故障、评估容量等。曲线图表神马的,真心能够提升工作效率;想想有的领导提出,监控要去UI化,只能呵呵了。
总结
对于自监控,简单整理下。
-
神医难自医。大型监控系统的故障监控,往往需要借助第三方监控系统(AntEye)。第三方监控系统,越简单、越独立,越好。
-
吃自己的狗粮。一个系统,充分暴露自身的状态数据,才更有利于维护。我们尽量,把状态数据的存储容器做成通用的、把获取状态数据的接口做成一致的、把状态数据的采集服务做成集中式的,方便继承、方便运维。当前,程序获取自己状态数据的过程还不太优雅、入侵严重;如果您能指点一二,我们将不胜感激。
附录
1. Falcon状态指标
以下是Falcon较重要的状态指标(非全部)及其含义。
## transfer
RecvCnt.Qps 接收数据的Qps
GraphSendCacheCnt 转发数据至Graph的缓存长度
SendToGraphCnt.Qps 转发数据至Graph的Qps
SendToGraphDropCnt.Qps 转发数据至Graph时, 由于缓存溢出而Drop数据的Qps
SendToGraphFailCnt.Qps 转发数据至Graph时, 发送数据失败的Qps
JudgeSendCacheCnt 转发数据至Judge的缓存长度
SendToJudgeCnt.Qps 转发数据至Judge的Qps
SendToJudgeDropCnt.Qps 转发数据至Judge时, 由于缓存溢出而Drop数据的Qps
SendToJudgeFailCnt.Qps 转发数据至Judge时, 发送数据失败的Qps
## graph
GraphRpcRecvCnt.Qps 接收数据的Qps
GraphQueryCnt.Qps 处理Query请求的Qps
GraphLastCnt.Qps 处理Last请求的Qps
IndexedItemCacheCnt 已缓存的索引数量,即监控指标数量
IndexUpdateAll 全量更新索引的次数
## query
HistoryRequestCnt.Qps 历史数据查询请求的Qps
HistoryResponseItemCnt.Qps 历史数据查询请求返回点数的Qps
LastRequestCnt.Qps Last查询请求的Qps
## task
CollectorCronCnt 自监控状态数据采集的次数
IndexDeleteCnt 索引垃圾清除的次数
IndexUpdateCnt 索引全量更新的次数
## gateway
RecvCnt.Qps 接收数据的Qps
SendCnt.Qps 发送数据至transfer的Qps
SendDropCnt.Qps 发送数据至transfer时,由于缓存溢出而Drop数据的Qps
SendFailCnt.Qps 发送数据至transfer时,发送失败的Qps
SendQueuesCnt 发送数据至transfer时,发送缓存的长度
## anteye
MonitorCronCnt 自监控进行状态判断的总次数
MonitorAlarmMailCnt 自监控报警发送邮件的次数
MonitorAlarmSmsCnt 自监控报警发送短信的次数
MonitorAlarmCallbackCnt 自监控报警调用callback的次数
## nodata
FloodRate nodata发生的百分比
CollectorCronCnt 数据采集的次数
JudgeCronCnt nodata判断的次数
NdConfigCronCnt 拉取nodata配置的次数
SenderCnt.Qps 发送模拟数据的Qps
## TODO: judge & alarm & so on