转转监控系统的内部原理及实践 审核中
一、背景
转转早期的监控系统 zzmonitor 是纯自研的,其数据上报方式比较简单,有且仅有四种数据上报方式:SUM、MAX、MIN、AVG。示例如下:
数据在客户端会每分钟做一次聚合,并以异步、批量的方式发送到 zzmonitor 服务端。另外,zzmonitor 的存储选型是 MySQL,分了 128 张表也仅能支持 7 天的数据存储。

以聚合方式来上报数据的目的是为了减少监控系统的数据存储量,但是同时也牺牲了太多,随着转转业务的发展,zzmonitor 的弊端也逐渐显露:
功能少,业务反馈大
仅提供这四个函数,无法监控 QPS、TP99 等。
API 设计不合理
以聚合方式来上报数据不够灵活,同样的数据,业务需要按照聚合方式多次上报;同时,部分数据无法二次加工,如:只能监控 1 分钟的平均值,无法监控到一天的平均值。
架构设计不合理
监控数据通常与时间强相关,使用 MySQL 存储性能差,数据压力也较大。存储数据常适合存储于时序数据库,时序数据库会带来良好的读写性能与数据压缩比。
维护 &开发成本高
随着业务量的上升,系统的很多地方出现了性能瓶颈,需要人持续的维护、排查问题。新功能的迭代也需要持续投入人力。
另外,除了给业务使用的 zzmonitor,运维层面还有各式各样的监控系统,如:Falcon、夜莺、ZABBIX、Prometheus 等,这给业务线同学使用上带来一定的困惑。
二、调研选型
基于以上背景,我们决定落地一套全新的立体式监控系统,能够集业务服务、架构中间件、运维资源于一体,简单易用,同时便于维护。落地前期主要针对 Cat、夜莺、Prometheus 做了一部分调研选型。
总体来说,Cat 更适合链路监控,夜莺更像一个简化版的 Prometheus+Grafana,而 Prometheus 拥有非常灵活的 PromQL、完善的 Exporter 生态,我们最终选择了 Prometheus。
三、Prometheus 能力
3.1 生态模型
Prometheus 自带一个单机的 TSDB,他以 pull 模式抓取指标,被抓取的目标需要以 Http 的方式暴露指标数据。对于业务服务,服务需要将地址注册到注册中心,Prometheus 做服务发现,然后再做指标抓取。

Prometheus 拥有非常完善的 Exporter 生态,大部分我们使用的中间件都有成熟的 Exporter,利用这些 Exporter,我们可以快速的搭建起监控体系。

Prometheus 的黄金搭档 Grafana 在可视化方面表现也十分不错,Grafana 拥有非常丰富的面版、灵活易用。以下是我们落地的实际效果图。

Prometheus 从客户端角度区分出了几种指标类型:Counter、Gauge、Histogram、Summary(转转内部不建议使用)。
3.2 Counter
Counter 是一个只增不减的计数器,Prometheus 抓取的是 Counter 当前累计的总量。常见的如 GC 次数、Http 请求次数都是 Counter 类型的监控指标。
以下是 Counter 的样本数据特点,我们可以在查询时计算出任意一段时间的增量,也可以计算任意一段时间的增量/时间,即 QPS。

如果服务器重启,Counter 累计计数归零,Prometheus 还能计算出准确的增量或 QPS 吗?Prometheus 称这种情况为 Counter 重置,Counter 在重置后总是从 0 开始,那么根据这个假设,在给定的时间窗口计算增量时,只需将重置后的样本值叠加上重置前的值,以补偿重置,就像重置从未发生过一样。

3.3 Gauge
Gauge 是个可增可减的仪表盘,Prometheus 抓取的是 Gauge 当前时刻设置的值。常见的如内存使用量、活跃线程数等都是 Gauge 类型的监控指标。
以下是 Gauge 类型的样本数据特点,我们一般不做二次计算,直接展示 Prometheus 抓取的原始值。

