β

open-falcon 源码解析(一)

nosa.me 192 阅读

open-falcon 是小米开源的企业级监控系统,我最近在抽空读它的源码,是为了我能够在很短的时间内搭建出一套好用的监控系统,而且如果有不满足的需求可以很快修改。

下面是 open-falcon 的架构图,组件还挺多的,到目前为止我读完了 Agent、Transfer、Graph 和 Query 四个部分。

falcon-arch

Agent 部分

Agent 获取的数据格式为:
type MetricValue struct {
Endpoint  string      `json:”endpoint”`
Metric    string      `json:”metric”`
Value     interface{} `json:”value”`
Step      int64       `json:”step”`
Type      string      `json:”counterType”`
Tags      string      `json:”tags”`
Timestamp int64       `json:”timestamp”`
}

1. Type 是 rrd DsType(数据源类型),比如 GAUGE、COUNTER 和 DERIVE;
2. 当收集 网卡、硬盘使用率、硬盘 IO、进程、端口、目录大小、url 时,会使用 Tags,其他不会,因为这些信息需要额外知道比如 网卡设备、硬盘分区、硬盘设备、进程名称、端口号、目录名称、url 链接等信息;
3. Agent 会起一个 goroutine,获取要检测的 url 链接、端口号、进程名、目录大小等信息,这些信息是本机无法知道;
4. Step 值由配置文件中的 Transfer.Interval 决定;
5. 每个 Metric 收集和传输时间间隔由 Transfer.Interval 决定,收集到的信息会传递给 Transfer;
6. 支持「插件」,插件目录列表通过 RPC 获取,目录列表中的所有目录下的脚本都会当做插件执行,脚本名称要包括执行超时时间(以 _ 分割),而且脚本的输出要符合 MetricValue 的格式。

Transfer 部分

Transfer 收到 Agent 发来的数据后,会先做一下清理,不合法的数据会被忽略,比如 Type 不合法,Value 为空,Step <= 0 等。

然后 Transfer 把格式改成 MetaData:
type MetaData struct {
Metric string `json:”metric”`
Endpoint string `json:”endpoint”`
Timestamp int64 `json:”timestamp”`
Step int64 `json:”step”`
Value float64 `json:”value”`
CounterType string `json:”counterType”`
Tags map[string]string `json:”tags”`
}
1. CounterType 是 MetricValue 的 Type 值 。
2. Tags 会从 key1=value1,key2=value2 字符串变成类似 {key1:value1, key2:value2} 的 map。

然后 Transfer 把数据插入 Graph 和 Judge 内存队列,插入到哪台 Graph 机器 或者 Judge 机器 (称为 node )由一致性 hash 决定,队列以 node 为 key,根据 node 可以拿到队列。

然后对于 Graph 和 Judge 的每一个 node,都起一个 goroutine 来发送,根据 node 拿到 addr,对每一个 addr 已经初始化了 RPC 连接池,从连接池中选一个连接,然后 RPC 调用。

这里我有一个疑问:如果 Graph 或者 Judge 的一个 node 挂了,那么一致性 hash 会自动摘除吗?目前看起来不会。一个解决办法是所有 Graph 或者 Judge 机器都向 zookeeper 注册一个临时节点,而 Transfer 来监听变化,如果变化就更新一致性 hash。

另外,数据在插入 Graph 的队列之前会被改成下面的格式:
type GraphItem struct {
Endpoint  string            `json:”endpoint”`
Metric    string            `json:”metric”`
Tags      map[string]string `json:”tags”`
Value     float64           `json:”value”`
Timestamp int64             `json:”timestamp”`
DsType    string            `json:”dstype”`
Step      int               `json:”step”`
Heartbeat int               `json:”heartbeat”`
Min       string            `json:”min”`
Max       string            `json:”max”`
}

1. DsType、Step、Heartbeat、Min 和 Max 都是 rrd 的概念;
2. Step 不能小于 30s;
3. 如果 MetaData 的 CounterType 是 GAUGE,则 DsType 也是 GAUGE,如果 CounterType 是 COUNTER 或 DERIVE,DsType 都会被改成 DERIVE;
4. DsType 如果是 GAUGE,Min 和 Max 分别是 U、U,如果是 DERIVE,Min 和 Max 分别是 0、U。

