02 | 基本概念:指标+日志+链路追踪=可观测性?

你好,我是翁一磊。

上节课,我们介绍了计算机系统监控的发展历史,这节课我们来具体聊一聊可观测性,以及大家对于可观测性的一些误解。

什么是可观测性?

就像我们在开篇词中说的,可观测性强调的是可以从系统向外部输出的信息来推断出系统内部状态的好坏。

当我们把“可观测性”这个概念挪到软件系统时,其实强调的也是一种度量能力,一个软件应用程序具有可观测性,意味着它能够让我们通过各种维度和各种角度去分析和理解这个系统当前所处的任何状态,无论这种状态有多奇怪、无论我们之前有没有遇到过,都不需要预先定义或预测。如果能够在不发布新代码(如增加一个用于调试的日志)的情况下理解任何奇怪或不确定性的状态,那么我们的系统就具备可观测性。

因此,可观测性是描述人们如何与他们的复杂系统互动,以及如何理解这些复杂系统的概念。如果你接受这个定义,那么看看接下来这些问题:

  • 如何收集数据并将它们组合起来进行分析?
  • 处理这些数据的技术要求是什么?
  • 要从这些数据中获益,团队需要具备哪些能力?

这些问题,我们都会在专栏中一一解答。不过别着急,这节课我们还是要先把可观测性的概念和内涵理清楚。

指标+日志+链路追踪=可观测性?

既然选择学习这门课程,你八成听过可观测性的“三大支柱”:指标(metrics),日志(logs)和链路追踪(Tracing)。但是,指标、日志再加上链路追踪,真的就是可观测性吗?让我们先来看一下这三类数据的含义。

指标:是在⼀段时间内测量的数值。它包括特定属性,例如时间戳、名称、键和值。和⽇志不同,指标在默认情况下是结构化的,这会让查询和优化存储变得更加容易。

例如:2022/05/20 12:48:22,CPU usage user,23.87%,它就表示 CPU 运行在用户态的时间占比在这一刻为 23.87%。

日志:是对特定时间发⽣的事件的⽂本记录。日志一般是非结构化字符串,会在程序执行期间被写入磁盘。每个请求会产生一行或者多行的日志,每个日志行可能包含 1-5 个维度的有用数据(例如客户端 IP,时间戳,调用方法,响应码等等)。当系统出现问题时,⽇志通常也是工程师⾸先查看的地⽅。常见的日志格式是下面的样子。

127.0.0.1 - - [24/Mar/2021:13:54:19 +0800] "GET /basic_status HTTP/1.1" 200 97 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"

链路追踪:有时候也被称为分布式追踪(Distributed Tracing),表示请求通过分布式系统的端到端的路径。当请求通过主机系统时, 它执⾏的每个操作被称为“跨度”(Span)。

举个分布式调用的例子:客户端发起请求,请求首先到达负载均衡器,经过认证服务、系统服务,然后请求资源,最终返回结果;那这里面的操作就包括请求网关、身份认证、请求资源、以及返回结果等。

链路追踪一般会通过一个可视化的瀑布图展现出来。瀑布图展示了用于调试的请求的每个阶段,以及每个部分的开始时间和持续时长。

比方说,在下图这个例子里,瀑布图由 Span 组成。特定的链路追踪中的 Span 可能是根 Span(也就是最顶层的 Span),也可能是根 Span 以下的 Span。Span还可能包含 Span,这种常被称为父子关系。比如,如果服务 B 调用服务 B-1,服务 B-1 调用 B-2,那么在这条链路中,Span B 是 Span B-1 的父亲 Span,Span B-1 是 Span B-2 的父亲 Span。

图片

然而,仅仅是收集这些数据类并不能保证系统的可观测性,尤其是当你彼此独⽴地使⽤它们时。从根本上来说,指标、日志和链路追踪只是数据类型,与可观测性无关。

另一方面,这三种数据类型也有着局限性。

  1. 指标

由于指标最大的特点是聚合性,它生成的数值反映了预定义时间段内系统状态的汇总报告,在此期间处于活动状态的所有请求的行为都会汇总为一个数值,因此缺乏细颗粒度。同时这些指标很可能都是彼此不相关的,没有关联性。

例如:page_load_time 指标可能会检查在最后 5 秒间加载所有活动页面所花费的平均时间;requests_per_second 指标可能会检查任何给定服务在最后一秒内打开的 HTTP 连接数。这就导致能够挖掘的信息的颗粒度是比较粗的,如果在 5 秒内发生了一千个离散事件,从 page_load_time 指标中根本无法获取某一事件的具体情况。

