作者苑冲——转转架构部存储小组负责人,负责MQ、监控系统、KV存储、Redis、KMS凭据管理系统等等。热爱架构,热爱分享。
▼
— 1 —
背景概述
监控系统俗称“第三只眼”,通常在企业内承担着重要的角色,正所谓“无监控不运维”。虽然开源社区存在众多的监控系统,但落地一套为不同角色提供不同监控视角的一体化监控系统并非是一件容易的事。
在转转早期业务规模较小时,内部各个系统或自研、或利用开源上线了诸多监控系统。随着业务的发展,各个子系统独自维护监控与告警带来了许多问题,如数据散乱、告警散乱、功能偏弱、各成体系、运维成本高等,同时,也给研发人员带来了许多困扰,如学习与使用成本较高。下面介绍转转监控系统的历史背景及我们的思考。
1. 面向业务的旧版监控系统ZZMonitor
ZZMonitor是转转早期自研的、业务使用最广泛的核心监控系统,它会自动采集JVM指标,并对业务开放了埋点数据上报的能力,同时转转架构部还利用ZZMonitor对各个组件(如MQ Client、Redis Client、MySQL连接池等)增加了核心指标埋点。ZZMonitor有且仅有四种数据上报方式:SUM、MAX、MIN和AVG,代码示例如下:
ZZMonitor客户端会根据指标上报所使用的聚合函数,在客户端对数据按分钟进行聚合,并以异步、批量的方式上报到ZZMonitor服务端;服务端存储选型是MySQL,其分了128张表也仅能支持7天的数据存储。数据最终将以服务为维度自动展示,如图1-1所示。
客户端预先聚合再上报数据的目的是减少监控指标的数据存储量,但同时也牺牲了很多。随着业务的发展,ZZMonitor的弊端逐渐显露。
· API设计不合理,功能弱、业务反馈严重,仅提供四种聚合函数,无法监控QPS、P99等。
· 固定每分钟以聚合方式来上报数据不够灵活,同样的数据,业务需要按照聚合方式多次上报。同时,部分数据无法二次加工,如只能固定监控1分钟的平均值,无法监控到一天的平均值。
图1-1 ZZMonitor监控系统
· 存储选型不合理,监控数据通常与时间强相关,数据会持续追加写入,一旦写入后数据不会再次修改,这并不适合于关系型数据库MySQL,而适合存储于时序数据库,时序数据库会带来良好的读写性能与数据压缩比。
· 开发、维护成本高,新监控组件的接入、新功能的迭代都需要持续投入人力。另外,随着业务量的上升,系统的很多地方出现了性能瓶颈,如系统吞吐量达不到预期效果,需要持续维护,排查问题。
2. 面向RPC的服务管理平台
服务管理平台同属于转转自研的系统,基于RPC框架自动埋点,独立于其他监控系统,监控与告警系统自成一体,以服务为维度监控调用方与服务方各个RPC接口的QPS、耗时、服务质量详情、成功率、失败率等指标,如图1-2所示。
图1-2 服务管理平台
3. 面向运维资源的各个监控系统
除此之外,转转内部还散落着众多的监控系统,如面向Docker自研的云平台监控、面向物理机的Open-Falcon与夜莺监控系统、面向Redis自研的Redis监控平台、面向MySQL自研的数据库平台与Zabbix,以及面向TiDB与Nginx的Prometheus监控系统等近十套监控系统。
4. 现状思考
从业务侧的角度出发,核心监控系统ZZMonitor仅支持分钟粒度的四种聚合函数,数据无法二次聚合,可监控的场景相对较少;散乱的监控系统带来了许多学习成本与使用成本,一次请求异常通常需要排查几个监控系统才能看到想要的数据,错看、漏看、不知道怎么看的问题时有发生。
而从架构侧的角度出发,ZZMonitor的功能较弱、吞吐较低、无法满足多样的监控需求带来了内部监控系统的“百花齐放”,同时也带来了许多开发和维护成本,甚至架构部后端人员开发的前端页面也经常被业务研发人员吐槽。
针对现状,我们急需对监控系统重新统一规划。通过前期调研,业务人员期望以服务为维度提供All-In-One的一体化监控视角,同时期望它功能丰富、简单易用、UI页面美观;架构人员期望监控系统能够做到统一化、高吞吐、低维护成本,最好可以借助开源社区持续迭代。
摆在我们面前的有两条路:一是保持原状,自研迭代,逐步收拢各个监控系统到ZZMonitor内。此方案的优点在于用户习惯保持不变,但缺点也很明显,迭代速度慢、维护成本高、自研系统需要重新设计(包括SDK API)。二是借助开源社区的力量重新统一规划,落地一体化监控,抛弃历史包袱、轻装上阵,缺点是会带来用户习惯的迁移。经过综合考虑,我们选择了第二条路,痛一次,不痛则不通。
— 2 —
调研选型
我们重点关注了业内比较流行的Prometheus + Grafana。Prometheus的PromQL可以为我们实现灵活多变的监控需求,丰富的Exporter生态可以为我们的运维服务监控提供便利,Grafana上丰富灵活的看板可以为我们在可视化上节约开发成本,Prometheus与Grafana的社区也十分活跃,提供了一整套监控系统解决方案。
当然,我们也对开源社区的其他监控系统做了部分调研,如表15-1所示。
表2-1 选型对比(数据基于2021年7月)
总体来说,Prometheus + Grafana基本可以满足我们的低成本、一体化、功能丰富的需求。不过,Prometheus + Grafana并非开箱即用,也存在着一些痛点。
(1)架构复杂(一个是集群,一个是注册与发现)。Prometheus官方只提供单机版的实现,针对集群的解决方案是多副本采集+Prometheus联邦,这增加了架构模型的复杂度和运维难度,复杂的链路模型又会降低系统的容错性。对于IP经常变动的业务服务来说,需要接入注册中心以供Prometheus服务发现,这进一步增加了系统的复杂性。
(2)Grafana的看板规划。对于All-In-One的一体化监控,对看板的规划是前期非常重要的一项工作,否则随着时间的推移,在众多混乱的看板里又会出现错看、漏看、不知道怎么看的问题。
(3)PromQL与Grafana看板的学习成本和使用成本。我们既要给出自由与权利,让业务人员可以在监控系统上尽情发挥,又要尽量帮业务人员做好兜底工作,让他们只需要点点鼠标就能完成工作。
(4)告警相关问题。Prometheus与Grafana都可以设置告警,但它们的告警设置都需要编写PromQL。问题在于,如果不熟悉指标名字、标签名和标签值,即使十分熟悉PromQL语法,也很难正确设置告警,就导致每个人都必须熟悉并理解所有核心指标(如JVMGC次数、容器CPU利用率等)的结构,这显然是不可能的。除此之外,Grafana的告警不支持Grafana模板变量,导致部分告警场景无法得到支持。
下面我们一起来看看转转是如何解决这些问题的。
— 3 —
落地实践
1. 架构设计
Prometheus自带一个单机的TSDB,它以Pull的方式抓取指标,被抓取的指标需要以Http的方式暴露指标数据。对于地址经常变动的业务服务,服务需要将地址注册到注册中心,Prometheus先做服务发现,然后再做指标抓取,如图3-3所示。
图3-3 Prometheus架构模型
除了单机的TSDB,Prometheus还提供了存储的扩展,自定义了一套读写协议。当发生读写请求时,Prometheus会将请求转发到三方存储中,如图3-4所示。
图3-4 Prometheus远端存储
Prometheus的远端存储为我们提供了扩展性与可靠性,经过选型对比,我们最终选定M3DB作为远端存储。M3DB是Uber开源的专为Prometheus而生的分布式时序数据库,拥有较高的数据压缩比,同时也是夜莺强烈推荐的第三方存储。其架构如图3-5所示,各个模块的功能如下。
图3-5 M3DB架构模型
· M3 Coordinator:它是协调Prometheus和M3DB之间读写的一个服务,是上下游的桥梁,自身无状态,可以认为是M3DB的写模块。
· M3DB:它是一个分布式时间序列数据库,是真正的存储节点,提供可扩展的存储和时序索引。
· M3 Query:M3DB的专用查询引擎,兼容Prometheus查询语法,支持低延迟实时查询和长时间数据的查询,可以聚合更大的数据集,可以认为是M3DB的读模块。
· M3 Aggregator:它是一个专用的指标聚合器,能保证指标至少聚合一次,并持久化到M3DB存储中,可用于降采样,提供更长久的存储。
· ETCD:M3DB的元数据中心,管理集群的分片拓扑、选举等。
至此,我们便可以参考图3-6所示的官方路线设计出监控系统的架构了。对于拥有丰富的Exporter生态的运维服务来说,IP变动不频繁,我们固定IP配置由Prometheus主动抓取指标;而对于业务服务,转转的线上环境比较复杂,并没有完全容器化,需要单独引入注册中心,各个业务的服务在启动时先将地址注册到注册中心,并开启随机端口以Http Server的方式暴露各个服务的指标,Prometheus再从注册中心做服务发现,然后再做指标的拉取,最终将数据推送到M3DB中。
这套架构模型比较复杂,主要体现在以下几点:
· 架构复杂、层级太深、模块太多,运维成本高。
· 对于运维服务,每次增减Exporter实例都需要手动修改Prometheus配置。
· 对于业务服务,Prometheus客户端较重,需要单独引入注册中心。
· 对于业务服务,Prometheus的作用仅仅是指标抓取的一个中转,既没必要也容易增加问题点,还需要考虑Prometheus集群的问题。
图3-6 Prometheus官方路线
面对以上架构,我们做了一些思考,对于业务服务,是否可以省略服务注册与发现,将Prometheus Pull模型修改为客户端主动Push远端存储模型?既然Prometheus可以将拉取到的指标数据推送到第三方存储,为什么我们不能在业务服务上绕过Prometheus,直接推送到第三方存储呢?
于是,我们调研了Prometheus的远端存储协议,远端存储传输协议为HTTP,序列化方式为ProtoBuf。进而,我们改进了Prometheus客户端的设计,使客户端遵循存储协议,并自动为每个指标增加环境、服务名、IP标签,以异步、批量的方式主动将指标Push到M3DB。改进之后,客户端变得非常轻量,近乎零依赖(尤其是注册中心),并且完全兼容原生客户端的用法,因为我们只修改了数据上报的通信方式,对API无任何修改,如图3-7所示。
而对于各个运维服务监控,我们没有放弃Prometheus庞大丰富的Exporter生态,继续沿用Prometheus Pull模型。还有一个问题,Prometheus配置运维服务Exporter固定IP的方式不太灵活,由于同一类运维服务Exporter的端口是固定的,因此我们可以使用Prometheus Http服务发现的方式对接转转内部CMDB资产管理系统,CMDB内维护着各个运维服务所部署的IP列表。这样,增减运维服务Exporter只需要维护CMDB系统即可获取其IP列表。
图3-7 Prometheus SDK改动
转转监控系统最终架构,如图3-8所示。
图3-8 最终架构
2. 看板规划
我们对Grafana Dashboard划分了五个维度,如图3-9所示。
图3-9 Dashboard维度划分
· 业务看板:一个业务模块对应一个Dashboard,摘取该业务线下各个业务服务全局核心指标监控。例如,客服售后将组内各个业务服务的核心指标抽取到一个业务大盘里,方便领导在一个视图里看到客服售后的整体情况。
· 后端服务:一个后端服务对应一个Dashboard,以服务为粒度监控服务质量详情。
· 前端服务:一个前端服务对应一个Dashboard,以服务为粒度监控服务质量详情。
· 架构组件:一个中间件SDK对应一个Dashboard,以中间件SDK为粒度监控所有服务质量详情。
· 运维服务:一个运维服务对应一个Dashboard,以运维服务为粒度监控服务质量详情。
其中,业务看板、架构组件、运维服务Dashboard看板数量和变动相对较少,我们采取管理员人工手动创建Dashboard看板的方式;而对于数千个前后端服务,我们对接了转转内部服务信息管理系统,为每一个前后端服务分别自动全量或增量创建对应的Dashboard,每个Dashboard上均有环境、服务器IP、聚合维度三个变量,环境与IP用于对当前Dashboard的所有看板做过滤,聚合维度决定是否要忽略IP标签,对看板的数据做聚合。
前后端服务仅有一个Dashboard肯定是不够的,我们还对业务服务使用的各个中间件SDK增加了Prometheus埋点。而在Grafana可视化方面,由于Grafana的每个Dashboard都是一个JSON配置,因此我们的各个中间件SDK监控会对应一个预先设置好的Row JSON模板,后台服务会自动按需为每一个服务的Dashboard添加对应的Row JSON模板。这样,前后端服务的Dashboard就拥有了以Row划分的各个中间件SDK监控,包括不限于JVM监控、日志监控、线程池监控、数据库连接池监控、Redis Client监控、MQ Client监控、Web监控、RPC监控、物理机监控、容器监控等,如图3-10所示。此功能上线后,得到了业务人员的一致好评,他们什么都没做,就自动拥有了服务内一系列的监控看板。
图3-10 Dashboard组件监控
除了Dashboard与Row的自动创建,还需要对看板的权限做一定的规划,否则随着时间的推移,精心设计的看板规划又会变的一团糟,我们对看板的编辑做了权限控制。除此之外,部分监控系统的数据十分敏感,我们还需要对看板的查看做权限控制。Grafana的Folder和Dashboard自带RBAC的权限控制,我们仅需要对接转转内部员工系统、服务信息管理系统,并在Grafana上初始化好用户信息、服务权限信息即可完成Grafana的认证与鉴权。如此一来,只有服务信息管理系统的开发人员才有对应服务Dashboard的编辑权限,而对于少部分看板的查询权限,也可以单独设置。
对于登录认证,转转内部系统统一使用企业微信扫码SSO单点登录,我们基于Grafana Auth Proxy对接了转转SSO系统,Auth Proxy允许在Grafana请求Header中仅提供用户名来对用户做认证。当用户访问grafana.xxx.com时,Nginx会基于用户的登录态Cookie做拦截。如果用户没有登录,则会交由SSO系统跳转企业微信扫码页面,用户扫码后SSO系统识别用户身份、设置Cookie并再次重定向到grafana.xxx.com,Nginx再将Cookie内的用户身份添加到访问Grafana的请求Header中即可。
图3-11 Grafana认证和鉴权
3. 降本增效
Prometheus + Grafana的学习成本主要在于PromQL与Grafana的看板配置。为了降低学习成本,我们为业务显式埋点的指标自动创建看板,同时还修改了Prometheus的客户端。当指标初始化时。可选择将看板创建在当前服务Dashboard的哪个Row内,不同的指标类型会创建不同的看板,看板的标题使用指标的Help描述,如图3-12所示。
图3-12 自动创建Grafana看板
Counter类型的指标会自动创建QPS、数量、总数量看板,如图3-13所示。
Counter counter = Counter.build()
.name("upload_picture_count")
.row("核心业务监控") //看板创建在Dashboard的哪个Row下
.help("上传图片数") //看板的标题
.register();
图3-13 Counter类型的指标自动创建看板示例
Gauge类型的指标会自动创建15秒上报一次数据的原始点看板,如图3-14所示。
图3-14 Gauge类型的指标自动创建看板示例
Gauge gauge = Gauge.build()
.name("active_thread_size")
.row("线程池监控") //看板创建在Dashboard的哪个Row下
.help("线程池活跃线程数") //看板的标题
.labelNames("threadPoolName")
.register();
Histogram类型的指标自动创建平均值、P99、调用QPS、调用次量、调用总次量、分布统计、分布折线图、分布热力图,如图3-15所示。
Histogram histogram = Histogram.build()
.name("age_distribution")
.row("用户监控")
.help("用户年龄分布")
.labelNames("method", "uri")
.buckets(10, 20, 30, 40, 50, 60, 70)
.register();
图3-15 Histogram类型的指标自动创建看板示例
值得一提的是,Grafana仅提供修改整个Dashboard JSON的API,并未提供为某个Dashboard单独添加一个看板的API。当我们自动创建看板时,会模拟Grafana前端,解析整个Dashboard JSON,计算新看板所属位置的坐标后向Dashboard JSON中添加新看板的JSON数据,最后将精心构造好的新Dashboard JSON提交给Grafana后台。
4. 告警管理
Grafana 8.0之前的告警模块诟病较多,2021年6月,Grafana 8.0推出了一个新告警模块ngalert。ngalert会周期性地执行告警PromQL,并将告警内容提交到内置的AlertManager做告警降噪,如图3-16所示。
图3-16 Grafana ngalert
在实际落地测试中,ngalert的表现也并不理想,主要体现在以下几个方面。
· 业务人员需要自己手写告警PromQL,尤其对于服务内置的指标,业务人员需要理解指标的结构才能设置好告警。
· 对部分告警场景的支持不太友好。比如,对当前服务所使用的物理机负载做告警,由于服务使用的物理机经常变动,我们不能将IP填写在告警PromQL中,只能通过其他指标的IP来关联所有物理机的负载指标,而这种关联的代价通常非常大。例如,node_load1 * on (instance) group_right jvm_info{appName="myService"},通过jvm_info指标获取myService服务的IP并关联所有的node_load1指标。这个问题在各个服务的Dashboard看板内同样存在,那么如何展示当前服务所使用的物理机监控呢?我们可以通过模板变量的方式解决,每个服务的Dashboard都有IP模板变量,只需要将变量写到对应的PromQL中即可,node_load1{instance="$instance"}。遗憾的是,告警并不支持模板变量。
· 发布时间较短,测试时存在性能瓶颈。我们初始化了8000个左右的告警项,Grafana出现页面卡顿、崩溃,DB监控发现Grafana数据库有很多慢SQL,具体原因未知。
基于以上背景,我们决定自研告警系统。在用户新建告警时,我们会将告警配置翻译成PromQL并持久化到MySQL中。告警后台系统的每个实例会均分告警任务,并采用单线程轮询任务队列的方式判断任务是否到期执行,再交由工作线程执行告警任务,并判断是否触发告警,如图3-17所示。告警系统上线后累积共有数万条告警项,目前表现较为平稳。
图3-17 告警执行流程
对于业务自定义指标的告警设置,只需要点击就可以设置好告警,如图3-18所示。
图3-18 业务指标告警
对于架构部内置的中间件SDK指标告警,我们做了极大的简化,业务人员只需要填阈值即可,如图3-19所示。当然,我们默认也会为各个服务的核心指标统一初始化默认告警。
图3-19 内置指标告警
除了告警任务的执行,我们还需要对告警做降噪处理,我们在Alertmanager的基础上制定了标签规范、告警分级降噪、分级抑制、告警智能合并,并基于Alertmanager OpenAPI扩展了未恢复告警、静默告警、告警历史等功能。
— 4 —
效果收益
经过上述优化和改造,我们在转转落地了一套集业务服务、架构中间件、运维于一体的监控系统,下面是最终的效果和收益。
1. 业务看板
业务看板以业务模块为维度,摘取部分核心指标监控,如客服售后退款详情(图4-20)、通过各个服务的异常日志监控推送系统的服务质量详情(图4-21)。
图4-20 业务看板-客服售后
图4-21 业务看板-推送系统服务质量
2. 业务服务
业务服务看板都是自动按需创建的,包括不限于JVM监控、日志监控、容器监控、MQ Client监控、Redis Client监控、RPC监控等,以服务为维度监控整个服务的运行状态,如图4-22和4-23所示。
图4-22 后端服务(1)
图4-23 后端服务(2)
3. 架构组件
架构组件以某一个具体中间件、组件SDK为维度(偏向业务使用方监控),监控各个服务的运行状态,如监控各个服务的线程池执行详情、MQ各个Topic的生产和消费详情、分布式锁的使用详情等,如图4-24和4-25所示。
图4-24 架构组件-线程池
图4-25 架构组件-MQ
4. 运维组件
运维组件以某一个具体中间件服务、资源为维度(偏向运维监控),监控中间件、资源的运行状态,如物理机监控、MySQL监控等,如图4-26和4-27所示。
图4-26 运维服务-物理机
图4-27 运维服务-MySQL数据库
— 5 —
总结
借助于开源社区的力量,我们通过引进Prometheus + Grafana打造了转转新一代的监控系统。在落地过程中,我们始终站在用户的角度,基于转转的业务场景进行了少量的二次改造,针对架构模型、看板规划、学习成本、告警管理做了深度的定制化,最终落地了一套集业务服务、架构中间件、运维于一体的监控系统。新架构具有如下优点:
· 架构简洁,链路简单,省略了注册中心,甚至省略了Prometheus,运维成本低。
· 看板风格统一、使用成本低、拥有权限隔离,且服务自动拥有中间件SDK监控。
· 业务人员开箱即用,既有使用自由,又拥有为业务自动创建看板的兜底。
· 报警设置简单易用,灵活高效,通过鼠标点击即可拥有多条件、多通道告警,定制化程度高,由系统自动翻译成PromQL,摆脱了手写PromQL报警的烦恼。
新系统上线后,也受到了业务线人员的广泛使用和一致好评。相信转转遇到的问题其他公司也可能会遇到,当然,转转的解决方案并不是适用于所有场景,没有最好的架构,只有最适合的架构,谨供参考。
本文节选自《软件研发行业创新实战案例解析》。本书旨在通过各个公司在工程创新、管理创新、产品创新、技术创新、效能创新上的最佳实践,以及对案例的分析和总结,为其他公司提供一定的参考和借鉴,以帮助大家更快速地解决所遇到的问题。
本书共包含22个实战案例,涵盖了研发效能提升、数字化实践、敏捷转型、研发管理、人才培养、AI视觉分析引擎构建等软件研发各个领域的多个方面,适用于软件研发行业中的各类管理人员和从业者。
想要了解更多软件研发行业创新案例?那就快来看看这本书吧!
END
点这里↓↓↓记得关注标星哦~
中智凯灵中智凯灵是国内领先的专业人力资本服务供应商,为企业提供从人力测评、关键岗位人才培养、版权课程设计与输出、人才培养体系设计与开发等一系列的人力资本专业服务。本平台致力于为企业提供人才培养方面的内容分享。104篇原创内容公众号