关于大型监控系统的高性能组件设计

      以前有幸参与过一个分布式监控系统开发,有一些所谓的设计和开发的经验,但苦于公司的内部系统无法开源,所以也就藏着掖着。   前两天跟一途牛网、饿了么的朋友胡扯了监控系统的事,这尼玛正好点燃了我这高射炮,可以有个地好好释放了下。 记得13年的时候,跟金山猎豹的斌哥聊过一些设计方案,后来听他说也借鉴了一些思路,真假就不知道了。  趁着现在脑袋还清闲,开源的监控系统还没有注意这一片思路前,先把自己的经验共享出来。  这样,如果有其他公司朋友如果想实现高性能的监控,或者是扩展现存的监控性能瓶颈, 可以适当的参考下我这文章。  

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新.

http://xiaorui.cc/?p=3552

首先老生常谈一下分布式监控系统的简单流程. 


流程图:

           –>   register
agent   –>   proxy   –>   judge   –>   alarm   –>  store   –>  前端  

对于监控系统的瓶颈解决过程,我深有体会….   这里说个我经历过的事情,我以前公司的zabbix监控系统就管理了将近3w+服务器,包含云主机。 对的,是3w个主机.  结果跟你想的一样,确实经常出问题。 问题很是多样化,有mysql io的问题,有zabbix server性能问题,有proxy卡顿,报警不及时。 总之造成这些问题的原因是量级过大引起的。


那么后来是怎么解决的? 

mysql这一段做了分库分表,存储换成了ssd,数据库引擎也更换为tokudbd。 报警不再采用同步阻塞的方式,而是使用我开发的report系统来发送。 对于proxy中继服务挂了咋办? 明显是相关的客户端太多了,就做了一些分流,多开了几个proxy。 对于zabbix server的瓶颈是怎么解决的? 不好意思,没在代码层次解决。 是根据业务线拆分成到不同的zabbix里,也就是说多开了一组zabbix server。


那么然后呢?

确实解决了这些问题,那么为什么没有在代码层次解决?那么他的性能瓶颈在哪里?  我上面有说过,我有分布式监控系统的开发及优化的验,所以很容易想到,也能找到他的瓶颈,比如阈值判断回扫数据库, 数据没有聚合归档, 线程使用不合理等等…. 这本身是很不合理的设计。。。我自己也大范围的改过zabbix server代码,最后因为各种问题不得不放弃。  说实话真心没必要把时间耗尽在这zabbix里,zabbix的代码有些杂乱不是很好理解,好在我没花多少时间放在zabbix上。   大多数公司到了一定级别后,都会选择性的开发监控系统,我一直都觉得监控系统开发说难也难,说简单也简单,这是废话….   只要把几个瓶颈点和功能点想明白后,这都不是问题…    其实不管用是python,golang实现,都离不开对于监控系统的理解和优化。 

另外,zabbix其实也是个商业组织,他们本身有zabbix优化方案的,虽然没有说实现过程,但点出了他们使用了那些技术,比如redis,分布式任务队列…  

不扯了,说正题


对于监控的客户来说,你需要实现这么几个功能.

  • 注册主机,并发送心跳包
  • 收集客户端的各种数据,   系统类的数据可以从/proc拿到
  • 需要开启一个对localhost的接口,用户可以自定义区sender数据.
  • 预先设定工作线程数目,可根据任务的数量动态创建
  • 需要设计一个良好的任务队列,可以理解为任务生成器.
  • 定时去获取最新的监控列表及插件
    • 异步, 可以使用condition的方式通知另一个线程去更新列表及插件信息并更正running , wait 任务配对信息.
    • 同步, 工作线程拿到任务后, 可查看内存中该任务对应的状态(继续,停止,周期变化,有更新),再操作。
  • agent需要有一个schduler的角色,来收缩扩展任务的变更.
  • push逻辑一定要单独写,这样解耦的目的是,工作线程只需要收集数据就可以了. 
  • 收集监控数据一定要做好timeout的配置,对于/proc不需要,涉及到网络io及大范围扫描时,需加timeout.
  • 模块内部可实现reload及自省反射模式