3.4 Histogram
Histogram 通常用于数据分布统计,Histogram 需要定义桶区间分布,根据用户上报的数据,来决定具体落到哪个桶内。
以下是部分源码,各个桶内存储上报的总次数,Prometheus 抓取的是各个桶的当前状态。
基于各个时刻的桶内数据,我们可以计算出任意一段时间的数据分布情况。如下为例,我们可以利用桶内数据计算出 10:01~10:02 的数据分布情况。
这个计算结果可视化后可能如下图所见,实际意义可能是一天的 RPC 调用耗时分布。基于这个计算结果,还可以计算出 TP 值,即 n%的值都不高于 x。

如果我们每个时间点都计算一次分布,我们就得到了这样的面板:分布折线图
。

利用这个数据,我们还可以换一种表现形式:热力图
。热力图每个时刻都是一个直方图,热力图通过颜色深浅区分出数据分布的占比。

Histogram 除了 buckets,还记录了 sum(上报数据的总值)、count(上报数据的总次数)。我们通过一段时间内 sum 的增量/count 的增量,即可算出上报数据的平均值。
3.5 多维标签
Prometheus 的指标可以定义多个标签,比如对于以下指标:
数据上报后,类比 MySQL 表,表名为 http_request,某一时刻的表数据如下:
有了这些结构化的数据,就可以在任意维度进行查询和聚合。比如,查询 uri=/addGoods 的数据;再比如,按照 method 维度做 SUM 聚合,查看总数。
3.6 误差
Prometheus 在设计上就放弃了一部分数据准确性,得到的是更高的可靠性,架构简单、数据简单、运维简单、节约机器成本与人力成本。通常对于监控系统,数据拥有少量的误差是可以接受的。
如计算增量时,给定的时间窗口中的第一个和最后一个样本,永远不会与真实的采样点 100%重合,Prometheus 会做数据线性外推,估算出对应时间的样本值。

四、架构设计
4.1 远端存储
Prometheus 自带一个单机的 TSDB,显然这是远远不够的,好在 Prometheus 提供了存储的扩展,自定义了一套读写协议。当发送读写事件时,Prometheus 会将请求转发到三方存储中。

我们经过选型对比,最终选定 M3DB 作为远端存储。M3DB 是 Uber 开源的专为 Prometheus 而生的时序数据库,拥有较高的数据压缩比,同时也是夜莺强烈推荐的三方存储。

M3 Coordinator(M3 协调器) :M3 协调器是一个协调 Prometheus 和 M3DB 之间的读写的服务。它是上下游的桥梁,自身无状态。
M3DB(存储数据库) :M3DB 是一个分布式时间序列数据库,是真正的存储节点,提供可扩展的存储和时序索引。
M3 Query (M3 查询引擎) :M3DB 专用查询引擎,兼容 Prometheus 查询语法,支持低延迟实时查询和长时间数据的查询,可以聚合更大的数据集。
M3 Aggregator (M3 聚合器) :M3 聚合器是一个专用的指标聚合器,会保证指标至少会聚合一次,并持久化到 M3DB 存储中,可用于降采样,提供更长久的存储。
4.2 官方路线
以下是完全以官方路线设计出的系统架构,转转的线上环境比较复杂,并没有完全容器化,我们需要单独引入注册中心。各个业务的服务在启动时将地址注册到注册中心,Prometheus 再从注册中心做服务发现,然后再做指标的拉取,最终将数据推送到 M3DB 中。

这套架构模型比较复杂,主要体现在以下几点:
架构复杂、层级太深、模块太多,运维成本高
客户端较重,需要引入注册中心
Prometheus 的作用仅仅是个指标抓取的中转,即没必要也容易增加问题点,还需要考虑分片的问题
4.3 客户端设计
面对以上架构,我们做了一些思考,既然 Prometheus 可以将拉取到的指标数据推送到三方存储,为什么我们不能在业务服务上绕过 Prometheus 而直接推送到三方存储呢?
于是,我们调研了 Prometheus 远端存储协议,改进了客户端的设计。客户端遵循 Prometheus 远端存储协议(ProtoBuf + Http),并以异步、批量的方式主动将指标推送到 M3DB。
改进之后客户端非常轻量,近乎零依赖,并且完全兼容原生客户端用法,因为我们只修改了数据上报的内核逻辑,对 API 无任何修改。

