learn-tech/专栏/周志明的架构课/44_聚合度量能给我们解决什么问题?.md
2024-10-16 06:37:41 +08:00

20 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        44 _ 聚合度量能给我们解决什么问题?
                        你好,我是周志明。这节课我们来探讨“可观测性”这个小章节的最后一个话题:聚合度量。

度量Metrics的目的是揭示系统的总体运行状态。相信你可能在一些电影里见过这样的场景舰船的驾驶舱或者卫星发射中心的控制室处在整个房间最显眼的位置布满整面墙壁的巨型屏幕里显示着一个个指示器、仪表板与统计图表沉稳端坐中央的指挥官看着屏幕上闪烁变化的指标果断决策下达命令……

而如果以上场景被改成指挥官双手在键盘上飞舞,双眼紧盯着日志或者追踪系统,试图判断出系统工作是否正常。这光想像一下,你都能感觉到一股身份与行为不一致的违和气息,由此可见度量与日志、追踪的差别。

简单来说,度量就是用经过聚合统计后的高维度信息,以最简单直观的形式来总结复杂的过程,为监控、预警提供决策支持。

我们大多数人的人生经历可能都会比较平淡没有驾驶航母的经验甚至连一颗卫星或者导弹都没有发射过那就只好打开电脑按CTRL+ALT+DEL呼出任务管理器看看下面这个熟悉的界面它也是一个非常具有代表性的度量系统。

Windows系统的任务管理器界面

在总体上,度量可以分为客户端的指标收集、服务端的存储查询以及终端的监控预警三个相对独立的过程,每个过程在系统中一般也会设置对应的组件来实现。

那么现在呢你不妨先来看一下我在后面举例时会用到的Prometheus组件流程图图中Prometheus Server左边的部分都属于客户端过程而右边的部分就属于终端过程。

虽然说Prometheus在度量领域的统治力暂时还不如日志领域中Elastic Stack的统治地位那么稳固但在云原生时代里它基本已经算得上是事实标准了。所以接下来我就主要以Prometheus为例给你介绍这三部分组件的总体思路、大致内容与理论标准。

指标收集

我们先来了解下客户端指标收集部分的核心思想。这一部分主要是解决两个问题:“如何定义指标”以及“如何将这些指标告诉服务端”。

如何定义指标?

首先我们来聊聊“如何定义指标”这个问题。乍一看你可能会觉得它应该是与目标系统密切相关的,必须根据实际情况才能讨论,但其实并不绝对。

要知道无论目标是何种系统它都具备了一些共性特征虽然在确定目标系统前我们无法决定要收集什么指标但指标的数据类型Metrics Types是可数的所有通用的度量系统都是面向指标的数据类型来设计的现在我就来一一给你解读下

计数度量器Counter这是最好理解也是最常用的指标形式计数器就是对有相同量纲、可加减数值的合计量。比如业务指标像销售额、货物库存量、职工人数等技术指标像服务调用次数、网站访问人数等它们都属于计数器指标。 瞬态度量器Gauge瞬态度量器比计数器更简单它就表示某个指标在某个时点的数值连加减统计都不需要。比如当前Java虚拟机堆内存的使用量这就是一个瞬态度量器再比如网站访问人数是计数器而网站在线人数则是瞬态度量器。 吞吐率度量器Meter顾名思义它是用于统计单位时间的吞吐量即单位时间内某个事件的发生次数。比如在交易系统中常以TPS衡量事务吞吐率即每秒发生了多少笔事务交易再比如港口的货运吞吐率常以“吨/每天”为单位计算10万吨/天的港口通常要比1万吨/天的港口的货运规模更大。 直方图度量器Histogram直方图就是指常见的二维统计图它的两个坐标分别是统计样本和该样本对应的某个属性的度量以长条图的形式记录具体数值。比如经济报告中要衡量某个地区历年的GDP变化情况常会以GDP为纵坐标、时间为横坐标构成直方图来呈现。 采样点分位图度量器Quantile Summary分位图是统计学中通过比较各分位数的分布情况的工具主要用来验证实际值与理论值的差距评估理论值与实际值之间的拟合度。比如我们说“高考成绩一般符合正态分布”这句话的意思就是高考成绩高低分的人数都比较少中等成绩的比较多按不同分数段来统计人数得出的统计结果一般能够与正态分布的曲线较好地拟合。 除了以上常见的度量器之外还有Timer、Set、Fast Compass、Cluster Histogram等其他各种度量器采用不同的度量系统支持度量器类型的范围肯定会有所差别比如Prometheus就支持了上面提到的五种度量器中的Counter、Gauge、Histogram和Summary四种。