对于proxy中继组件来说,他是减少服务端直接跟客户端建立链接,在多个数据中心场景下,每个节点都跟server端建立连接也不是个好主意。  强烈推荐大家在proxy端进行judge判断,然后把报警这个事告诉alarm,至于单条信息实时推送,归档汇总计算完了后,会伴随推松过去。   在proxy进行judege判断,解决了server不小的压力。


对于judge组件来说,他主要是实现判断客户端提交的数据是否达到了阈值条件,是则触发alarm.   judge系统不可能简单就判断一次,也肯定不能就判断一次,这样很容易就造成报警泛滥。 我们需要实现下面几个功能 

  • 简单判断
  • 时间区间次数判断
  • 多久内只发一次


在详细描述他之前,我们要知道judge的不合理会成为server端的一个性能瓶颈, 比如zabbix每次判断状态的时候都回扫数据库….  你完全可以把数据放到缓存里面,但你们肯定会问连续三次触发报警,怎么实现…  60秒内触发三次阈值怎么判断.    你可以使用redis sorted set来实现,把时间戳作为score,数值作为值. 

这里提一下服务注册机制,为什么需要服务注册机制?   当时开发这组件的主要目的是为了收集客户端的心跳,如果他没有心跳了,那么就说明他挂了或者是网络有异常.

我们举个例子,你监控流量,server 端收集到了后,可以进行阈值判断。 但是如果过了很久还是没有发送给你,你靠什么来判断阈值,或者是 得到他长时间没给我信息这个事件。    我们这里需要明确一个点, 在开发监控系统的时候,就要设计server端的timer事件只关心agent心跳,对于该客户端的内存,流量,负载都不会注册timer事件。  因为我只要知道你没来心跳,那么别的信息也过不来。 

服务注册还可以进行服务归类,对于业务项目来说,可以看到他所属的项目的服务树状态,也就是说可以看到项目的整体状态。 这里不限于agent级别,你可以让多个进程都注册上来,如果某个进程被oom了,那么你可以在前端的服务树里看到他的消失。 这里可以使用 zookeeper,etcd做服务注册, 我推荐使用etcd,虽然etcd没有zookeeper那种临时session的概念,但ttl timeout足够用了,另外etcd的源代码实现也够简单和高效,很是养眼。    我以前写过一些etcd的文章,有兴趣的朋友可以看看  http://xiaorui.cc/tag/etcd/  。  友情提示下,redis是没有那种expire watch机制,因为redis 过期key扫描力度很粗,在stackoverflow看过一个回复,说是在redis里把expire的扫描任务改成每秒执行,然后使用brpop事件的方式来达到watch,有些意思。 

alarm ,就是报警,没啥好说的,只需要跟judge解耦构建成异步方式就可以了,另外别像zabbix那样把事件放到mysql,这样多个线程去读取的时候,还要考虑数据安全问题。

  • 微信
  • 邮件
  • 短信
  • 电话


store,  简单说他是入库的模块,高级说法是归档聚合计算模块.  可想而知,高级说法明显复杂的多.  很多监控系统的前段为毛那么慢? 就算用了ssd之后虽然有明显提高,但瓶颈出现在mysql计算上。  比如5秒一个数据,你想看一个月的图表趋势,怎么算?很明显这要经过大量的临时的聚合计算。 我们不能怎么搞? 我们需要提前入库的时候,就要计算好聚合点。 比如一堆的5秒数据,我们按照他们当前的分钟数来聚合成15秒点,30秒的点,60秒的点….


监控系统的聚合归档怎么实现?


还是使用redis sorted set有序队列,每个主机的监控项目为一个队列,这个队列可以跟judge一样.   取一分钟,然后15 秒一组,30  一组,60秒一组,计算avg后入库。  


为什么不用45秒? 因为涉及到多个分钟段。 不好实现。


