1.监控基础
(1)为什么需要监控
系统拆分为微服务后,节点数量大大增加,导致需要监控的机器、网络、进程、接口调用数等监控对象的数量大大增加;同时,一旦发生故障,我们需要快速根据各类信息来定位故障。这两个目标如果靠人力去完成是不现实的。举个简单例子:我们收到用户投诉说业务有问题,如果此时采取人工的方式去搜集、分析信息,可能把几十个节点的日志打开一遍就需要十几分钟了,因此需要服务监控系统来完成微服务节点的监控。
监控的目标是及时发现系统的问题,并尽可能快地做出相应的动作,让系统一直处于健康的状态。监控,可以拆分为“监”和“控”分别理解,其含义恰好对应着两种主要手段,也就是“监视”和“控制”。比如说,生产环境出现了个 bug,怎样定位问题?这需要做好“监视”;发现问题根源后如何正确响应?这需要做好“控制”。
(2)不同层级的关注点
- 系统层:系统层主要是指CPU、磁盘、内存、网络等服务器层面的监控,这些一般也是运维比较关注的对象。
- 应用层:应用层指的是服务角度的监控,比如接口、框架、某个服务的健康状态等,一般是服务开发或框架开发人员关注的对象。
- 用户层:这一层主要是与用户、与业务相关的一些监控,属于功能层面的,大多数是项目经理或产品经理会比较关注的对象。
(3)监控维度
- 全局维度。从整体角度监控对象的的请求量、平均耗时以及错误率,全局维度的监控一般是为了让你对监控对象的调用情况有个整体了解。
- 分机房维度。一般为了业务的高可用性,服务通常部署在不止一个机房,因为不同机房地域的不同,同一个监控对象的各种指标可能会相差很大,所以需要深入到机房内部去了解。
- 单机维度。即便是在同一个机房内部,可能由于采购年份和批次的不同,位于不同机器上的同一个监控对象的各种指标也会有很大差异。一般来说,新采购的机器通常由于成本更低,配置也更高,在同等请求量的情况下,可能表现出较大的性能差异,因此也需要从单机维度去监控同一个对象。
- 时间维度。同一个监控对象,在每天的同一时刻各种指标通常也不会一样,这种差异要么是由业务变更导致,要么是运营活动导致。为了了解监控对象各种指标的变化,通常需要与一天前、一周前、一个月前,甚至三个月前做比较。
- 核心维度。业务上一般会依据重要性程度对监控对象进行分级,最简单的是分成核心业务和非核心业务。核心业务和非核心业务在部署上必须隔离,分开监控,这样才能对核心业务做重点保障。
(4)什么是好的监控系统
- 能发现异常:也就是要保障监控的召回率,监控体系首先要保障一定能发现异常,如果监控无法发现异常一切都没有意义。
- 快速发现异常:也就是要保障监控的时效性,监控和报警的响应速度直接影响故障的恢复速度。好的监控一定要保证快速发现问题。
- 快速定位问题:在快速发现问题的基础上,优秀的监控体系还应该能迅速定位问题的根源。不仅仅是识别问题,更重要的是能够指出问题的具体位置。
- 给出影响评估:对于大规模故障,监控体系不仅要能发现和定位问题,还应能提供准确的影响评估。这有助于决策者快速了解故障对业务的影响范围,从而制定相应的应对策略。
一个完善的监控系统,并不是 “报警很多很完善” 的系统,而是 “信噪比高,有故障就报 警,有报警就直指根因” 的监控系统。不应该仅仅因为 “某东西看起来有点问题” 就发出报警。 当报警出现得太频繁时,员工就会进入“狼来了” 效应,怀疑报警的有效性甚至忽略报 警。有的时候在报警过多的时候,甚至会忽略掉真实发生的故障。由于无效信息太多,分析 和修复可能会变慢,故障时间也会相应延长。高效的报警系统应该提供足够的信息,并且误 报率非常低。有故障就报警” 关注的是报警的覆盖率。如果我们通过客户报障或其他手段发现故障, 对于监控系统来说,就应该认为是一次监控事故。“有报警就直指根因” 关注的是报警的有效性和排障的效率。
2.如何为系统添加监控
从业务需要出发,从解决问题出发
(1)构建业务监控大盘
业务监控大盘的重要性在于它可以实时且直观地帮助我们去判断当前业务的整体情况。如果大盘稳定就说明这个时候没有大的故障,如果大盘不稳定,说明这个时候已经有故障或者异常出现了。
为了让大盘看上去更清晰,建议给每个功能创建一个面板。在设计这些面板的时候,首先需要关注的是请求量,重点关注同环比,可以用红色、蓝色和绿色线条表示今天、昨天、上周。其次是成功率、容量、P99 响应时间,这里我们只需要关注实时数据就可以了,这样做能够确保监控的时效性,同时也能看到同环比。
(2)细化及拆分核心指标
对于关键指标,我们要确保监控的准确性和及时性。这里我们对核心指标进一步细化到子领域,并添加报警策略。这样我们能够在一分钟内迅速判断核心指标是否存在异常波动。
(3)梳理和细化核心链路
寻找业务请求的主干和支流。主干就是核心节点和核心链路,支路就是一些非核心的调用和服务。针对主干和支流采用不同的监控方式,优先监控主干
(4)统一添加基础指标
在完成对核心指标和关键链路的监控后,我们需要给非核心指标和非核心的服务添加监控。我们强调监控指标的全面性,对于任何发现的遗漏都要及时补充,包括功能可用性、服务调用、CPU 利用率、硬件故障、变更记录等多个方面。
3.监控系统的典型架构组成
- 采集器:用于收集监控数据,业界有不少开源解决方案,大同小异,总体分为推拉两种模式,各有应用场景。Telegraf、Exporters 用得最广泛,Grafana-Agent 和 Categraf 是后来者,当然还有 Datadog-Agent 这种商业解决方案,我的建议是优先考虑 Categraf,相对而言,它使用起来更加便捷。如果有些场景 Categraf 没有覆盖,可以考虑辅以一些特定的 Exporter。
- 时序库:用于存储时序数据,是一个非常内卷的行业,有很多开源方案可供选择。如果规模比较小,1000 台机器以下,通常一个单机版本的 Prometheus 就够用了。如果规模再大一些,建议你考虑 VictoriaMetrics,毕竟架构简单,简单的东西可能不完备,但是出了问题容易排查,更加可控。其中TDEngine 姑且可以看做是国产版 InfluxDB,GitHub 的 Star 数上万,针对物联网设备的场景做了优化,性能很好,也可以和 Grafana、Telegraf 整合,对于偏设备监控的场景,TDEngine 是个不错的选择。
- 告警引擎:用于做告警规则判断,生成告警事件。这是监控系统的一个重要组成部分,通常是基于固定阈值规则来告警。当然,随着时代的发展,也有系统支持统计算法和机器学习的方式做告警预判,我觉得是可以尝试的。AiOps 概念中最容易落地,或者说落地之后最容易有效果的,就是告警引擎。不过 Google SRE 的观点是不希望在告警中使用太多 magic 的手段,这个就见仁见智了。
- 数据展示:用于渲染展示监控数据。最常见的图表就是折线图,可以清晰明了地看到数据变化趋势,有些人会把监控大盘配置得特别花哨,各种能用的图表类型都用一下,这一点我不敢苟同,我还是觉得实用性才是最核心的诉求。很多监控系统会内置看图功能,开源领域最成熟的就是 Grafana,如果某个存储无法和 Grafana 对接,其流行性都会大打折扣。
4.监控系统原理
- 数据采集:收集到每一次调用的详细信息
- 数据传输:将收集到的数据传输给数据处理中心进行处理
- 数据处理:按照服务的维度进行聚合,计算出不同服务的请求量、响应时间以及错误率等信息并存储起来
- 数据展示:通过接口或者Dashboard 的形式对外展示服务的调用情况
(1)数据采集
通常有两种数据收集方式:
- 服务主动上报,这种处理方式通过在业务代码或者服务框架里加入数据收集代码逻辑,在每一次服务调用完成后,主动上报服务的调用信息。调用缓存、数据库的请求量会比较高,一般会单机也会达到每秒万次,如果把每次请求耗时都发送给监控服务器,那么,监控服务器会不 堪重负。所以在埋点时,先做一些汇总。比如,每隔 10 秒汇总这 10 秒内, 对同一个资源的请求量总和、响应时间分位值、错误数等,然后发送给监控服务器。
- 代理收集,这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。
无论哪种数据采集方式,首先要考虑的问题就是采样率,也就是采集数据的频率。采样率决定了监控的实时性与精确度,一般来说,采样率越高,监控的实时性就越高,精确度也越高。但采样对系统本身的性能也会有一定的影响,尤其是采集后的数据需要写到本地磁盘的时候,过高的采样率会导致系统写入磁盘的 I/O 过高,进而会影响到正常的服务调用。所以设置合理的采用率是数据采集的关键,最好是可以动态控制采样率,在系统比较空闲的时候加大采样率,追求监控的实时性与精确度;在系统负载比较高的时候减小采样率,追求监控的可用性与系统的稳定性。
(2)数据传输
数据传输最常用的方式有两种:
- UDP 传输,这种处理方式是数据处理单元提供服务器的请求地址,数据采集后通过 UDP协议与服务器建立连接,然后把数据发送过去。
- Kafka 传输,这种处理方式是数据采集后发送到指定的 Topic,然后数据处理单元再订阅
无论采用哪种传输方式,数据格式都十分重要,尤其是对带宽敏感以及解析性能要求比较高的场景,一般数据传输时采用的数据格式有两种:
- 二进制协议,最常用的就是 PB 对象,它的优点是高压缩比和高性能,可以减少传输带宽并且序列化和反序列化效率特别高。
- 文本协议,最常用的就是 JSON 字符串,它的优点是可读性好,但相比于 PB 对象,传输占用带宽高,并且解析性能也要差一些。
(3)数据处理
数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度:
- 接口维度聚合,这个维度是把实时收到的数据按照接口名维度实时聚合在一起,这样就可以得到每个接口的实时请求量、平均耗时等信息。
- 机器维度聚合,这个维度是把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。
聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种:
- 索引数据库,比如 Elasticsearch,以倒排索引的数据结构存储,需要查询的时候,根据索引来查询。
- 时序数据库,比如 OpenTSDB,以时序序列数据的方式存储,查询的时候按照时序如1min、5min 等维度来查询。
(4)数据展示
数据展示是把处理后的数据以 Dashboard 的方式展示给用户。数据展示有多种方式,比如曲线图、饼状图、格子图展示等。
5.Pull or Push
基于Pull类型的监控系统顾名思义是由监控系统主动去获取指标,需要被监控的对象能够具备被远端访问的能力;基于Push类型的监控系统不主动获取数据,而是由监控对象主动推送指标。两种方式在非常多的地方都有区别,对于监控系统的建设和选型来说,一定要事先了解这两种方式各自的优劣,选择合适的方案来实施,否则如果盲目实施,后续对监控系统的稳定性和部署运维代价来说将是灾难性的。
(1)原理与架构对比
Pull模型数据获取的核心是Pull模块,一般和监控的后端一起部署,例如Prometheus,核心组成包括:
- 服务发现系统,包括主机的服务发现(一般依赖于公司内部自己的CMDB系统)、应用服务发现(例如Consul)、PaaS服务发现(例如Kubernetes);Pull模块需要具备对这些服务发现系统的对接能力
- Pull核心模块,除了服务发现部分外,一般使用通用协议去远端拉取数据,一般支持配置拉取间隔、超时间隔、指标过滤/Rename/简单的Process能力
- 应用侧SDK,支持监听某个固定端口来提供被Pull的能力
- 由于各类中间件/其他系统不兼容Pull协议,因此需要开发对应的Exporter的Agent,支持拉取这些系统的指标并提供标准的Pull接口
Push模型相对比较简单:
- Push Agent,支持拉取各类被监控对象的指标数据,并推送到服务端,可以和被监控系统耦合部署,也可以单独部署
- ConfigCenter(可选),用来提供中心化的动态配置能力,例如监控目标、采集间隔、指标过滤、指标处理、远端目标等
- 应用侧SDK,支持发送数据到监控后端,或者发送到本地Agent(通常是本地Agent也实现一套后端的接口)
纯粹从部署复杂性上而言,在中间件/其他系统的监控上,Pull模型的部署方式太过复杂,维护代价较高,使用Push模式较为便捷;应用提供Metrics端口或主动Push部署代价相差不大。
(2)Pull的分布式解决方案
在扩展性上,Push方式的数据采集天然就是分布式的,在监控后端能力可以跟上的时候,可以无限的横向扩展。相比之下Pull方式扩展较为麻烦,需要:
- Pull模块与监控后端解耦,Pull作为Agent单独部署
- Pull Agent需要做分布式的协同,一般最简单是做Sharding,例如从服务发现系统处获取被监控的机器列表,对这些机器进行Hash后取模Sharding来决定由哪个Agent来负责Pull。
- 新增一个配置中心(可选)用来管理各个PullAgent
这种分布式的方式还是有一些问题:
- 单点瓶颈还是存在,所有的Agent都需要去请求服务发现模块
- agent扩容后,监控目标会变化,容易产生数据重复或缺失
(3)监控能力对比
①监控目标存活性
存活性是监控所需要做的第一件也是最基础的工作,Pull模式监控目标存活性相对来说非常简单,直接在Pull的中心端就知道能否请求到目标端的指标,如果失败也能知道一些简单的错误,比如网络超时、对端拒绝连接等。
Push方式相对来说就比较麻烦,应用没有上报可能是应用挂了,也可能是网络问题,也可能是迁移到了其他的节点上了,因为Pull模块可以和服务发现实时联动,但Push没有,所以只有服务端再和服务发现交互才能知道具体失败的原因。
②数据齐全度计算
数据齐全度这个概念在大型的监控系统中还是非常重要的,比如监控一千个副本的交易应用的QPS,这个指标需要结合一千个数据进行叠加,如果没有数据齐全度的概念,若配置QPS相比降低2%告警,由于网络波动,超过20个副本上报的数据延迟几秒,那就会触发误报。因此在配置告警的时候还需要结合数据齐全度数据进行综合考虑。
数据齐全度的计算也一样是依赖于服务发现模块,Pull方式是按照一轮一轮的方式进行拉取,所以一轮拉取完毕后数据就是齐全的,即使部分拉取失败也知道数据不全的百分比是多少;而Push方式由每个Agent、应用主动Push,每个客户端的Push间隔、网络延迟都不一样,需要服务端去根据历史情况计算数据齐全度,相对代价比较大。
③短生命周期/Serverless应用监控
在实际场景中,短生命周期/Serverless的应用也有很多,尤其是对成本友好的情况下,会大量使用Job、弹性实例、无服务应用等,例如渲染型的任务到达后启动一个弹性的计算实例,执行完毕后立马销毁释放;机器学习的训练Job、事件驱动的无服务工作流、定期执行的Job(例如资源清理、容量检查、安全扫描)等。这些应用通常生命周期极短(可能在秒级或毫秒级),Pull的定期模型极难去监控,一般都需要使用Push的方式,由应用主动推送监控数据。
为了应对这种短生命周期的应用,纯Pull的系统都会提供一个中间层(例如Prometheus的Push Gateway):接受应用主动Push,再提供Pull的端口给监控系统。但这就需要额外多个中间层的管理和运维成本,而且由于是Pull模拟Push,上报的延迟会升高而且还需要即使清理这些立即消失的指标。
④灵活性与耦合度
从灵活性上来讲,Pull模式稍微有一些优势,可以在Pull模块配置到底想要哪些指标,对指标做一些简单的计算/二次加工;但这个优势也是相对的,Push SDK/Agent也可以去配置这些参数,借助于配置中心的存在,配置管理起来也是很简单的。
从耦合度上讲,Pull模型和后端的耦合度要低很多,只需要提供一个后端可以理解的接口即可,具体连接哪个后端,后端需要哪些指标等不用关心,相对分工比较明确,应用开发者只需要暴露应用自己的指标即可,由SRE(监控系统管理者)来获取这些指标;Push模型相对来说耦合度要高一些,应用需要配置后端的地址以及鉴权信息等,但如果借助于本地的Push Agent,应用只需要Push本地地址,相对来说代价也并不大。
(4)Pull or Push如何选型
目前开源方案,Pull模式的代表Prometheus的家族方案(之所以称之为家族,主要是默认单点的Prometheus扩展性受限,社区有非常多Prometheus的分布式方案,比如Thanos、VictoriaMetrics、Cortex等),Push模式的代表InfluxDB的TICK(Telegraf, InfluxDB, Chronograf, Kapacitor)方案。这两种方案都有各自的优缺点,在云原生的大背景下,随着Prometheus在CNCF、Kubernetes带领下的大火,很多开源软件都开始提供Prometheus模式的Pull端口;但同时还有很多系统本身设计之初就难以提供Pull端口,这些系统的监控相比而言使用Push Agent方式更为合理。
而应用本身到底该使用Pull还是Push一直没有一个很好的定论,具体的选型还需要根据公司内部的实际场景,例如如果公司集群的网络很复杂,使用Push方式较为简单;有很多短生命周期的应用,需要使用Push方式;移动端应用只能用Push方式;系统本身就用Consul做服务发现,只需要暴露Pull端口就可以很容易实施。
所以综合考虑情况下对于公司内部的监控系统来说,应该同时具备Pull和Push的能力才是最优解:
- 主机、进程、中间件监控使用Push Agent;
- Kubernetes等直接暴露Pull端口的使用Pull模式;
- 应用根据实际场景选择Pull or Push;
6.监控术语
(1)监控指标
监控指标是指数值类型的监控数据,比如某个机器的内存利用率,某个 MySQL 实例的当前连接数,某个 Redis 的最大内存上限等等。
①全局唯一字符串作为指标标识
监控指标通常是一个全局唯一的字符串,如host.10.2.3.4.mem_used_percent,这个字符串中包含了机器的信息,也包含了指标名,可以唯一标识一条监控指标。虽然这种方式一目了然,非常清晰,但是缺少对维度信息的描述,不便于做聚合计算。
{ "name": "host.10.2.3.4.mem_used_percent", "points": [ { "clock": 1662449136, "value": 45.4 }, { "clock": 1662449166, "value": 43.2 }, { "clock": 1662449196, "value": 44.9 }, { "clock": 1662449226, "value": 44.8 } ]
②标签集的组合作为指标标识
mysql.bytes_received 1287333217 327810227706 schema=foo host=db1mysql.bytes_sent 1287333217 6604859181710 schema=foo host=db1mysql.bytes_received 1287333232 327812421706 schema=foo host=db1mysql.bytes_sent 1287333232 6604901075387 schema=foo host=db1mysql.bytes_received 1287333321 340899533915 schema=foo host=db2mysql.bytes_sent 1287333321 5506469130707 schema=foo host=db2
(2)指标类型
- Gauge:测量值类型,可正可负,可大可小,关注当前值
- Counter:单调递增的值,关注变化量/变化率
- Histogram:直方图类型,主要用于描述延迟分布
- Summary:摘要类型,典型用法是在客户端计算延迟分布位
- 类型扩展知识:用途-便于埋点,存储层面压根不识别类型