小米开源监控系统OpenFalcon应对高并发7种手段

今天给大家简单介绍一下 OpenFalcon 应对高并发的一些手段。OpenFalcon 是一个监控系统,来自于小米的运维团队,OpenFalcon 主要针对运维架构师、DevOP 及关注高并发的研发人员。小米在使用 OpenFalcon 的过程当中,每个周期(5 分钟)大约有 1 亿条数据汇报上来。

下面我首先会对 OpenFalcon 做一个简单介绍,然后再介绍小米在高并发场景 7 种应对的手段,包括怎么做数据采集、转发、分片、报警等内容。

openfalcon-01

OpenFalcon 架构

OpenFalcon 简介

监控系统是整个运维环节,乃至整个产品生命周期中最重要的一环。

当公司刚刚起步,业务规模较小,运维团队也刚刚建立的初期,选择一款开源的监控系统,是一个省时省力,效率最高的方案。

之后,随着业务规模的持续快速增长,监控的对象也越来越多,越来越复杂,监控系统的使用对象也从最初少数的几个 SRE,扩大为更多的 DEVS,SRE。这时候,监控系统的容量和用户的“使用效率”成了最为突出的问题。

OpenFalcon 来自于小米的运维团队,在 2014 年初开发,大约开发了半年左右第一个版本上线,之后一直在线上稳定运行,后来我们成立了 OpenFalcon 社区。

现在有很多公司正在使用 OpenFalcon,像美团、金山云、京东金融、赶集、宜信、快网等。

BAT 这种大公司目前还没了解到是否使用 OpenFalcon,他们大多使用自研系统。

OpenFalcon 官网是 http://open-falcon.org/ ,文档地址是 http://book.open-falcon.org/ ,感兴趣的同学的可以进一步访问了解。

OpenFalcon 是在这样一个场景下产生,当时小米有三套 Zabbix,每套大约可以支撑 2,000 台机器左右。当时有 6,000 台机器,所以搭建了 3 套 Zabbix。另外内部还有 1 套业务监控系统,内部叫 PerfCounter,负责业务性能指标监控。

由于 4 套监控系统维护起来比较麻烦,于是就有了开发这么一个大一统的解决方案,也就是 OpenFalcon 的想法。

(高可用小编:看来牛人偷懒,永远是强大系统产生的源动力)

OpenFalcon 其他部分特性还包括:

水平扩展能力:支持每个周期上亿次的数据采集、告警判定、历史数据存储和查询

高可用:整个系统无核心单点,易运维,易部署,可水平扩展

开发语言: 整个系统的后端全部 golang 编写,portal 和 dashboard 使用Python 编写

OpenFalcon 在高并发场景的 7 种应对手段

1. 数据采集推拉抉择

对于监控系统来说,包含几个核心功能:数据采集、历史数据存储、报警,最后是绘图展示及数据挖掘。

数据采集

这一块我们希望做一个大一统的东西,因此要采集很多监控指标,比如 Linux 本身的监控指标,像 CPU、内存、网卡、IO 等,一些硬件指标,比如风扇的转速、温度等等,再加上一些开源软件如 MySQL,Nginx,OpenStack,HBase 等监控指标。

公司内部有很多产品线,每一个服务运行状况都希望采集到,比如中间层服务开了一些 RPC 端口,每一个 RPC 端口在服务过程中,latency 是多少,QPS 是多少,我们都希望了解到。

所以需要覆盖很多监控指标,监控团队刚开始只有两个人,不可能把所有监控指标都采集到——人力不行、技术水平也达不到。

所以我们需要共建监控数据,让专业的人干专业的事

DBA 同学对 MySQL 比较熟,那他们去采集 MySQL 相关指标,分布式团队同学去采集 HBase 或 Hadoop 等指标,网络组同学去采集交换机路由器指标。

而作为监控团队要做的,就是制订一个规范。制定一个数据进来的机制,然后公司开发人员和运维人员按照规范去推送数据。

数据收集没有采用拉的方式,因为太多数据源,作为一个 Client 去连接源端,采集数据,再关闭连接,会产生很多 time_wait 状态连接,其吞吐必然是不高,所以要把自己做成 Server 端。现在每个周期有 1 亿多条数据。

这个周期简单解释一下,监控数据是一个持续上报,比如 Linux 一些基本监控项一分钟一次采集之后上报,下一分钟再采集再上报。一些像业务监控有 3 分钟的,有 5 分钟的。

因此周期是指数据量是 5 分钟以内,有上报动作的数据。

2. 中间节点快速转发与容错

数据有很多要 Push 到 Server 端,Server 端最前端组件是 Transfer,Transfer 接收到数据后要把它转发给后面两个组件,一个是 Graph(用来做绘图),一个是 Judge(用来做报警判断)。