4.4 最终架构
最终,我们的系统架构如下图所示,对于业务服务,我们会通过客户端主动将指标推送到 M3DB 中;而对于各个中间件,由于服务 IP 变动不频繁,继续沿用 Exporter 生态。
这样,我们省去了注册中心,省去了服务发现,也省去了抓取分片。对于业务服务我们甚至省去了 Prometheus,我们只用了 Prometheus 的协议,而没有使用 Prometheus 的服务。

4.5 性能测试
由于 Prometheus 需要在客户端埋点上报数据,我们对客户端的性能也做了重点测试。
<table style="text-align: center"><tr><td></td><td></td><td>Counter</td><td>Gauge</td><td>Histogram(14 个桶)</td></tr><tr><td rowspan="3">QPS</td><td>单线程</td><td>4300W</td><td>4100W</td><td>2600W</td></tr><tr><td>10 线程</td><td>3030W</td><td>2600W</td><td>3030W</td></tr><tr><td>50 线程</td><td>2940W</td><td>2940W</td><td>2380W</td></tr><tr><td rowspan="3">平均耗时</td><td>单线程</td><td>23ns</td><td>24ns</td><td>38ns</td></tr><tr><td>10 线程</td><td>33ns</td><td>38ns</td><td>33ns</td></tr><tr><td>50 线程</td><td>34ns</td><td>34ns</td><td>42ns</td></tr><tr><td rowspan="3">内存</td><td>100 个标签</td><td>20KB</td><td>20kb</td><td>77kb</td></tr><tr><td>200 个标签</td><td>39KB</td><td>39KB</td><td>153KB</td></tr><tr><td>500 个标签</td><td>96KB</td><td>96KB</td><td>381KB</td></tr></table>QPS 不存在瓶颈,千万级以上,耗时纳秒级别,内存占用取决于标签数量,总体来说资源消耗比较小。
五、落地实现
5.1 Grafana 规划
第一个规划是多环境统一,不管线上、沙箱、测试环境,都统一使用同一个 Grafana,避免维护多套面板、避免业务记多套地址。
第二个是维度划分,我们目前对 Dashboard 划分四个维度:
业务大盘
我们每个研发同学、每个部门都会负责很多服务,业务大盘摘取了大家负责服务的重点指标,提供一个全局的视角。
业务服务
这里是以业务服务为维度,拥有我们内置的各种监控面板,是一个 All In One 的视角。
架构组件
以各个组件为维度,监控所有服务整体使用情况,如线程池监控、日志监控。
运维组件
以运维视角来看到各个中间的监控,如 Redis、Nginx 等。

5.2 数据打通
对于监控数据,尤其是业务指标监控,有些数据是比较敏感的,我们打通公司内部用户系统,就可以实现认证、鉴权权限控制。除此之外,我们还打通了服务信息系统、服务权限系统。

5.3 企业微信认证
转转的内部系统统一走的企业微信扫码认证,我们基于 Grafana 的一个小功能 Auth Proxy 实现了企业微信认证。简单来说,当开启了 Auth Proxy,访问 Grafana 的请求如果携带了 Header 用户名,Grafana 就认为是对应用户访问了系统。
对于转转,当用户访问 grafana.xxx.com 时,Nginx 会做拦截跳转企业微信扫码认证,认证之后由 Nginx 种入对应 Header 访问 Grafana。
有些同学看到这里可能会担心会不会有安全问题,无需担心。
Header 是由 Nginx 层种入,不会泄露到前端;
如果走域名访问,Nginx 会做统一拦截认证;
如果走 IP 访问,我们封禁了非 80 端口的 IP 访问。