当然,这并不是说指标完全没用,指标对于静态仪表板的构建、随时间变化的趋势分析、或监控维度是否保持在定义的阈值内很有用,但这些并不是可观测性,因为这些信息的颗粒度在做故障排查或根因分析时是远远不够的。

  1. 日志

日志文件本质上是分散的事件,是一大块非结构化文本,旨在方便人类阅读,但要达到这个目的,日志通常要将一个事件的所有细节分成多行文本。这样在生产环境中,日志通常散布在数以百万计的文本行中,通过使用某种类型的日志文件解析器才可以完成对它们的搜索。解析器将日志数据拆分为信息块,并尝试以有意义的方式对它们进行分组。但是,对于非结构化数据,解析变得复杂,因为不同类型的日志文件存在不同的格式化规则里(或根本没有规则)。

针对这一点的解决方案是创建结构化日志数据,例如将上面的日志解析成下面这样。

结构化日志是机器可解析的,如果它们被重新设计为类似于结构化事件的话,可以帮助我们实现可观测的目标。关于结构化事件,后面还会做进一步介绍。

"fields": {
  "agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36",
  "browser":"Chrome",
  "browserVer":"89.0.4389.72",
  "bytes":97,
  "client_ip":"127.0.0.1",
  "engine":"AppleWebKit",
  "engineVer":"537.36",
  "http_method":"GET",
  "http_url":"/basic_status",
  "http_version":"1.1",
  "isBot":false,
  "isMobile":false,
  "message":"127.0.0.1 - - [24/Mar/2021:13:54:19 +0800] "GET /basic_status HTTP/1.1" 200 97 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"",
  "os":"Intel Mac OS X 11_1_0",
  "referrer":"-",
  "status":"OK",
  "status_code":200,
  "ua":"Macintosh"
},
  1. 链路追踪

链路追踪检测的主要问题是,如果仅靠开发人员“插桩”(英文 Instrument,有些地方也翻译成埋点,是指将有关系统状态的数据发送到监测系统)他们的代码是不够的。大量应用程序是使用可能需要额外工具的开源框架或库构建的。这在多语言架构的地方变得更加具有挑战性,因为要考虑到每种语言、框架和协议的不同。

同时,增加插桩的成本也是比较高的,很难真正做到全面覆盖。这样的方式只适用于具体的业务场景,如果其他地方有类似的需要,就需要再次插桩。而且随着产品的不断迭代,我们很难一次性把需要插桩的地方都考虑周全,这就会带来反复的工作,也可能会涉及多次上线,增加了工作量的同时也降低了系统的可靠性。另一方面,大量的插桩也会占用比较高的计算资源。

总之,指标、日志和链路追踪只是数据的类型,本身并不代表可观测性。可观测性也不是供应商提供的一种技术,而是你构建的系统的属性,就像可用性、高可用性和稳定性这些一样。

设计和构建可观测系统的目标是确保系统运行时,操作员可以检测到服务停机、错误和响应缓慢等不良行为,并可以通过足够的信息来确定问题的根本原因。

可观测性的特性

就像我在前面介绍的,我们对软件系统的“可观测性”的定义是一种度量能力,能够帮助你更好地理解和解释系统当前所处的任何状态,无论这种状态或者问题是否在之前出现过。而结构化的事件(Structured Events)就是可观测性的基础。

事件指的是特定请求与服务交互时所有信息的记录,通过事件能了解生产环境中服务所受到的影响。

那什么是结构化的事件呢?

在请求第一次进入服务时,会有一个空的地图(Map)被初始化出来。在该请求的生命周期内发生的任何细节(包括唯一的 ID、变量值、标头、请求传递的每个参数、执行时间、对远程服务的任何调用、这些远程调用的执行时间),或任何可能在之后的调试中有价值的上下文,都会附加到这个地图中。然后,当请求即将退出或出错时,刚刚所发生的事情都被丰富地记录了下来。写入该地图的数据被组织和格式化为键值对,以便于搜索。换句话说,这些数据就是结构化的事件。

这样做的好处是什么呢?

当你调试服务中的问题时,可以相互比较结构化事件,及时发现异常。当某些事件的行为与其他事件明显不同时,你可以尝试确定这些异常值的共同点。探索这些异常值,需要分析可能与你的调查相关的事件,按照这些事件中所包含的不同维度(甚至是不同维度的组合)进行过滤和分组。另一方面,对你有帮助的信息可能包含不特定于任何给定请求的运行时信息(例如容器信息或版本信息),也包含有关通过服务的每个请求的信息(例如购物车 ID、用户 ID 或会话令牌等等)。这两种类型的数据都对调试很有用。