首先对每一个后端实例创建一个 Queue 出来,比如 20 个 Graph 实例,60 个 Judge 实例,为每个实例创建一个 Queue,每个 Transfer 内存中有 80 个 Queue,

当一条监控数据过来之后,需要判断数据发到哪一个实例,然后放到这个实例对应 Queue。Transfer 接收数据 RPC 逻辑非常简单,数据拿到后放到 Queue 就立马返回。

Agent 到 transfer,transfer 到 judge,judge 到 Redis,Redis 到 alarm,报警链路比较长,如果希望尽快触发报警,链路每一个环节都希望比较快速处理,用 Queue 方式,Transfer 吞吐特别大,不会拖慢整个链路。

数据放在 Queue 中有一个问题,如果不及时转发,比如后端 load 比较高或者挂了,Queue 数据就会堆积,超过内存就会 crash,所以做了一个简单保护措施,将 Queue 设成定长,超过 Queue 长度,数据就放不进来。

数据 Push 到 Queue 后,有一个专门对 Queue 读入 worker,读到了之后转发。转发这个动作由一堆写 worker 完成,这里 worker 是指 goroutine,如此这般,一堆 goroutine 协同工作,来提高转发性能。

3. 一致性哈希分片提高吞吐容量

一致性哈希分片

这么多数据上来不可能由一台机器处理,因此做了一个数据分片,即一个机器只处理一部分数据,报警数据是由 Judge 做分片,绘图数据由 Graph 做分片,这两个都是使用一致性哈希。

一致性哈希分片有一个问题,就是扩缩容比较麻烦,数据打到某个 judge 之后,就会一直打到这个 judge。

当列表发生变化时候,由于一致性哈希算法,原来打到一台 judge 实例的数据就会打到另外一个 Judge 实例,所以 Judge 一定要保证没有状态,这样才能方便扩缩容。

状态标记

其实说它没有状态也不太合适,judge 内存里也存了几个点。比如某一台机器 cpu.idle 数据上来之后,连续 3 ~ 5 次达到一个特定阀值才去报警,而不是达到阀值立马报警。像 CPU,是一直都忙碌状态才去报警,Judge 判断的时候它是判断多个点。

产生报警之后,还有一些后续处理,比如对这个报警事件做一个判断,上次产生事件是什么状态,他是第几次报警,是不是达到了最大报警次数不能再报了,避免重复报警给处理人员造成干扰。一般大家都设置报警 3 次。

因此报警事件需要记录一个状态,标记是否健康,如果有问题,记录当前是第几次。Judge 状态存在数据库,虽然 1 亿条数据上来,实际上报警数据不多,可能只有 10 万条数据级别,因此可以存入数据库,这样 Judge 就剥离了报警事件状态。

虽然 Judge 内存当中还存一些前面所说数据状态,但也不是一个大问题。因为监控数据是源源不断上来,即使丢掉一些状态,新的 Judge 实例很快就会又填充数据。

扩容

一致性哈希对扩容不是很友好,比如 20 台 Graph 的机器如果变成 40 台,势必有老的 Graph 实例的一部分数据打到新的 Graph 上,因此我们做了一个自动数据迁移。

这个自动迁移是由于一致性哈希造成的问题,如果用映射表做分片,在映射表这统一维护数据和 graph 实例对应关系,扩容就简单很多。

4. 数据与策略快速匹配

当数据上来后,要判断这个数据是不是触发了报警策略,策略有很多种,比如我们现在系统中有几千上万条。

上来一条数据之后,要判断这个数据跟哪一条策略相关,然后再判断阀值是多少,是不是要触发报警。

我们选择去数据库同步所有报警策略列表,因为策略列表整个公司不会特别多,虽然数据量多,但是策略不是特别多,比如只有几千条或者几万条。

在策略数据库做了一个简单索引,方便数据上来之后快速定位策略,然后根据阀值看是否要需要报警处理。

为了加快策略判定,需要知道近期一些历史数据,历史存在 Judge 内存中,这样获取速度相对来说快一些。

第一个版本做测试时没有放到内存中,当时用了 56 个 Redis 实例,每个实例大概有 3,000 QPS,那时上的量不大,仍然达到了一个这么高的 QPS。由于并发高,把数据放到 Redis 中并不是一个很好的解决办法,后来就把它放到了Judge 内存里。这样处理起来就快很多。

报警状态也存在 Judge 内存及 DB。如果每个报警判断都去访问 DB 比较耗时,所以就加载到 Judge 内存,相当与缓存的机制,内存没有再去 DB 加载。在 Judge 重启的时候,内存没有数据,这时报警状态的判断需要去 DB 加载,然后再读到内存里。

5. 数据延迟写入,减少 RRD 文件打开次数

时间序列数据自动归档

报警数据存储采用 RRD,RRD 比较有名。大量开源监控软件存储时间序列数据都选用 RRD。

