这样用Prometheus+Grafana搭建监控报警系统,省钱又全能!

苑冲 2022-10-17 10:35:49

一、背景

 

转转早期的监控系统zzmonitor是纯自研的,其数据上报方式比较简单,有且仅有四种数据上报方式:SUM、MAX、MIN、AVG。示例如下:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
public void test() {    long start = System.currentTimeMillis();    //do something    long cost = System.currentTimeMillis() - start;          ZMonitor.sum("执行次数"1);    ZMonitor.max("最大耗时", cost);    ZMonitor.min("最小耗时", cost);    ZMonitor.avg("平均耗时", cost);}

 

数据在客户端会每分钟做一次聚合,并以异步、批量的方式发送到zzmonitor服务端。另外,zzmonitor的存储选型是MySQL,分了128张表也仅能支持7天的数据存储。

 

zzmonitor监控系统

 

以聚合方式来上报数据的目的是为了减少监控系统的数据存储量,但是同时也牺牲了太多,随着转转业务的发展,zzmonitor的弊端也逐渐显露:

 

  • 功能少,业务反馈大

 

仅提供这四个函数,无法监控QPS、TP99等。

 

  • API设计不合理

 

以聚合方式来上报数据不够灵活,同样的数据,业务需要按照聚合方式多次上报;同时,部分数据无法二次加工,如:只能监控1分钟的平均值,无法监控到一天的平均值。

 

  • 架构设计不合理

 

监控数据通常与时间强相关,使用MySQL存储性能差,数据压力也较大。存储数据常适合存储于时序数据库,时序数据库会带来良好的读写性能与数据压缩比。

 

  • 维护&开发成本高

 

随着业务量的上升,系统的很多地方出现了性能瓶颈,需要人持续的维护、排查问题。新功能的迭代也需要持续投入人力。

 

另外,除了给业务使用的zzmonitor,运维层面还有各式各样的监控系统,如:Falcon、夜莺、ZABBIX、Prometheus等,这给业务线同学使用上带来一定的困惑。

 

二、调研选型

 

基于以上背景,我们决定落地一套全新的立体式监控系统,能够集业务服务、架构中间件、运维资源于一体,简单易用,同时便于维护。落地前期主要针对Cat、夜莺、Prometheus做了一部分调研选型。

 

 

总体来说,Cat更适合链路监控,夜莺更像一个简化版的Prometheus+Grafana,而Prometheus拥有非常灵活的PromQL、完善的Exporter生态,我们最终选择了Prometheus。

 

三、Prometheus能力

 

 

1、生态模型

 

Prometheus自带一个单机的TSDB,他以pull模式抓取指标,被抓取的目标需要以Http的方式暴露指标数据。对于业务服务,服务需要将地址注册到注册中心,Prometheus做服务发现,然后再做指标抓取。

 

Prometheus架构图

 

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

 

Exporter生态

 

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

 

Grafana落地效果

 

Prometheus从客户端角度区分出了几种指标类型:Counter、Gauge、Histogram、Summary(转转内部不建议使用)。

 

 

2、Counter

 

Counter是一个只增不减的计数器,Prometheus抓取的是Counter当前累计的总量。常见的如GC次数、Http请求次数都是Counter类型的监控指标。

 

  •  
  •  
Counter counter = Counter.build().name("upload_picture_total").help("上传图片数").register();counter.inc();

 

以下是Counter的样本数据特点,我们可以在查询时计算出任意一段时间的增量,也可以计算任意一段时间的增量/时间,即QPS。

 

Counter计数器

 

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

 

Counter重置

 

 

3、Gauge

 

Gauge是个可增可减的仪表盘,Prometheus抓取的是Gauge当前时刻设置的值。常见的如内存使用量、活跃线程数等都是Gauge类型的监控指标。

 

  •  
  •  
Gauge gauge = Gauge.build().name("active_thread_num").help("活跃线程数").register();gauge.set(20);

 

以下是Gauge类型的样本数据特点,我们一般不做二次计算,直接展示Prometheus抓取的原始值。

 

Gauge仪表盘

 

 

4、Histogram

 

Histogram通常用于数据分布统计,Histogram需要定义桶区间分布,根据用户上报的数据,来决定具体落到哪个桶内。

 

  •  
  •  
Histogram histogram = Histogram.build().name("http_request_cost").help("Http请求耗时").buckets(10, 20, 30, 40).register();histogram.observe(20);

 

以下是部分源码,各个桶内存储上报的总次数,Prometheus抓取的是各个桶的当前状态。

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
public void observe(double value) {    for (int i = 0; i < bucket.length; ++i) {        //遍历所有桶,如果数据小于桶上限,对应桶+1        if (value <= bucket[i].le) {            bucket[i].add(1);            break;        }    }    //增加sum总值    sum.add(value);}

 

基于各个时刻的桶内数据,我们可以计算出任意一段时间的数据分布情况。如下为例,我们可以利用桶内数据计算出10:01~10:02的数据分布情况。

 

 

这个计算结果可视化后可能如下图所见,实际意义可能是一天的RPC调用耗时分布。基于这个计算结果,还可以计算出TP值,即n%的值都不高于x。

 

Histogram桶分布

 

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

 

分布折线图

 

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

 

热力图

 

Histogram除了buckets,还记录了sum(上报数据的总值)、count(上报数据的总次数)。我们通过一段时间内sum的增量/count的增量,即可算出上报数据的平均值。

 

 

5、多维标签

 

Prometheus的指标可以定义多个标签,比如对于以下指标:

 

  •  
  •  
  •  
  •  
  •  
Counter counter = Counter.build().name("http_request").labelNames("method","uri").help("Http请求数").register();
counter.labels("POST", "/addGoods").incr();counter.labels("POST", "/updateGoods").incr(2);counter.labels("GET", "/getGoods").incr();

 

数据上报后,类比MySQL表,表名为http_request,某一时刻的表数据如下:

 

 

有了这些结构化的数据,就可以在任意维度进行查询和聚合。比如,查询uri=/addGoods的数据;再比如,按照method维度做SUM聚合,查看总数。

 

 

6、误差

 

Prometheus 在设计上就放弃了一部分数据准确性,得到的是更高的可靠性,架构简单、数据简单、运维简单、节约机器成本与人力成本。通常对于监控系统,数据拥有少量的误差是可以接受的。

 

如计算增量时,给定的时间窗口中的第一个和最后一个样本,永远不会与真实的采样点100%重合,Prometheus会做数据线性外推,估算出对应时间的样本值。

 

数据外推

 

四、架构设计

 

 

1、远端存储

 

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

 

远端协议

 

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

 

M3DB架构

 

  • M3 Coordinator(M3 协调器) 

 

M3协调器是一个协调Prometheus和 M3DB 之间的读写的服务。它是上下游的桥梁,自身无状态。

 

  • M3DB(存储数据库)

 

M3DB是一个分布式时间序列数据库,是真正的存储节点,提供可扩展的存储和时序索引。

 

  • M3 Query (M3 查询引擎)

 

M3DB专用查询引擎,兼容Prometheus查询语法,支持低延迟实时查询和长时间数据的查询,可以聚合更大的数据集。

 

  • M3 Aggregator (M3 聚合器)

 

M3聚合器是一个专用的指标聚合器,会保证指标至少会聚合一次,并持久化到M3DB存储中,可用于降采样,提供更长久的存储。

 

 

2、官方路线

 

以下是完全以官方路线设计出的系统架构,转转的线上环境比较复杂,并没有完全容器化,我们需要单独引入注册中心。各个业务的服务在启动时将地址注册到注册中心,Prometheus再从注册中心做服务发现,然后再做指标的拉取,最终将数据推送到M3DB中。

 

官方路线

 

这套架构模型比较复杂,主要体现在以下几点:

 

  • 架构复杂、层级太深、模块太多,运维成本高;

 

  • 客户端较重,需要引入注册中心;

 

  • Prometheus的作用仅仅是个指标抓取的中转,即没必要也容易增加问题点,还需要考虑分片的问题。

 

 

3、客户端设计

 

面对以上架构,我们做了一些思考,既然Prometheus可以将拉取到的指标数据推送到三方存储,为什么我们不能在业务服务上绕过Prometheus而直接推送到三方存储呢?

 

于是,我们调研了Prometheus远端存储协议,改进了客户端的设计。客户端遵循Prometheus远端存储协议(ProtoBuf + Http),并以异步、批量的方式主动将指标推送到M3DB。

 

改进之后客户端非常轻量,近乎零依赖,并且完全兼容原生客户端用法,因为我们只修改了数据上报的内核逻辑,对API无任何修改。

 

客户端设计

 

 

4、最终架构

 

最终,我们的系统架构如下图所示,对于业务服务,我们会通过客户端主动将指标推送到M3DB中;而对于各个中间件,由于服务IP变动不频繁,继续沿用Exporter生态。

 

这样,我们省去了注册中心,省去了服务发现,也省去了抓取分片。对于业务服务我们甚至省去了Prometheus,我们只用了Prometheus的协议,而没有使用Prometheus的服务。

 

最终架构

 

 

5、性能测试

 

由于Prometheus需要在客户端埋点上报数据,我们对客户端的性能也做了重点测试。

 

 

QPS不存在瓶颈,千万级以上,耗时纳秒级别,内存占用取决于标签数量,总体来说资源消耗比较小。

 

五、落地实现

 

 

1、Grafana规划

 

第一个规划是多环境统一,不管线上、沙箱、测试环境,都统一使用同一个Grafana,避免维护多套面板、避免业务记多套地址。

 

第二个是维度划分,我们目前对Dashboard划分四个维度:

 

  • 业务大盘

 

我们每个研发同学、每个部门都会负责很多服务,业务大盘摘取了大家负责服务的重点指标,提供一个全局的视角。

 

  • 业务服务

 

这里是以业务服务为维度,拥有我们内置的各种监控面板,是一个All In One的视角。

 

  • 架构组件

 

以各个组件为维度,监控所有服务整体使用情况,如线程池监控、日志监控。

 

  • 运维组件

 

以运维视角来看到各个中间的监控,如Redis、Nginx等。

 

立体监控

 

 

2、数据打通

 

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

 

数据打通

 

 

3、企业微信认证

 

转转的内部系统统一走的企业微信扫码认证,我们基于Grafana的一个小功能Auth Proxy实现了企业微信认证。简单来说,当开启了Auth Proxy,访问Grafana的请求如果携带了Header用户名,Grafana就认为是对应用户访问了系统。

 

对于转转,当用户访问grafana.xxx.com时,Nginx会做拦截跳转企业微信扫码认证,认证之后由Nginx种入对应Header访问Grafana。

 

有些同学看到这里可能会担心会不会有安全问题,无需担心。

 

  • Header是由Nginx层种入,不会泄露到前端;

 

  • 如果走域名访问,Nginx会做统一拦截认证;

 

  • 如果走IP访问,我们封禁了非80端口的IP访问。

 

认证流程

 

 

4、组件面板自动初始化

 

转转的架构中间件基于客户端做了各种埋点,包括不限于:JVM、线程池监控、数据库连接池、Codis、Web监控、日志监控、Docker监控等。

 

Grafana的Dashboard是一整个大JSON,我们只需提前定义好我们的模板,向Dashboard JSON内添加一行模板即可。

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
{    "panels": [        {            "title": "业务指标",            "panels": []        },        {            "title": "JVM",            "panels": []        },        {            "title": "日志监控",            "panels": []        }    ]}

 

 

5、自动建图

 

光有组件监控还不够,业务也有监控需求,但是PromQL与Grafana的复杂却让人望而却步。没关系,有问题找架构,架构给解决。我们会按照指标类型为业务自动在Grafana上创建出不同的面板。

 


自动建图

 

Counter类型自动创建三张面板:

 

  • QPS

  • 增量

  • 区间增量:根据所选时间所计算的增量

 

  •  
Counter counter = Counter.build().name("upload_picture_count").help("上传图片数").register();

 


Counter可视化效果

 

Gauge类型展示一张面板:

 

  • 15秒上报一次的原始点

 

  •  
Gauge gauge = Gauge.build().name("active_thread_size").help("活跃线程数").labelNames("threadPoolName").register();

 

Gauge可视化效果

 

Histogram类型自动创建八张面板,分别为:

 

  • 上报数据平均值

  • 上报数据TP99

  • 上报次数QPS:即调用Histogram observe函数的QPS

  • 上报次数增量:即调用Histogram observe函数的增量

  • 上报次数区间增量:即调用observe函数的所选时间的增量

  • 分布统计:根据自定义桶与所选时间展示各个区间的上报次数

  • 分布折线图:根据自定义桶展示各个区间的分布趋势图

  • 分布热力图:根据自定义桶用颜色表示数据的分布区间

 

  •  
Histogram histogram = Histogram.build().name("http_request_cost").help("Http请求耗时").labelNames("method", "uri").buckets(10, 20, 30, 40).register();

 


Histogram可视化效果

 

 

6、样板间

 

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

 

样板间

 

六、报警系统

 

 

1、背景

 

监控系统的另一个重要领域是报警系统,Grafana8.0之前的报警系统诟病较多,2021年6月,Grafana8.0之后推出了一个新报警系统ngalert。不过ngalert在我们实测过程中也不太理想,主要体现在以下几个问题:

 

  • 业务自己写PromQL,学习成本较高;

     

  • 业务需要理解我们内置的指标的含义;

     

  • 发布时间短,我们实测8000个报警就会出现性能瓶颈

 

Grafana ngaler

 

 

2、设计

 

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

 

报警流程

 

 

3、效果

 

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

 


报警效果

 

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

 


内置指标报警

 

七、最终效果

 

 

1、业务服务维度

 

以服务为维度,内置丰富的组件面板,提供All In One的视角。

 


业务服务

 


业务服务

 


业务服务

 

 

2、架构组件维度

 

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

 


架构组件

 


架构组件

 

 

3、运维组件维度

 

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

 


运维组件

 


运维组件

 

 

4、业务大盘

 

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

 


业务大盘

 


业务大盘

 

八、总结

 

至此,我们通过借助开源社区的力量,并结合转转的业务场景做了简单二次扩展,落地了一套集业务服务、架构中间件、运维资源于一体的立体式监控平台,为全公司的各个业务提供一站式的监控报警服务。

 

这套监控系统在落地时核心扩展点主要围绕以下几块:

 

  • 简化链路,Pull模型修改为Push+Pull模型

     

  • 自动建图、面板自动初始化

     

  • 自研报警系统,可视化PromQL

     

  • 打通转转内部信息系统,如服务信息系统、用户系统等

 

整体上来说,这套系统架构简洁、功能丰富、简单易用、维护成本低,还能借助开源社区的力量不断迭代。自上线三个月以来,各业务线积极接入,广受业务好评。

 

作者丨苑冲
来源丨公众号:转转技术(ID:zhuanzhuantech)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

最新评论
访客 2023年08月20日

230721

访客 2023年08月16日

1、导入Mongo Monitor监控工具表结构(mongo_monitor…

访客 2023年08月04日

上面提到: 在问题描述的架构图中我们可以看到,Click…

访客 2023年07月19日

PMM不香吗?

访客 2023年06月20日

如今看都很棒

活动预告