所有这些数据都可以用于调试并存储在你的事件中。它们是任意“宽度”的事件,因为你需要的调试数据可能包含大量字段,或是来自任意维度,而不应该有实际限制。如果要分析一个异常的状态,具有可观测性的调试方式就是尽量保留每一个请求的上下文,这样你就可以针对这个上下文分析定位修复这个Bug或者调整相关的环境配置了。

所以我们说,数据的高基数和高维度,这将成为能够发现隐藏在复杂系统架构中的其他隐藏问题的关键组成部分。我们分开来看一下。

基数的作用

在数据库的概念中,基数是指包含在一个集合中的唯一值的数量。低基数意味着这一列在其集合中有很多重复的值;高基数意味着该列包含很大比例的完全唯一的值。

举例来说,在一个包含1亿条用户记录的集合中,任何通用唯一标识符(UUID)都是高基数的,另外用户名也具有很高的基数(当然会低于UUID,因为有些名称可能是重复的)。另一方面,像性别这样的领域的基数就会很低。再举个例子,假设所有用户都是人类,像物种这样的字段可能具有最低的基数。

基数对于可观测性很重要,因为高基数信息在调试或理解系统的数据时是最有用的。如果能够按照这些字段,例如 userid、cartid、requestid 或任何其他 ID (host、container_name、hostname、version、span 等),根据其中的唯一 ID 来查询数据,是在“大海”中精确定位每一滴“水滴”的最佳方法。你总是可以通过聚合采样高基数的值获得较低基数的值(例如,通过首字母存储姓氏),但没法反过来。

维度的作用

基数指的是数据中值的唯一性,维度指的则是数据中键(key)的数量。在可观测系统中,遥测数据被生成为任意“宽度”的结构化事件,它们可以而且应该包含数百甚至数千个键值对(即维度)。事件范围越广,事件发生时获取的上下文就越丰富,在以后调试时,就越容易定义问题的原因。

假设你有一个事件模式,每个事件定义了六个高基数维度:时间、应用、主机、用户、端点以及状态。通过这六个维度,你可以创建查询,分析任何维度组合,以发现可能导致异常的相关模式。例如,你可以检索:“过去半小时内,发生在主机 host001 上的所有的502错误请求”,或是“由用户 vipuser001 在做数据导出时产生的所有403错误请求”。

也就是说,只需六个基本维度,你就可以通过一组有用的条件,来确定你的应用程序系统中可能发生的情况。但是在现代系统中,可能发生的故障的排列方式是无限的,只在传统监控数据中捕捉几个基本维度是不够的。现在想象一下,除了六个维度之外,你还可以关注数百乃至数千个包含无数细节、值、计数器或字符串的维度,这些维度在将来的某个时候可能对你的调试有帮助。例如,你可以包含像这样的维度:

create_time
component
date_ns
duration
endpoint
env
http.route
host
operation
parent_id
pid
resource
service
servlet.path
source
source_type
start
span_id
span_type
status
trace.id
thread.id
thread_name
version

有了更多可用的维度,你就可以检测各种事件,在任何一组服务请求之间建立高度复杂的关联了。数据的维度越高,就越有可能发现应用程序行为中隐藏的、难以捉摸的模式。在后面的章节,我们还会更详细地讲解这部分内容。

小结

好了,这节课就讲到这里,我来小结一下。

尽管“可观测性”这个专有名词已经出现几十年了,但在软件系统中它还是一个新事物,它带来了一些新的考虑和特性。可观测性的出现,其实也刚好符合计算机领域现阶段的需求,由于现代系统引入了额外的复杂性,系统的故障比以往任何时候都更难预测、检测和修复。

为了减轻这种复杂性,工程团队现在必须能够以灵活的方式不断收集遥测数据,及时调试问题,而不需要首先预知故障可能如何发生。可观测性让工程师能够以灵活的方式分析遥测数据,快速找到未知问题的根源。

可观测性通常被错误地描述为包含指标、日志和追踪的“三个支柱”,但其实这些只是遥测数据类型。如果我们必须拥有可观测性的三个支柱,那么它们应该是支持高基数、高维度和可探索性工具。下节课,我们会探讨可观测性与传统系统监控方法的不同之处。