rrd 的相关内容参考 这里

Graph 部分

收到的 GraphItem 数据存储在 GraphItemMap 结构的 GraphItems 变量中,GraphItemMap 结构如下:
type GraphItemMap struct {
sync.RWMutex
A    []map[string]*SafeLinkedList
Size int
}

1. map[string]*SafeLinkedList 的 key 格式是 checksum_dsType_step(称为 ckey),其中 checksum 是 Endpoint, Metric 和 Tags 三者的 md5,*SafeLinkedList 存的则是 GraphItem;
2. A 中 有 Size 个 map[string]*SafeLinkedList,0 到 Size-1 这些数字由 hashKey(ckey) % Size 取得。

Graph 收到数据后,会做三件事:
1. 存入定义的 GraphItems 结构中,Graph 会事先启动一个叫 rrdtool 的 goroutine,不断从 GraphItems 中读取 GraphItem 存入 rrd 数据库。

rrd 文件路径格式是:基础目录/md5前两位/md5/dsType/step.rrd

为了让 rrd 性能满足需求,设置了各种合并策略,比如 1 分钟一个点存 12 小时,5 分钟一个点存 2 天(取平均、最大、最小) 等。

2. 更新索引。

索引由两个缓存变量保存:unIndexedItemCache 和 indexedItemCache,前者保存未建立索引的数据,后者保存已经建立索引的数据。
它们结构一样,它们都以 Endpoint、Metric 和 Tags 的 md5 为 key,value 结构如下:
type IndexCacheItem struct {
UUID string
Item *cmodel.GraphItem
}
UUID 是 endpoint/metric/tags/dstype/step 组成的字符串。

有一个专门的 goroutine 从 unIndexedItemCache 去数据,来循环建立(增量)索引。

有三种索引,分别是 endpoint_ts 索引、tag_endpoint 索引 和 endpoint_counter 索引,索引信息存在 Mysql 中。

额外的,Graph 提供 /proc http 接口来更新全量索引(数据从 indexedItemCache 中获取),默认建立两天内数据的索引。

3. 存入 HistoryCache,HistoryCache 供查看最近收到的 GraphItem,HistoryCache同样以 Endpoint、Metric 和 Tags 的 Checksum 为 key,每个 key 默认只保存三条 GraphItem。

Query 部分

查询组件,向 Graph 查询数据。

1. history 接口。

查询参数为:
type GraphHistoryParam struct {
Start int `json:”start”`
End int `json:”end”`
CF string `json:”cf”`
EndpointCounters []cmodel.GraphInfoParam `json:”endpoint_counters”`
}

GraphInfoParam 结构为:
type GraphInfoParam struct {
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
}

上两个结构合并成如下一个个结构,然后通过 RPC 向 Graph 查询:
type GraphQueryParam struct {
Start int64 `json:”start”`
End int64 `json:”end”`
ConsolFun string `json:”consolFuc”`
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
}

查询结果保存在下面的结构:
type GraphQueryResponse struct {
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
DsType string `json:”dstype”`
Step int `json:”step”`
Values []*RRDData `json:”Values”`
}

RRDData 如下:
type RRDData struct {
Timestamp int64 `json:”timestamp”`
Value JsonFloat `json:”value”`
}

查询过程:
1). 根据 Endpoint 和 Counter 查询出 dsType 和 step,其中 Counter 需要是 Metric/Tags 格式;
2). 计算出 rrd 文件路径,获取 rrd 中的数据;
3). 算出 ckey,获取缓存中的数据。
4). 聚合处理。

2. info 接口。

请求结构是:
type GraphInfoParam struct {
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
}

返回结构是:
type GraphInfoResp struct {
ConsolFun string `json:”consolFun”`
Step int `json:”step”`
Filename string `json:”filename”`
}

3. last 接口。

请求结构是:
type GraphLastParam struct {
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
}

返回结构是:
type GraphLastResp struct {
Endpoint string `json:”endpoint”`
Counter string `json:”counter”`
Value *RRDData `json:”value”`
}

Related posts:

  1. open-falcon 源码解析(二)
作者:nosa.me
未来不会有sa
原文地址:open-falcon 源码解析(一), 感谢原作者分享。

发表评论