如何将这些指标告诉服务端?

然后是针对“如何将这些指标告诉服务端”这个问题它通常有两种解决方案拉取式采集Pull-Based Metrics Collection和推送式采集Push-Based Metrics Collection

所谓Pull是指度量系统主动从目标系统中拉取指标相对地Push就是由目标系统主动向度量系统推送指标。

这两种方式实际上并没有绝对的好坏优劣以前很多老牌的度量系统比如Ganglia、Graphite、StatsD等是基于Push的而以Prometheus、Datadog、Collectd为代表的另一派度量系统则青睐Pull式采集Prometheus官方解释选择Pull的原因。另外你也要知道对于是要选择Push还是Pull不仅是在度量中才有所有涉及到客户端和服务端通讯的场景都会涉及到该谁主动的问题上一节课讲的追踪系统也是如此。

不过一般来说,度量系统只会支持其中一种指标采集方式,这是因为度量系统的网络连接数量,以及对应的线程或者协程数可能非常庞大,如何采集指标将直接影响到整个度量系统的架构设计。

然而Prometheus在基于Pull架构的同时还能够有限度地兼容Push式采集这是为啥呢原因是它有Push Gateway的存在。

如下图所示这是一个位于Prometheus Server外部的相对独立的中介模块它会把外部推送来的指标放到Push Gateway中暂存然后再等候Prometheus Server从Push Gateway中去拉取。

Prometheus组件流程图

Prometheus设计Push Gateway的本意是为了解决Pull的一些固有缺陷比如目标系统位于内网需要通过NAT访问外网而外网的Prometheus是无法主动连接目标系统的这就只能由目标系统主动推送数据又比如某些小型短生命周期服务可能还等不及Prometheus来拉取服务就已经结束运行了因此也只能由服务自己Push来保证度量的及时和准确。

而在由Push和Pull决定完该谁主动以后另一个问题就是指标应该通过怎样的网络访问协议、取数接口、数据结构来获取呢

跟计算机科学中其他类似的问题一样人们一贯的解决方向是“定义规范”应该由行业组织和主流厂商一起协商出专门用于度量的协议目标系统按照协议与度量系统交互。比如说网络管理中的SNMP、Windows硬件的WMI以及第41讲中提到的Java的JMX都属于这种思路的产物。

但是,定义标准这个办法在度量领域中其实没有那么有效,前面列举的这些度量协议,只是在特定的一小块领域里流行过。

要究其原因的话一方面是因为业务系统要使用这些协议并不容易你可以想像一下让订单金额存到SNMP中让Golang的系统把指标放到JMX Bean里即便技术上可行这也不像是正常程序员会干的事而另一方面度量系统又不会甘心局限于某个领域成为某项业务的附属品。

另外我们也要明确一个事实度量面向的是广义上的信息系统它横跨存储日志、文件、数据库、通讯消息、网络、中间件HTTP服务、API服务直到系统本身的业务指标甚至还会包括度量系统本身部署两个独立的Prometheus互相监控是很常见的

所以,这些度量协议其实都没有成为最正确答案的希望。

如此一来既然没有了标准有一些度量系统比如老牌的Zabbix就选择同时支持了SNMP、JMX、IPMI等多种不同的度量协议。而另一些以Prometheus为代表的度量系统就相对强硬它们不支持任何一种协议只允许通过HTTP访问度量端点这一种访问方式。如果目标提供了HTTP的度量端点如Kubernetes、Etcd等本身就带有Prometheus的Client Library就直接访问否则就需要一个专门的Exporter来充当媒介。

这里的Exporter是Prometheus提出的概念它是目标应用的代表它既可以独立运行也可以与应用运行在同一个进程中只要集成Prometheus的Client Library就可以了。

Exporter的作用就是以HTTP协议Prometheus在2.0版本之前支持过Protocol Buffer目前已不再支持返回符合Prometheus格式要求的文本数据给Prometheus服务器。得益于Prometheus的良好社区生态现在已经有大量、各种用途的Exporter让Prometheus的监控范围几乎能涵盖到所有用户关心的目标绝大多数用户都只需要针对自己系统业务方面的度量指标编写Exporter即可。你可以参考下这里给出的表格

另外顺便一提在前面我提到了一堆没有希望成为最终答案的协议比如SNMP、WMI等等。不过现在一种名为OpenMetrics的度量规范正逐渐从Prometheus的数据格式中分离出来有望成为监控数据格式的国际标准最终结果究竟如何要看Prometheus本身的发展情况还有OpenTelemetry与OpenMetrics的关系如何协调。

