openfalcon源码分析之Judge

openfalcon源码分析之Judge

本节内容

  1. Judge功能
  2. 源码分析
  3. 设计优缺点

1. Judge功能

在open-falcon中,Judge模块的功能是通过从HBS上同步告警的strategys(告警策略),及Expression,用来在接收transfer上报过来的数据时,对数据进行检测,每收到一条上报的数据,就去匹配对应的strategy和Expression是否满足条件,判断是否需要向redis发送告警。

告警策略

open-falcon中的策略有两类,strategys和Expression。strategys与主机进行管线,可以通过一个主机找到其对应的所有strategys。而Expression则不需要有任何关联,是对全局范围内进行的一个过滤和判断。

stratrgys查找策略

从transfer上接收过来的数据带有endpoint,通过主机可以找到其对应的主机组(主机和主机组是多对多的关系),通过主机组可以找到对应的模板组(模板和主机组是多对多的关系),模板之间又可能有继承关系,通过模板组可以找到对应的strategy(模板和strategy是一对多的关系)一个模板就是一组strategy的集合。这样就可以通过上报过来的数据的endpoint找到其对应的所有的strategys了。

expression查找策略

expression不和主机关联,其针对的是全局的配置,主要对应一些需要特殊定制的告警检查。

2. 源码分析

处理流程分析

接收数据方

  1. 通过RPC注册两个方法,一个是ping,返回一个nil的返回值,另一个是send,处理transfer发送过来的数据。
  2. 对于transfer发送过来的每个监控数据,都会生成一个pk(对数据的endpoint,metric,tags进行字符串拼接,之后再用md5进行hash,生成一个hash值)。通过pk的前两位hash值,去HistoryBigMap中找到对应的值,调用PushFrontAndMaintain。(HistoryBigMap的结构会在后面进行讲解)
  3. 调用PushFrontAndMaintain会去JudgeItemMap中找到pk对应的值(链表)
    • 若该pk能找到值,则调用该链表的PushFrontAndMaintain方法,将新到来的监控数据和链表最大长度传递进去。链表的PushFrontAndMaintain方法将新监控数据插入到链表的头部,如果超过了最大长度,则把最老的数据删除。再调用Judge方法对新监控数据和历史数据进行判断。

    • 若没找到值,则会新创建一个链表,把新监控数据放入链表中。再调用Judge方法对新监控数据和历史数据进行判断。

  4. Judge方法会调用CheckStrategy方法以及CheckExpression方法对新监控数据和历史数据进行处理。
    • CheckStrategy方法先将监控数据中的endpoint和metric进行字符串拼接成key,通过key去strategys中寻找对应的strategys列表。对于一个监控数据如果找到了多个strategy,则会对每个strategy的tags进行匹配,若和监控数据不符合则被过滤掉。剩下的每个strategys将会调用judgeItemWithStrategy方法,该方法先将strategy的表达式进行拼接找到对应的函数,然后将历史数据传递到Compute中进行逻辑判断,如果判断匹配则返回的值isTriggered为true,否则返回false.在judgeItemWithStrategy中会创建event事件,调用sendEventIfNeed方法判断该event是否应该发送出去。
      • sendEventIfNeed方法会去检查LastEvents中该event是否已经存在,并按照一定规则判断是否需要调用sendEvent将event发送到redis中。源码如下.
    • CheckExpression方法先将监控数据中的endpoint、metric、tags进行字符串拼接成key,再用该key在ExpressionMap字典中检查是否有对应的expressions,如果找到则调用filterRelatedExpressions进行处理
      • filterRelatedExpressions检查所有对应的expressions,筛选掉tags不匹配的expressions,剩下的就是所有符合条件的expressions,针对所有符合条件的expressions,调用judgeItemWithExpression构建告警event,后面的处理步骤和strategys一致。
// sendEventIfNeed方法中判断event是否该发告警的逻辑
if isTriggered {
    event.Status = "PROBLEM"
    if !exists || lastEvent.Status[0] == 'O' {
    // 本次触发了阈值,之前又没报过警,得产生一个报警Event
    event.CurrentStep = 1
       
    // 但是有些用户把最大报警次数配置成了0,相当于屏蔽了,要检查一下
    if maxStep == 0 {
    	return
    }
       
    sendEvent(event)
    return
    }
       
    // 逻辑走到这里,说明之前Event是PROBLEM状态
    if lastEvent.CurrentStep >= maxStep {
    // 报警次数已经足够多,到达了最多报警次数了,不再报警
    return
    }
       
    if historyData[len(historyData)-1].Timestamp <= lastEvent.EventTime {
    // 产生过报警的点,就不能再使用来判断了,否则容易出现一分钟报一次的情况
    // 只需要拿最后一个historyData来做判断即可,因为它的时间最老
    return
    }
       
    if now-lastEvent.EventTime < g.Config().Alarm.MinInterval {
    // 报警不能太频繁,两次报警之间至少要间隔MinInterval秒,否则就不能报警
    return
    }
       
    event.CurrentStep = lastEvent.CurrentStep + 1
    sendEvent(event)
    } else {
    // 如果LastEvent是Problem,报OK,否则啥都不做
    if exists && lastEvent.Status[0] == 'P' {
    event.Status = "OK"
    event.CurrentStep = 1
    	sendEvent(event)
    	}
}

同步strategy和expressions方

main函数中起了go-routine 专门用来从hbs同步strategy和expressions,同步完之后重新组装成StrategyMap和ExpressionMap两个字典。

几个数据结构分析

  1. HistoryBigMap
    • HistoryBigMap是个大字典,key是pk的hash值的前两位,一共有16*16=256个键,value是NewJudgeItemMap,也是一个字典,该字典的key是pk,value对应历史数据组成的链表,链表最大长度可在配置文件中的remain配置,默认是11,也就是默认对每个数据,保留11个历史数据节点。
  2. StrategyMap
    • StrategyMap是个字典,key是hostname和Metric拼接的字符串,value是一个array,因为同一个hostname和Metric可能对应多个Strategy(可能tags不同,当然可能没有tags时,父子模板中都有该Strategy)
  3. ExpressionMap
    • ExpressionMap是个字典,key是metric和tags拼接的字符串,value是一个array,metric和tags无法唯一确定expression。
  4. LastEvents
    • LastEvents是个字典,key是event.Id,该id是由strategy.id和pk的hash值拼接产生的,可以唯一确定一个数据,所以value就是event事件本身。

3. 设计优缺点

优点:

  1. 支持告警间隔以及最大告警次数,太多的告警并没有什么帮助,况且会将真正有用的告警淹没在无意义的告警中。
  2. 支持Expression,zabbix是将告警和主机绑定起来,某些并不和某个主机关联的告警无法做,有了Expression之后就可以关联那一类告警了。

缺点:

  1. 只支持按照次数告警,如果支持按照时间告警就好了,比如某台机器连续五分钟cpu负载过高告警。
posted @ 2017-08-23 19:33  随风飘雪012  阅读(2388)  评论(0编辑  收藏  举报
返回顶部 $(function(){ $('#returnTop').click(function () { $('html,body').animate({ scrollTop: '0px' }, 800); returnfalse; }); });