5.4 组件面板自动初始化
转转的架构中间件基于客户端做了各种埋点,包括不限于:JVM、线程池监控、数据库连接池、Codis、Web 监控、日志监控、Docker 监控等。
Grafana 的 Dashboard 是一整个大 JSON,我们只需提前定义好我们的模板,向 Dashboard JSON 内添加一行模板即可。
5.5 自动建图
光有组件监控还不够,业务也有监控需求,但是 PromQL 与 Grafana 的复杂却让人望而却步。没关系,有问题找架构,架构给解决。我们会按照指标类型为业务自动在 Grafana 上创建出不同的面板。

Counter 类型自动创建三张面板:
QPS
增量
区间增量:根据所选时间所计算的增量

Gauge 类型展示一张面板:
15 秒上报一次的原始点

Histogram 类型自动创建八张面板,分别为:
上报数据平均值
上报数据 TP99
上报次数 QPS:即调用 Histogram observe 函数的 QPS
上报次数增量:即调用 Histogram observe 函数的增量
上报次数区间增量:即调用 observe 函数的所选时间的增量
分布统计:根据自定义桶与所选时间展示各个区间的上报次数
分布折线图:根据自定义桶展示各个区间的分布趋势图
分布热力图:根据自定义桶用颜色表示数据的分布区间

5.6 样板间
除此之外,我们还在 Grafana 上提供了一个样板间,样板间拥有着业务同学所需要的大部分面板,只需简单复制,按照提示做略微的修改即可做出同样的效果。

六、报警系统
6.1 背景
监控系统的另一个重要领域是报警系统,Grafana8.0 之前的报警系统诟病较多,2021 年 6 月,Grafana8.0 之后推出了一个新报警系统 ngalert。不过 ngalert 在我们实测过程中也不太理想,主要体现在以下几个问题:
业务自己写 PromQL,学习成本较高
业务需要理解我们内置的指标的含义
发布时间短,我们实测 8000 个报警就会出现性能瓶颈

6.2 设计
基于以上背景,我们决定自研报警系统。我们参考了夜莺的设计与源码,当用户新建报警后,我们会自动生成 PromQL 持久化到 MySQL 中,报警服务通过 xxl-job 的分片广播调度去加载任务,对于每个任务,报警服务直接查询 M3DB 判断是否触发报警。

6.3 效果
对于业务自定义的指标,只需要点点点就可以设置好报警,报警系统会自动生成对应的 PromQL。

对于我们内置的中间件指标,只需填个阈值即可。

七、最终效果
7.1 业务服务维度
以服务为维度,内置丰富的组件面板,提供 All In One 的视角。



7.2 架构组件维度
以转转的架构中间件为维度,监控各个服务的使用详情,如日志、Codis 连接池、线程池等。


7.3 运维组件维度
利用 Prometheus 丰富的 Exporter 监控运维资源的整体使用情况,如 Nginx、机器、MySQL 等。


7.4 业务大盘
业务大盘提供一个全局的业务监控、分析视角。


八、总结
至此,我们通过借助开源社区的力量,并结合转转的业务场景做了简单二次扩展,落地了一套集业务服务、架构中间件、运维资源于一体的立体式监控平台,为全公司的各个业务提供一站式的监控报警服务。
这套监控系统在落地时核心扩展点主要围绕以下几块:
简化链路,
Pull
模型修改为Push+Pull
模型自动建图、面板自动初始化
自研报警系统,可视化 PromQL
打通转转内部信息系统,如服务信息系统、用户系统等
整体上来说,这套系统架构简洁、功能丰富、简单易用、维护成本低,还能借助开源社区的力量不断迭代。自上线三个月以来,各业务线积极接入,广受业务好评。
open source, open future.
作者简介
苑冲,转转架构部存储服务负责人,负责 MQ、监控系统、KV 存储、时序数据库、Redis Cluster 等基础组件,主导转转 MQ 流量路由、新监控系统的落地。热爱思考,热爱架构。
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转 FE」(专注于 FE)、「转转 QA」(专注于 QA),更多干货实践,欢迎交流分享~
评论