课后题

在这节课的最后,留给你一道思考题。

你在使用监控工具对系统和应用进行监控的时候,遇到过哪些难以依靠单纯的监控来解决的问题?后来是如何找到问题原因的?

欢迎你在留言区和我交流讨论,我们下节课见!

精选留言

  • LYy

    2022-10-14 21:27:23

    尝试做个总结:
    · What:
    老师这节课试图在为我们正本清源,把可观测性从大家认知里中具体的Metrics、Logs、Tracing这些实现层的概念抽象成通用的为了理解和解释系统所处状态的能力。同时指出这种能力应该做到不需要修改代码就可以观察到所有已知/未知状态,这就要求可观测在设计阶段就应该被当做一个质量属性(指业务维度之外的系统复杂度,如高可用、高性能、安全等)得到合适的设计。
    · How:
    回答完"复杂性是什么?"这个"What"之后,老师提出了基于结构化事件(Structed Events)来实现可观测性的理论,并且说明高基数(特异性)和高维度(丰富度)的结构化事件是我们设计时应该追求的目标。
    · Why not?
    精炼的概括Metrics、Logs、Tracing的本质和弊端,指出有了"三大件"也不一定能真的懂咱们的系统:
    - Metrics: 一段时间内的数值。聚合性强,但是粒度粗、关联性差;
    - Logs: 一次事件的记录。离散度高、缺少结构化,难于收集和理解;
    - Tracing: 一次请求的E2E路径。全覆盖难(受限于框架、语言等生态因素)、维护/运行成本高。

    总结完期待后面能有具体的基于结构化事件的可观测落地案例~
    这里提一个问题,当前基于"三大件"也有"指标异常圈定问题范围" -> "链路追踪找到问题环节" -> "报错日志发现问题原因"这样看起来很美的方法论,理论上也能达到快速发现问题的效果,这种系统构建可观测性的价值是否就没那么高了?
    作者回复

    感谢你的分享!比较全面,其他同学也可以一起看看。

    有关最后的问题,可观测性并不是某种工具提供的某种能力,而是能真正达到在分析系统状态或者问题时候,数据的关联和多维度分析,帮助定位问题原因。如果还是几个工具的整合,需要人工在工具间跳转和并搜寻判断,那这样的效率会打很多折扣。

    2022-10-18 08:32:40

  • 运维夜谈

    2022-09-24 22:20:11

    老师好,在谈到可观测性时,指标、日志、链路是基础,而指标里看到比较多的是在说一些通用性指标,但我觉得是不是还得结合业务来设置监控指标或者说是事件型告警,举个例子:一个文件传输和入库的场景,我们需要知道文件入库是否有延迟,这时候我们可以监控数据库或者磁盘目录,但是延迟的原因是什么告警不会说明,所以结合业务情况,可以再添加数据库连接失败告警、文件传输进程告警、上游是否产生文件的告警等等,我们可以通过添加很多维度的告警来判断问题出现在哪里。
    可观测性的目的也是为了尽快找到故障点,所以我觉得监控指标或者告警设置的更全面更容易知道故障源头,不知道我理解对不对?
    但是,告警设置太多,就会导致源头出现故障,会有大量后续环节的告警产生,容易产生告警风暴,想请教老师这个问题有什么解决思路?
    作者回复

    感谢分享!你说的没错,是需要根据系统和服务的不同类似,来从不同维度建立告警。告警风暴,一般是可以从分组、抑制、静默等角度去降低,而另一方面,在我后面的课程中也会讲到,最重要的告警应该是和用户使用相关,应该关注真正影响到用户体验的指标。

    2022-09-27 10:14:53

  • Geek_fa3bb6

    2022-09-18 00:27:20

    目前我们的监控都是一些基础指标的采集与告警,很多时候我们只是根据监控解决网络以及配置问题,在具体到应用业务故障时,我们就捉襟见肘,我们都需要通过metrics、logs、traces转一圈回来,然后再和业务方讨论才能确定问题根因

    老师,听下来,可观测性似乎通过不断地收集多元多维的数据,然后通过数据做分析来得出根因,那么对存储、延迟等要求就高了?
    作者回复

    可观测的数据采集确实更多维度,对后端存储性能要求更高,包括不同数据类型的存储

    2022-09-21 22:12:16

  • includestdio.h

    2022-09-15 07:38:15

    我们业务部署在k8s中,出现的故障经常是雪崩式的,每次故障的复盘都可以通过传统监控系统收集到大量故障时候的信息,但是最难的是没有人能把这些故障信息有理有据的串联起来,这一部分的工作完全靠直觉和推测,目前看起来可观测性好像可以解决我的问题
    作者回复

    可观测性的目标就是保障系统可靠性,快速找到问题的根本原因

    2022-09-15 21:17:05

  • 2022-09-17 17:45:27

    可观测下性是系统的属性,方法论。依赖 指标、日志和追踪 等数据类型进行分析
    作者回复

    归纳得很到位!

    2022-09-17 23:09:31

  • 耿安鹏

    2022-09-16 00:03:32

    大量的插桩增加了工作量的同时也降低了系统的可靠性,同时大量的插桩也会占用比较高的计算资源,中间件的插桩代码如何解决,老师这个问题有啥思路呢?
    作者回复

    这里我主要是想表明不能仅仅局限在应用链路的插桩之上,也需要关注其他维度的数据采集和联合。

    2022-09-17 16:25:31

  • __PlasticMan

    2024-06-05 20:10:05

    总结:介绍了三大支柱的局限性,主要是粒度和成本方面,同时提出了一种宽事件的方案,增强了对数据的探索和分析能力,但没有构建宽事件的细节,从三大支柱吗?
  • Geek_fa3bb6

    2022-12-13 13:35:37

    “为了减轻这种复杂性,工程团队现在必须能够以灵活的方式不断收集遥测数据,及时调试问题,而不需要首先预知故障可能如何发生” 这句话还是太明白,这里提到 “灵活的方式” 是指什么?以及 “不需要首先预知”,难道在收集遥测数据的时候,不需要关注如何插桩吗?,感觉一句很包含很多冲突
    作者回复

    “灵活的方式”是指能从各种角度,设定各种标签,从而在之后分析数据的时候能够从各种维度分析。“不需要首先预知”,也是和传统监控的区别,因为现代化系统架构复杂,很多时候发生问题,可能之前都没有遇到过,也没法把所有情况就先预估出来。这个和插桩并不矛盾,插桩是需要的,但也不可能把所有可能出现问题的地方都事先进行插桩,重要的还是能够在出现问题的时候,进行各维度的分析以及快速定位。

    2022-12-19 11:50:49

  • Geek_42abae

    2022-11-09 08:53:20

    1,监控维度不全,比如缺少业务监控,只有服务,中间件,基础设施类的。
    2,链路不全,请求经过不同的网络层,比如SLB,WAF这部分就不太容易跟踪。另外经过中间件的也容易缺失,MQ,DB,CACHE等,再有就是发起的异步分支跟踪困难
    3,缺少关联分析,很多告警是表面现象,找到根因困难
    4,缺少all-in-one产品
    作者回复

    关联分析和 All-in-One 产品可以看下后续的实战课程,会有示例的介绍。业务的监控需要和业务一起来进行分析,是需要有一些自定义的插桩

    2022-11-30 13:09:12

  • 刘小浩

    2022-09-27 09:45:05

    “如果我们必须拥有可观测性的三个支柱,那么它们应该是支持高基数、高维度和可探索性工具。”
    但如今比较火的prometheus,如果暴露数据具备高基数和高纬度的话,经常会挂。但业务方又对prometheus工具较熟悉。问题:
    1、如何解决这种高基数和高纬度问题
    2、对于服务来说,那些维度是有用的,那些是没用的
    作者回复

    可观测性数据的体量确实比以往要大很多。但数据的采集还是建议更加全面,这样在分析问题的时候才更有效率。所以从另一方面来说,数据存储也是在不断更新换代,可以考虑更高性能的方案,比如VictoriaMetrics等。

    2022-09-28 08:34:58

  • 花花大脸猫

    2022-09-22 08:42:53

    这一篇文章有点像纯理论性的描述了,我觉得老师可以带一下,然后具体的可以给一个官方的链接。
    作者回复

    前面会多说一些概念,后面就会有实战的课程

    2022-09-22 20:22:23

  • peter

    2022-09-15 09:12:41

    请教老师几个问题:
    Q1:本专栏会讲Prometheus的使用吗?
    Q2:Prometheus和skywalking是不是同类的产品?
    Q3:OpenTelemetry是个具体的产品吗?
    作者回复

    Prometheus不是本课的重点,它更多的只是一个监控工具,SkyWalking是应用性能监测工具也就是APM的维度。OpenTelemetry会在第四讲中介绍。

    2022-09-15 21:12:37