RRD 最大的优点是自动归档。监控数据有一个特点,不需要关心历史监控数据具体的值,只需知道当时的趋势即可。

最新 6 个小时可能有看原始值需求。但是最近 3 天 及 1 个月原始值的需求就很小。而且那个点特别多,一分钟 1 个点,一天有 1440 个点。如果加载 1 个月浏览器不崩才怪。

由于对历史点只要能看到趋势就行。因此监控数据特别需要归档功能。比如一个小时的数据归档为一个点,RRD 可以帮我们做这个事情。

RRD 优化:延迟合并写入

RRD 默认操作性能比较差,它的逻辑是打开 RRD 文件,然后去 seek,write,seek,write,最后 close 文件句柄。一个监控指标对应一个 RRD 文件,比如这个机器上面处理了大约 200 万个监控指标,实际上就有 200 万个 RRD 的文件。

每次写入数据的时候,打开这个 RRD 文件,读头部信息,包含一些 meta 信息,记录了归档策略、数据类型之类数据,然后去做写入操作。

如果每一个数据上来之后都做这个操作,RRD 读写会造成特别大的 IO。虽然现在磁盘都是 SSD,但由于 IO 居高不下,于是做了一个延迟写入的优化

现在数据都是 1 分钟上报一次,可能有的数据 10 秒或 30 秒上报一次。并不是上报上来立马打开 RRD 文件写入,而是等 10 分钟或者 30 分钟,在内存中缓存一段时间,缓存 30 个点或者 60 个点,一次性把文件打开,一次性写入再关闭,这样可以减少 RRD 文件打开的次数,让 IO 降低一些。

我们根据缓存的时间,比如缓存半个小时,再按照半个小时时间长度来做数据打散,比如现在半个小时是 1,800 秒,举个例子,1,800 秒把它做成 1,800 个槽位,每个数据上来之后平均分散到 1,800 个槽位当中,写的时候慢慢地写,这样做了一个打散操作,避免让 IO 出现一些峰值。

6. 报警事件按优先级入队列缓冲

报警事件量通常不是特别大,但个别时候会比较大——触发一些大面积报警的时候,比如某一个核心交换机挂掉了,会产生特别大的报警。

比如服务依赖于上游某几个服务,上游服务挂了,所有下游服务都报警。如果核心交换机挂了,很多核心服务都受影响,核心服务受影响之后,下游很多服务就产生报警,这样产生一个大面积报警。但通常情况下,报警量都是一个很平稳的一个量。

当大面积报警出现时,我们仍然是应用 Queue 机制,用 Queue 来抹平峰值。

报警的分级处理

我们将报警进行分级,从 P0、P1 一直到 P5,P0 最高。

基于优先级的分级策略

  • P0、P1 发短信发邮件,并且是收到报警立即发;
  • P2 这个级别发短信不是立即发,而是做一个报警合并;
  • P3、P4 那些低级别就做报警合并,同时不发短信,只发邮件。

我们对报警事件做一个分级,每一个级别就对应 Redis 里面一个Queue。利用 Redis BRPOP 简单实现按照优先级处理报警事件,即先处理 P0,再处理 P1、P2 顺序。

7. 通过限制 Worker 数目发送接口限流

系统本身可能链路比较长,每个链路都希望能够扛住高并发,实际上在系统开发过程当中,我们依赖于其他基础设施,比如内部有 SMTP 服务器来发送邮件,有短信通道来发送短信,但是这些接口扛不住很大的并发。

当要系统依赖调用几个并发能力较差的接口,最好做一个限流。

有一个专门的模块 Sender 发送报警邮件或者报警短信,它发送时候可以给它配置一个 Worker 数量,大家可以理解成最多有多少个线程来调用发送接口。这样就可以保护后端发送接口,不至于把它打挂。

总结

回到今天的议题“百万并发”,总结我们在设计 OpenFalcon 用到的技术。

1. 分片:一台机器抗不住就分成多台机器,数人云是一个 PaaS 平台,PaaS 平台很容易做扩容,原来三百台实例现在做三千台,在页面上按键按一下,10 秒就可以让 3000 台实例起来,做 3000 个分片。

2. 队列:有时候产生一些峰值,我们不希望被峰值打垮,于是用队列做缓冲,这个系统有多个地方用到队列,比如 transfer 内存中构建了多条队列,报警事件使用 Redis 做队列服务。

3. 索引:索引可以加快查询速度。

4. 限流:后端的接口抗不住压力的时候会做限流。

这只是几个简单的、很普适的手段,没有炫耀那些高精尖、大家不太能理解及很难借鉴的东西,希望能给大家在应对高并发时候提供一些参考,谢谢大家!

文/来自高可用架构公众号「ArchNotes」


你目前的身份是游客,评论请输入昵称和电邮!