存储查询

好,那么当指标从目标系统采集过来了之后,就应该存储在度量系统中,以便被后续的分析界面、监控预警所使用。

存储数据对于计算机软件来说其实是司空见惯的操作,但如果用传统关系数据库的思路来解决度量系统的存储,效果可能不会太理想。

我举个例子假设你要建设一个中等规模、有着200个节点的微服务系统每个节点要采集的存储、网络、中间件和业务等各种指标加一起也按200个来计算监控的频率如果按秒为单位的话一天时间内就会产生超过34亿条记录而对于这个结果你可能会感到非常意外

200节点× 200指标× 86400= 3,456,000,000记录

因为在实际情况中大多数这种200节点规模的系统本身一天的生产数据都远到不了34亿条那么建设度量系统肯定不能让度量反倒成了业务系统的负担。可见度量的存储是需要专门研究解决的问题。至于具体要如何解决让我们先来观察一段Prometheus的真实度量数据吧

{ // 时间戳 "timestamp": 1599117392, // 指标名称 "metric": "total_website_visitors", // 标签组 "tags": { "host": "icyfenix.cn", "job": "prometheus" }, // 指标值 "value": 10086 }

通过观察,我们可以发现这段度量数据的特征:每一个度量指标由时间戳、名称、值和一组标签构成,除了时间之外,指标不与任何其他因素相关。

当然指标的数据总量固然是不小的但它没有嵌套、没有关联、没有主外键不必关心范式和事务这些就都是可以针对性优化的地方。事实上业界也早就有了专门针对该类型数据的数据库即“时序数据库”Time Series Database

额外知识:时序数据库-

时序数据库是用于存储跟随时间而变化的数据,并且以时间(时间点或者时间区间)来建立索引的数据库。-

时序数据库最早是应用于工业(电力行业、化工行业)应用的各类型实时监测、检查与分析设备所采集、产生的数据,这些工业数据的典型特点是产生频率快(每一个监测点一秒钟内可产生多条数据)、严重依赖于采集时间(每一条数据均要求对应唯一的时间)、测点多信息量大(常规的实时监测系统均可达到成千上万的监测点,监测点每秒钟都在产生数据)。-

时间序列数据是历史烙印,它具有不变性、唯一性、有序性。时序数据库同时具有数据结构简单、数据量大的特点。

我们应该注意到存储数据库在写操作时时序数据通常只是追加很少删改或者根本不允许删改。因此针对数据热点只集中在近期数据、多写少读、几乎不删改、数据只顺序追加等特点时序数据库被允许可以做出很激进的存储、访问和保留策略Retention Policies

以日志结构的合并树Log Structured Merge TreeLSM-Tree代替传统关系型数据库中的B+Tree作为存储结构LSM适合的应用场景就是写多读少且几乎不删改的数据。 设置激进的数据保留策略比如根据过期时间TTL自动删除相关数据以节省存储空间同时提高查询性能。对于普通的数据库来说数据会存储一段时间后被自动删除的这个做法可以说是不可想象的。 对数据进行再采样Resampling以节省空间比如最近几天的数据可能需要精确到秒而查询一个月前的数据只需要精确到天查询一年前的数据只要精确到周就够了这样将数据重新采样汇总就极大地节省了存储空间。

而除此之外时序数据库中甚至还有一种并不罕见却更加极端的形式叫做轮替型数据库Round Robin DatabaseRRD它是以环形缓冲在“服务端缓存”一节介绍过的思路实现只能存储固定数量的最新数据超期或超过容量的数据就会被轮替覆盖因此它也有着固定的数据库容量却能接受无限量的数据输入。

所以Prometheus服务端自己就内置了一个强大的时序数据库实现我说它“强大”并不是客气在DB-Engines中近几年它的排名就在不断提升目前已经跃居时序数据库排行榜的前三。

这个时序数据库提供了一个名为PromQL的数据查询语言能对时序数据进行丰富的查询、聚合以及逻辑运算。当然了某些时序库如排名第一的InfluxDB也会提供类SQL风格的查询但PromQL不是它是一套完全由Prometheus自己定制的数据查询DSL写起来的风格有点像带运算与函数支持的CSS选择器。比如我要查找网站icyfenix.cn的访问人数的话会是如下写法

// 查询命令: total_website_visitors{host=“icyfenix.cn”}

// 返回结果: total_website_visitors{host=“icyfenix.cn”,job="prometheus"}=(10086)

这样通过PromQL就可以轻易实现指标之间的运算、聚合、统计等操作在查询界面中也往往需要通过PromQL计算多种指标的统计结果才能满足监控的需要语法方面的细节我就不详细展开了具体你可以参考Prometheus的文档手册。

最后我还想补充说明一下时序数据库对度量系统来说确实是很合适的选择但并不是说绝对只有用时序数据库才能解决度量指标的存储问题Prometheus流行之前最老牌的度量系统Zabbix用的就是传统关系数据库来存储指标。

好,接下来我们继续探讨聚合度量的第三个过程:终端的监控预警。

监控预警

首先要知道,指标度量是手段,而最终目的是要做分析和预警。

界面分析和监控预警是与用户更加贴近的功能模块但对度量系统本身而言它们都属于相对外围的功能。与追踪系统的情况类似广义上的度量系统由面向目标系统进行指标采集的客户端Client与目标系统进程在一起的Agent或者代表目标系统的Exporter等都可归为客户端负责调度、存储和提供查询能力的服务端ServerPrometheus的服务端是带存储的但也有很多度量服务端需要配合独立的存储来使用以及面向最终用户的终端BackendUI界面、监控预警功能等都归为终端组成而狭义上的度量系统就只包括客户端和服务端不包含终端。

那么按照定义Prometheus应该算是处于狭义和广义的度量系统之间尽管它确实内置了一个界面解决方案“Console Template”以模版和JavaScript接口的形式提供了一系列预设的组件菜单、图表等让用户编写一段简单的脚本就可以实现可用的监控功能。不过这种可用程度往往不足以支撑正规的生产部署只能说是为把度量功能嵌入到系统的某个子系统中提供了一定的便利。

因而在生产环境下大多是Prometheus配合Grafana来进行展示的这是Prometheus官方推荐的组合方案。但该组合也并非唯一的选择如果你要搭配Klbana甚至SkyWalking8.x版之后的SkyWalking支持从Prometheus获取度量数据来使用也都是完全可行的。

另外,良好的可视化能力对于提升度量系统的产品力也非常重要,长期趋势分析(比如根据对磁盘增长趋势的观察,判断什么时候需要扩容)、对照分析(比如版本升级后对比新旧版本的性能、资源消耗等方面的差异)、故障分析(不仅从日志、追踪自底向上可以分析故障,高维度的度量指标也可能自顶向下寻找到问题的端倪)等分析工作,既需要度量指标的持续收集、统计,往往还需要对数据进行可视化,这样才能让人更容易地从数据中挖掘规律,毕竟数据最终还是要为人类服务的。

而除了为分析、决策、故障定位等提供支持的用户界面外度量信息的另一种主要的消费途径就是用来做预警。比如你希望当磁盘消耗超过90%时,给你发送一封邮件或者是一条微信消息,通知管理员过来处理,这就是一种预警。

Prometheus提供了专门用于预警的Alert Manager我们将Alert Manager与Prometheus关联后可以设置某个指标在多长时间内、达到何种条件就会触发预警状态在触发预警后Alert Manager就会根据路由中配置的接收器比如邮件接收器、Slack接收器、微信接收器或者更通用的WebHook接收器等来自动通知我们。

小结

今天是“可观测性”章节的最后一节课可观测性作为控制理论中的一个概念从1960年代起就已经存在了虽然它针对信息系统和分布式服务的适用性是最近若干年中新发现的但在某种程度上这也算是过去20年对这些系统的监控方式的演变产物。

那么学完了今天这节课,你需要记住一个要点,即传统监控和可观测性之间的关键区别在于:可观测性是系统或服务内在的固有属性,而不是在系统之外对系统所做出的额外增强,后者是传统监控的处理思路。

除此之外,构建具有可观测性的服务,也是构建健壮服务不可缺少的属性,这是分布式系统架构师的职责。那么作为服务开发者和设计者,我们应该在其建设期间,就要设想控制系统会发出哪些信号、如何接收和存储这些信号,以及如何使用它们,以确保在用户能在受到影响之前了解问题、能使用度量数据来更好地了解系统的健康状况和状态。

一课一思

在你设计系统时,是否考虑过要对外部暴露哪些可观测的属性?你通常会暴露哪些数据?是以什么方式暴露的呢?欢迎在留言区分享你的见解和做法。

如果你觉得有收获,也欢迎把今天的内容分享给更多的朋友。感谢你的阅读,我们下一讲再见。