怕影响时间区间阈值判断,可以去前三分钟之前的一段数据。  每次都是 0- 三分前 !

那么最后归档的数据放在哪里, mysql ?   ElasticSearch , opentsdb, influxdb ?


mysql在量级不是很大的时候是没有问题,当出现性能瓶颈可以读写分离,再不行可以对历史表进行时间分表。 记得在逻辑里多加cache,如果条件可以的话,直接用ssd. 


influxdb我用过很深,在乐视的时候就在用,也看过部分的代码,因为是golang实现的,代码很是整洁,看代码的过程中也学到了架构上的设计。  最后我对他的印象是不堪大用,如果你的数据过500G,不管你使用leveldb,rocksdb,hyperdb都有些徒劳,性能缩水很厉害.  至于他的分布式? 我就不说了,我写过不少influxdb的文章,大家可以看看, http://xiaorui.cc/tag/influxdb/ 


大多数公司都会采用mysql,如果你的公司有Elasticsearch和hbase的基础服务,那么你可以在这基础上构建监控存储系统。  如果没有这些基础环境,单纯为了监控系统起这些服务,后期又扩展,不好说,看公司支持力度了。


我对于hbase和Elasticsearch都用了将今两年的时间, 不太推荐使用hbase,这里说明下我们不是直接使用hbase,而是使用在hbase之上封装的opentsdb.   opentsdb就是为了监控服务而存在的.   如果你的系统不是java开发的,那么操作hbase不外乎中间件的方法,像thrift server,opentsdb的tsd都可以算是代理,thrift提供的是最原始的hbase的方法,而opentsdb提供了类似监控模板的方法,又因为opentsdb的性能不弱,所以是有不少公司选择它,比如阿里, 当然人家那java党多,神也多,可以深度挖坑埋坑….


hbase本身是不支持二级索引的,他的索引只有rowkey行健,往往rowkey里含有主机的标识信息及时间信息,像这样该组监控数据产生了时序或者单调(递增/递减)行键,导致连续到来的数据会被分配到统一region中,一堆数据集中到一个region,虽然不能更用应用hbase的优势,但这样介绍了hbase之间的一些协作,像监控数据本身也没啥大量计算,另外使用hbase的时候,预先要设置region,要不然每次都要分裂分配region很是影响性能。  你每次从hbase想获取一个时间段数据,都是scan的过程,如果你想做一些各方面的统计,那就要写Mapreduce了。


我这里推荐大家使用ElasticSearch作为监控数据的存储,他的好处多多,首先他的查询性能没得说,可以像hbase那样横向扩展,可以像mysql那样多个字段查询,可以随意搭配字段进行查询,图标展现可以直接使用grafana,kibana,经过一年的使用发现,grafana真的很适合做监控系统的前端展现。   这时候对于你来说,只需要开发系统系统的管理页面就可以了,当然如果要规范化,尽量还是自己开发监控的前端。  不要试图修改grafana的前端源码,不容易呀,教训呀。


记得给query加缓存,以后数据可以从redis获取,需要控制好缓存的超时,对于一个小时之前的数据,可以缓存个10天,对于当前的数据可以选择性的缓存。


总的来说描述有些粗了, 有时间再好好细化下,由于篇幅原因没有聊到前端方面的优化,后期陆续更新。 


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

3 Responses

  1. 2016年11月3日 / 下午2:14

    经验之谈 O(∩_∩)O谢谢

  2. teemo.mo 2016年7月25日 / 上午10:11

    使用elasticsearch存放序列化数据 使用grafana进行图表展现使用django运维平台实现实现获取显示控制, 确认可行性后期的数据存储+数据展示+报警整合, 可以继续弄弄的.挤一挤, 差不多是这样子吧,我现在也弄,占个位置先.

  3. 膜拜 2016年7月21日 / 上午10:47

    应该加个赞助的链接,这样子更显得专业

膜拜进行回复 取消回复

邮箱地址不会被公开。 必填项已用*标注