最终,我们在解决上述问题之后在阿里云上实现了上述的告警计算平台。
从某种角度而言 Flink 版本是第一个在 SaaS 上投产的系统,然而,它并不完美,有着上述这些问题。
从某种角度而言,Flink 计算告警有些大材小用,我们需要更轻量的架构。(这里中断,展示一下我们的告警系统。)
在 Flink 版本开发3个月后,我们开始着手开发新的企业级告警平台。因为现有的 Flink 版本,因为很多原因无法对外交付。
我也是从这个时候开始参与 OneAPM 告警的研发,我们做的第一件事情,就是结合之前 DSL 开发的经验,思考如何重新定义告警规则。这是因为 Flink 上定义的告警规则,就和现有的云迹 CTMAM 的告警规则一样,比较死板,不好扩展,且较为复杂。
这期间也参考了 Esper 之类的开源项目,比较后我们惊喜地发现,最好的告警规则定义方式就是 SQL。
我们在定义好规则模板之后,便开始由解析计算引擎 -> 处理队列引擎 -> 分布式管理平台 -> 操作接口的顺序 开发了现有的告警引擎。
首先是基于规则 DSL 的解析计算引擎。之前的 Mock Agent,我们使用的是 Scala 原生提供的语法分析组合子设计的。Druid SQL 使用的是 Antlr4,先解析出基本的 AST 语法树,然后转义为 Druid JSON 查询模型,最后序列化为查询 JSON。
这里的告警规则 SQL,我们用的是类似 Druid SQL 的方式。语法模板定义甚至都是类似的。只是增加了四则混合运算表达式的解析和运算,还有 avg 一类的计算函数的实现。
最终,它的解析处理流程就和 PPT 图示的一样。规则 SQL 语句被 Antlr4 做词法语法分析,将部分非逻辑单元符号化,然后构建出一棵 SQL 语法树。我们按照 Antlr4 提供的 Visitor 模式,以深度优先检索的方式遍历,然后不断的将结果按照定义的算子单元组合。最后对外暴露出两个方法,一个返回布尔值表示是否满足规则运算定义,另一个返回计算中想要获取的指标数据。
我们基于解析出来的规则对象,在 Engine 层对计算的事件队列和当前事件结合起来,就产生了实际想要的计算结果。
Engine 就相当于最小粒度的计算单元,但是,它缺少一些上下文管理。我们需要事件队列管理,规则和计算数据的关联,才能真正意义上调用 Engine 去计算。
基于这个需求,我们开发了 Runtime 模块。它在逻辑上有两大抽象,一个是 RuntimeContext,一个是 EventChannel。
RuntimeContext 就和它的名称一样,表示运行时上下文,每个RuntimeContext 对应一条具体的规则示例,内部会维护对应的 RuleTemplate。我们在设计初期就考虑类似多数据源的情况,一条计算规则可能对应多个探针数据,于是内部定义了 InputStream 的概念。
它相当于实际的一条计算指标数据流,实际存储在 EventChannel 上,EventChannel 为在内存中存储的一个指标数据队列。它有两块数据:一个是一个 Event Queue,一个是当前才来的一条要计算的指标数据。Event Queue 的设计参考了 Guava Cache 里面的队列,因为规则创建时对应的数据窗口大小是确定的,于是这个 Queue 的大小也是确定的。
一个 RuntimeContext 示例可能对应多个 EventChannel,一个 EventChannel 也可能对应多个 RuntimeContext,二者基于一个唯一的 key 关联起来。我们修改规则的时候,需要修改对应的 RuntimeContext。事件来了要计算的时候,是直接 sink 到 EventChannel 中。
Runtime 相当于 Flink 里面最小的计算任务,有着自己的状态,能解析 SQL 并进行运算。
但是对于分布式、集群等部署环境,它还存在着较大的问题。在其之上,我们使用 Akka 开发了核心的运行模块。
我们使用 Akka Cluster 开发了计算集群,Akka Cluster 将 Akka 应用分为 Seed Node 和一般 Node。启动的时候,要先启动 Seed Node,才能启动子 Node。但是启动后如果 Seed Node 挂了,Akka 可以选出一个新的存活节点当做 Seed Node。
我们在 Akka 集群启动后,会使用 Seed Node 创建 Kafka Message Dispatcher Actor 来和 Kafka 消费数据,然后分发到各个子节点上。这么做的话,同一时刻,只有一个线程在从 Kafka 消费数据。使用单线程的考虑有很多,比如避免 Kafka repartition。其次,我们测试后发现,从 Kafka 消费这块使用单线程不存在瓶颈。
每个 Akka 节点都分为 EvenStreamActor、RuleActor 两类核心处理计算单元,EventStreamActor 除了管理 EventChannel 之外,还会将数据分发到别的 Akka 节点,做二次计算。RuleActor 管理 RuntimeContext,其下包含 Persist Actor 将告警事件和应用实时状态持久化到金字塔存储,Alert Actor 将告警数据写入至 Doko MQ 用于接入系统执行告警行为(如短信、邮件、WebHook 等)。
感谢博主,收获很多,个人觉得整个架构重点在关注数据流式处理和存储,为什么不考虑用类似influxdb等时序数据库引擎实现呢?
雨帆大佬,这篇文章写的是非常好啦。小弟还有些不懂的地方,可以求个认识么?邮箱是QQ也是微信。