在软件开发行业,可观测性(observability)这个话题已经引起了很多人的兴趣,并且经常出现在各种热门研讨会的话题列表之中。但是,当一个热门的新话题从引起人们的兴趣转变为人们愿意真正应用它时,事情就变得不一样了。如果不能深入洞察和理解一个名词真正的细微含义,就可能会导致大量误解。本章着眼于术语“可观测性”的数学起源,同时查看软件开发者们如何把这个方法论应用到生产软件系统中。
我们还将带领大家了解在生产软件系统中引入可观测性的必要性。调试软件应用程序内部状态的传统实践方式是为传统系统设计的,那时的系统比今天的系统要简单很多。随着系统架构、基础设施技术和用户需求的不断发展,我们用来调试的软件工具却几乎没有发生变化。总的来说,今天许多工程团队仍然在沿用20年前的早期(nascent)监控工具来进行调试实践——尽管他们当前管理的系统要复杂得多。当传统的工具和调试方法无法胜任快速发现隐藏的、难以定位的问题的任务时,可观测性工具也就应运而生了。
本章将帮助你理解什么是“可观测性”、如何确定一个软件系统是否“可观测”(observable)、为什么需要可观测性,以及如何使用可观测性来发现其他方法无法发现的问题。
“可观测性”一词是由工程师鲁道夫·E.卡尔曼(Rudolf E. Kálmán)在1960年创造的。从那以后,它便在不同的领域有了不同的含义。在讲述现代软件系统的可观测性定义之前,让我们先了解一下广义的可观测性。
在1960年的论文中,卡尔曼引入了一个他称为“可观测性”的特征来描述数学下的控制系统 [1] 。在控制论中,可观测性被定义为一种度量:可以根据系统的外部输出信息推断出系统内部状态的好坏。
可观测性的这个定义将把可观测性和可控制性作为数学对偶来研究,通常关联着传感器、线性代数方程组和形式化方法。当机械工程师研究现实世界的物理系统时,可观测性的这种传统定义会很自然地出现在他们的脑海里。
如果你正在寻找数学和机械工程方面的教科书,那么这本书不适合你。这些书肯定是存在的,并且任何机械工程师或控制系统工程师都会(通常是热情而详尽地)告诉你,在传统的系统工程师术语中,可观测性有一个正式的含义。然而,当同样的概念适用于更复杂的虚拟软件系统时,它开启了一种完全不同的理解你所编写的代码并与之交互的新方式。
卡尔曼的可观测性定义可以应用到现代软件系统中。不过,当我们将可观测性的概念应用于软件系统时,还需要增加一些额外的思考来满足软件工程领域的需求。为了让一个软件应用程序具有可观测性,你必须能做到以下几点:
・ 了解应用程序的内部运行情况。
・ 了解你的应用程序可能进入的所有系统状态,甚至是你以前从未见过和无法预测的新状态。
・ 仅通过观测和使用外部工具来了解内部运行情况和系统状态。
・ 理解内部状态,而不是提供任何新的自定义代码来定义它(因为这意味着你需要事先完全了解这些内部状态)。
要判断你是否能做到这些,一种很好的方式就是问自己以下问题:
・ 能否持续回答任何关于应用内部运行机制的开放式问题,以解释所有的异常并可以做到完全无死角的覆盖(例如,发现某个问题可能与某些模糊的东西关联,但却无法进一步分解从而真正定位这个问题)?
・ 能否在任何给定的时间理解软件的任何特定用户可能正在体验的东西?
・ 能否快速地看到你关注的系统性能的任意一个层面,无论是全局的系统性能问题导致的系统缓慢,还是每一个用户实际请求中所产生的性能问题导致的缓慢?
・ 能否比较任意组的用户请求行为,从而正确地发现所有在应用程序中遇到意外行为的用户的共同属性或状况?
・ 如果你从单个用户请求中发现了可疑情况,那么是否可以在所有用户请求中搜索,以确定类似的行为模式,从而确认或排除你的怀疑?
・ 能否确定哪个系统用户产生的负载最大(因此导致应用程序性能变慢),以及第2个、第3个或第100个产生负载最多的用户?
・ 能否分辨那些最近体验糟糕的用户是不是最近才开始获得糟糕的体验的?
・ 如果第142位最慢的用户抱怨性能速度慢,能否分析他们的请求,然后发现是什么导致了该用户的性能变糟?
・ 如果用户抱怨超时故障发生,但你的图表显示第99个、99.9个,甚至99.99个百分点的请求都是快速且正常的,那么你能找到这个隐藏的超时吗?
・ 是否可以立刻回答类似前面的这些问题,而不需要事先准备(提前设置特定的监视器来聚合必要的数据)?
・ 即使以前从未见过或调试过某些遇到的问题,你也能回答关于应用程序的这些问题吗?
・ 能否快速得到上一个问题的答案,这样你就可以持续迭代地问一个新问题,一个又一个,直至找到导致问题的正确原因,而不会中断你的思路(这通常意味着在几秒钟内得到答案,而不是花费几分钟)?
・ 即使这个问题从未发生过,一旦发生了,你能知道它是怎么发生的吗?
・ 在调试或者定位问题时通常能解决各种令人困惑的奇怪问题,还是只能处理那些你知道发生原因的问题?
・ 能否快速地(在几分钟内)隔离系统中的任何错误,无论这些错误多么复杂,甚至是深埋或隐藏在程序的堆栈中?
对于许多软件开发团队来说,遵循上述准则是一个很高的门槛。如果你已经知道怎么去回答上面的问题,那么你无疑就理解了为什么可观测性已经成为软件工程团队的一个流行话题。
简单地说,我们对软件系统的“可观测性”的定义是:一种度量能力,能帮助你更好地理解和解释系统当前所处的任何状态,无论这种状态多么新颖或怪异。你必须能够在特定的迭代调查中,跨系统状态数据的所有维度和维度组合,比较调试这种怪异或新颖的状态,而无须事先定义或预测这些调试需求。如果你无须发布新代码(如增加一个用于调试的日志)就可以理解任何新颖或怪异的状态,那么你的系统就具备可观测性。
我们相信,以这种方式将可观测性的传统概念引入软件工程中是一种独特的方法,值得我们去深入研究其中的细微差别。对于现代软件系统,可观测性与数据类型或输入无关,也与数学方程式无关。它的本质是关于人们如何与他们复杂的系统互动并尝试理解它们。因此,可观测性需要发现人与技术之间的交互,以理解这些复杂系统如何一起工作。
如果你接受这个定义,那么看看接下来这些问题:
・ 如何收集数据并将其组合起来进行分析?
・ 处理这些数据的技术要求是什么?
・ 要从这些数据中获益,团队的哪些能力是必要的?
我们将在本书后面进一步探讨这些问题。现在讨论将可观测性应用于软件时,一些额外的上下文内容。
软件系统中的可观测性与控制论中的原本定义有许多共同之处。然而,它不需要那么数学范,而且更实用。从某种程度上来说,相比于成熟的机械工程,软件工程本身是一门更年轻、发展更快的学科。软件系统的开发并不需要物理公式计算。在某种程度上,缺乏严谨性反而是软件行业没有更早引入可观测性所产生的一个历史伤疤。
过去,当工程师试图理解如何弥合在本地测试中编码的理论运行状况和正式环境中大规模运行代码时所产生的影响之间的差距时,我们没有去寻找一个新的术语、定义或功能来描述我们是如何做到这一点的。当前管理系统和团队的环境与方式导致我们将实践从传统概念(如监控)中演进出来,而这些传统概念其实已经不再有效。作为一个行业,我们需要超越工具和术语方面的实际差距,以克服停机和缺乏更主动的解决方案所带来的痛苦。
可观测性是解决这个差距的方法论。受各种技术和社会因素影响,复杂的生产软件系统可能变得一团糟。因此,需要社会和技术两方面的解决方案来把我们从这个困境中解救出来。可观测性本身并不是所有软件工程问题的完整解决方案。但它确实帮助你清楚地看到在软件的所有阴暗角落发生了什么,否则你通常会在黑暗中磕磕绊绊,试图通过脑补来理解事物。
想象一下,你在一个周六早上醒来,计划着为整个大家庭做早午餐。你脑子里有好几道菜,包括奶酪蛋奶酥的复杂配方、每个人已知的过敏源和食物敏感性的清单,以及紧张的时间安排——奶奶必须在中午之前赶到机场,这本身就是一个不小的挑战。现在想象一下,你找不到你的眼镜(而且你和我们一样近视)。其实在软件工程中每天都会遇到类似的问题,要让生活有序一点,可观测性是一个很好的起点。
在继续之前,我们需要定位“可观测性”的另一个定义,这个定义正在被一部分软件即服务(SaaS)开发者工具供应商广泛推广。这些供应商坚持认为“可观测性”没有任何特殊含义——它只是“遥测”(telemetry)的另一个同义词,与“监控”没有区别。这个定义的支持者将可观测性降级为运维软件的另一个通用术语。你肯定会听到这种解释,即将可观测性解释为今天他们已经在做的事情的“三大支柱”:指标(metrics)、日志(log)和链路(trace)
。
很难判断哪个定义更糟糕:它是冗余的(为什么我们需要发明“遥测”的另一个同义词?)或者它的认知是混乱的(为什么要汇编一个由一种数据类型、一种反数据类型、一堆字符串和一种按时间顺序可视化事物的方式组成的列表)。无论如何,当你意识到该定义的支持者在出售工具以及基于现有指标、日志和链路工具收集和存储数据的思维方式时,该定义的逻辑缺陷就变得明显了。这一定义的支持者的业务模型限制了他们对未来可能性的思考。
公平地说,我们——本书的作者——也是可观测性产品的供应商。然而,本书并不是为了推销我们的工具而编写的。我们写这本书的目的是解释我们如何以及为什么将可观测性的原始概念应用于管理现代软件系统。无论选择什么工具,你都可以应用本书中的概念来练习构建具有可观测性的生产软件系统。可观测性不是通过将不同的工具与市场营销结合在一起实现的。你不必采用一种特定的工具来获得软件系统中的可观测性。相反,我们认为可观测性要求我们思考如何收集有效调试所需的数据。我们相信,作为一个行业,是时候发展我们用来管理现代软件系统的实践了。
既然我们已经就可观测性在现代软件系统环境中到底是什么达成了一致,那么让我们来讨论一下为什么这种方法上的转变现在很重要。简而言之,使用指标和软件监控来了解软件正在做什么的传统方法远远不够。这种方法基本上是被动的。它可能在过去很好地服务于行业,但现代系统需要更好的方法。
在过去的二三十年里,硬件和运维操作人员之间一直由一套被大多数人称为“监控”的工具和惯例来管理。从业者基本上继承了这组工具和惯例,并将其视为理解物理和代码之间的模糊虚拟空间的最佳方法。他们已经接受了这种方法,尽管在很多情况下,其固有的局限性让他们不得不在解决问题的过程中度过了许多不眠之夜。然而,他们仍然给予它充分的信任,甚至可能是情感,因为这套方案是他们拥有的最好的手段。
使用监控,软件开发人员不能完全看到系统。开发人员“斜视”着系统,并且尝试各种方式,如增加备份、提高容量,并预测可能失败的各种方式,但都是徒劳的。然后观察并且监控那些已知的故障模式。他们会设置性能阈值,并随意地将其读作“好”或“坏”。开发人员通过部署一个小型机器人军队,一遍又一遍地检测这些阈值,把发现收集到仪表盘上。之后,围绕这些机器人组织成团队、轮岗和升级。当这些机器人表现不好时,开发人员会提醒自己。随着时间的推移,他们倾向于像园丁一样对这些阈值进行修剪、调整,并对生长的噪声信号大惊小怪。
几十年来,开发者和运营者都是这么做的。长期以来,监控一直是事实上的方法,以至于他们倾向于认为这是理解系统的唯一方法,而不是方法之一。监控是一种默认的做法,甚至它基本上是不可见的。在行业里,大家通常不会问是否应该这么做,而是问如何去做。
监控的很多实践都是基于许多无法有效说明的假设系统(将在下文中详细说明)。但是随着系统的不断演化——系统变得越来越抽象和复杂,底层组件开始变得越来越不重要——这些假设就变得不那么真实了。随着开发人员和运维人员继续采用现代方法部署软件系统(SaaS API、容器编排平台、分布式系统等),这些假设中的裂缝变得更加明显。
因此,越来越多的人发现自己撞上了固有局限性的墙,并意识到监控方法根本不适合新的现代世界。传统的监控实践对于理解系统来说毫无效用。指标和监控的设定现在还不够。为了理解失败的原因,需要研究它们的历史和预期的背景。
1988年,根据简单网络管理协议(Simple Network Management Protocol,SNMPv1;RFC 1157中定义),监控的基础底座诞生了:指标(metric)。指标是单个数字,可以选择添加标记,以对这些数字进行分组和搜索。从本质上看,指标是一次性且低成本的。它们具有可预测的存储空间。它们很容易沿着规则的时间序列聚合。因此,指标成了一两代遥测的基本单位——我们从遥远的端点收集数据,然后自动传输到监控系统。
许多复杂的监控系统的能力都建立在这个指标的基础上:时序数据库(TSDB)、统计分析、图形库、花哨的仪表盘、保证响应的轮岗、运维团队、事件升级策略,以及消化和响应机器人大军发给你的各种告警消息。
但是,你可以通过指标和监控工具理解的系统的复杂性存在一个上限。一旦你跨越了这个界限,事情就会变得很出乎意料。上个月运行良好不代表以后也会运行良好。一旦发生了问题,你就会开始回退到底层命令,如strace、tcpdump和数百个print语句,以发现有关系统日常故障的问题。
事实上,我们很难准确计算出何时会达到临界点。最终,系统自身可能进入的那个状态将超过团队基于先前设定的运维模式。有太多无法预知的、古怪的系统状态需要我们不断去理解。团队再也无法提前判断应该创建哪些仪表盘来应对无数的故障模式。
基于监控和指标的工具是基于对架构和组织的某些假设构建的,这些假设在实践中作为复杂性的上限。这些假设通常是看不见的,直到你超越它们,那时它们就不再隐藏,并成为你理解正在发生的事情的能力的“祸根”。其中一些假设可能如下:
・ 你所管理的是一个单体应用。
・ 只有一个状态数据存储(“数据库”)运行。
・ 有许多低级别系统指标可用,例如常驻内存和平均CPU负载。
・ 应用程序运行在容器、虚拟机(VM)或裸机上,这些都是可控的。
・ 系统指标和仪表指标是调试代码的主要信息来源。
・ 需要监视一组相当静态且长时间运行的节点、容器或主机。
・ 工程师只在问题发生后才检测系统的问题。
・ 仪表盘和遥测技术的存在只是为了满足运维工程师的需求。
・ 监控发现这些“黑盒”应用程序的问题的方式与对待桌面应用程序的方式非常相似。
・ 监控的重点是正常运行时间和故障预防。
・ 相关性指标检测发生在有限的或少量的维度。
很明显,与现代系统的真实情况相比,传统的监控方法在若干方面都有不足之处。现代系统的真实情况如下:
・ 应用程序是多服务的。
・ 存在混合持久化(即多种数据库和存储系统)。
・ 基础设施是非常动态的,容量可弹性伸缩。
・ 管理了许多分布广泛的松散耦合服务,其中许多服务不在直接控制之下。
・ 工程师需要积极检测产品代码的变化情况,以便在对用户造成影响之前尽早发现细微问题。
・ 单纯靠仪表不足以理解复杂系统中发生了什么。
・ 软件工程师为自己所写代码运行时的问题负责,并积极地测试自己的代码,且会在重新部署时主动检测其性能。
・ 可靠性的重点是如何容忍常数和持续的降级,同时通过利用错误预算(error budget)、服务质量和用户体验等形式来建立对影响用户的故障的复原力。
・ 相关性检测会关联几乎无限量的维度。
最后一点很重要,因为它描述了思考现代系统架构的复杂性所需的综合知识已经超越了一个人可以合理思考的相关知识极限。在发现性能问题背后的潜在相关性方面涉及如此多的可能性,以至于没有人类大脑可以容纳这些知识。
通过可观测性,比较高维度和高基数的数据才可能成为发现埋藏在复杂系统架构中其他隐藏问题的关键组成部分。
当系统复杂性超过了临界点,就不可能再将系统模型放入你的大脑缓存中。当试图通过对它的各种组件进行推理时,你的思维模式可能已经过时了。
工程师可能习惯于通过直觉进行调试。要找到问题的根源,你可能会根据直觉甚至使用很久以前的设置中断等方式来调试。然而,过去的这些方式在这个世界上已不再适用。只有当遇到的大多数问题都是过去曾遇到过的问题时,直觉方法才会起作用 [2] 。
类似地,基于指标的监控方法依赖于在过去遇到的已知故障模式。监控有助于检测系统何时超过或低于预先认为异常的可预测阈值。但是,如果出现了完全无法预知的某些异常呢?
从历史上看,软件工程师遇到的大多数问题都是一些可预测的故障。也许你不知道软件会以何种方式出现故障,但如果对相关情况及其组件进行推理,那么发现一个新的错误或故障根因并不需要逻辑上的跳跃。大多数软件开发人员很少遇到真正不可预测的逻辑跳跃,因为他们通常不需要面对这一类复杂性的情况(到目前为止,开发人员的大多数复杂性都被锁定在单体应用中)。
每个应用程序都有其固有的不可减少的复杂性。唯一的问题是:谁来处理它——用户、应用程序开发人员还是平台开发人员?
——Larry Tesler
众所周知,现代分布式系统架构会导致一种无法预测且以之前没有遇到过的方式出现的故障。这种情况经常发生,尤其是刚接触分布式计算的程序员,经常会做出全局的错误判断。应用程序开发人员还会将现代分布式系统作为抽象的基础设施平台进行使用。作为这些平台的用户,应用程序开发人员也必须处理这些分布式平台带来的问题,这些复杂性直接落在了他们的身上。
以前隐藏在一台物理机器随机访问内存内部中相互调用的应用程序代码子例程的复杂性,现在已经作为主机之间的服务请求浮出水面。同时新出现的复杂性跨越多个服务,在单个功能的请求过程中多次穿梭于不可预测的网络。当现代架构开始倾向于将整体分解为微服务时,软件工程师就失去了用传统的调试器逐级调试代码的能力。与此同时,他们的工具还没有适应这一巨变。
简而言之,我们分解了传统的单体应用。现在,每个请求都必须在网络上多次跳转,每个软件开发人员都需要更好地了解系统的运作,才能完成他们的日常工作。
透过容器化的趋势、容器编排平台(如Kubernetes)的兴起、向微服务的转变、混合持久化(多种数据库、消息队列)的普遍使用、服务网格的引入、弹性自动伸缩实例的流行、Serverless计算、Lambda函数,以及软件开发人员典型工具集中的任何其他SaaS应用程序,都可以看到这种巨变的例子。将这些不同的工具串在一起形成一个现代系统架构,意味着一个请求可能在到达你控制的代码时已经执行了20~30次跳转(如果请求包含了数据库查询,则可能跳转数需要乘以2)。
在云原生系统中,关于调试的最困难的事情不再是理解代码如何运行,而是找到有问题的代码存在于系统的何处。通过仪表盘或服务映射来查看哪个节点或服务速度较慢是不太可能的,因为这些系统中的分布式请求经常在不同的节点中循环。在这些系统中找到性能瓶颈非常具有挑战性。当某件事变慢了时,一切都变慢了。更有挑战性的是,因为云原生系统通常作为平台运行,代码甚至可能存在于该团队无法控制的系统部分。
在现代世界,任意一个请求都将跨越无数个服务与机器,产生无数请求,从而导致与这些请求相关的几十个指标产品产生分裂,如果我们需要推断在这个过程中各种请求跳转发生了什么,那么需要将如此多的相关指标连接起来。这几十条线索的有用性完全取决于是否有人能够提前预测该度量值是超过阈值还是低于阈值,这意味着该操作有助于创建以前从未遇到过的未知异常故障模式。
相比之下,通过可观测性的方式进行调试是从一个异常的状态开始。在这个状态发生的同时某些深层上下文也发生了。通过可观测性进行调试就是尽量保留每一个请求的上下文,这样你就可以针对该上下文修复这个bug或者调整相关的环境配置。监控面向的是已知问题是如何发生的,但可观测性面向的是未知问题是如何发生的。
在数据库的概念中,基数是指包含在一个集合中的唯一值的数量。低基数意味着这一列在其集合中有很多重复的值。高基数意味着该列包含很大比例的完全唯一的值。包含单个值的列总是可能具有最低的基数。包含唯一ID的列总是可能具有最高的基数。
例如,在一个包含1亿条用户记录的集合中,可以假设任何通用唯一标识符(UUID)都可能具有最高基数。高基数的另一个例子可能是公钥签名。用户名因为有些名称是重复的,尽管低于UUID,但其实也具有很高的基数。如果程序是在50年前编写的,那么诸如性别等字段的基数就会很低,但考虑到对性别的更近期的理解,也许就不再是这样了。假设所有用户都是人类,像“物种”这样的字段可能具有最低的基数。
基数对于可观测性很重要,因为高基数信息在识别用于调试或理解系统的数据时最有用。按字段[如用户ID、购物车ID、请求ID或任何其他ID(实例、容器、主机名、构建号、跨度等)]进行排序会更有效。能够根据唯一ID查询是在任何给定的“干草堆”中精确定位每一根具体“草秆”的最佳方法。你总是可以通过聚合采样高基数的值来获得较低基数的值(例如,通过首字母存储姓氏),但反之不行。
不幸的是,基于指标的工具系统在任何合理的规模下都只能处理低基数的维度。即使只有几百台主机可以比较,对于基于指标的系统,你也不可能在未触及基数的系统数量限制的情况下使用主机名作为标识标记。
这些内在的限制对查询数据的方式造成了很多意想不到的影响。在使用指标进行问题定位时,你可能需要考虑高基数的影响,在记录指标之前就开始思考如何将这些指标归集为相对的低基数以保证不触及相关的限制。
这有两大含义。首先,如果在调查过程中,你决定必须问一个额外的问题来发现潜在问题的根源,那么这不能在事后进行。你必须事先设置可能回答这个问题的指标,并等待问题再次发生以进行分析。其次,因为回答这个附加的问题需要记录另一组指标,大多数基于指标的工具供应商将为记录该数据向你收费。每采用一种新方法记录新指标以发现无法提前预测的隐藏问题,你的成本就会直线增加。
基数指的是数据中值(value)的唯一性,维度则指的是数据中键(key)的数量。在可观测系统中,遥测数据被生成为任意宽的结构化事件(参见第8章)。这些事件会非常“宽”,因为它们可以且应该包含数百甚至数千个键值对(或维度)。事件范围越广,事件发生时获取的上下文就越丰富,因此在以后调试时就能发现各种“到底发生了什么”的情况。
假设你有一个事件模式,每个事件定义了6个高基数维度:time、app、host、user、endpoint和status。通过这6个维度,你可以创建查询,分析任何维度组合,以发现可能导致异常的相关模式。例如,你可以检索:“所有502错误请求都发生在过去半小时,发生在主机foo上”或者“所有403错误请求都在/export端点,由用户bar产生”或者“所有超时的支付请求都由应用程序baz以及这个应用所在的主机发送到/payments端点”。
只用这6个维度,你就可以检测一组有用的条件来确定应用系统中可能发生的情况。现在想象一下,除了6个维度之外,你可能还需要关注成百上千个包含任何细节、值、计数器或字符串的维度,这些维度在将来的某个时候可能对调试有帮助。例如,你可以包含像这样的维度:
有了更多可用的维度,你就可以检测各种事件,从而在任何一组服务请求之间建立高度复杂的关联(参见第8章)。数据的维度越高,你就越有可能发现应用程序行为中隐藏的或难以捉摸的模式。在现代系统中,可能发生的故障的排列是无限的,仅在传统监控数据中捕捉几个基本维度是不够的。
你必须收集在用户、代码和系统的交叉点上发生的所有事情的丰富细节。高维数据将会揭示关于这些交叉点如何展开的更大背景因素。在后面的章节中,我们将介绍如何分析高维数据(通常包含高基数数据),以确定系统中你所关心的问题发生在哪里以及为什么发生。
与传统监控工具限制遥测数据的基数和维度不同,可观测性工具鼓励开发人员为可能发生的每一个事件收集丰富的遥测数据,传递任何给定请求的完整上下文,并存储它以备将来可能使用。可观测性工具是专门为查询高基数、高维数据而设计的。
因此,调试时你可以以任意方式查询事件数据。有了可观测性,你可以通过探索数据来迭代调查你关心的条件,看看它能揭示系统状态的什么信息。你问一个不需要提前预测的问题就可以找到答案或线索,这个线索会引导你问出下一个问题,下一个,再下一个。你一遍又一遍地重复这个模式,直到你如大海捞针一般找到你要找的那根“针”。可观测系统的一个关键功能是能够通过开放式的方式探索系统。
一个系统的可探索性是由你提出任何问题并检测其相应内部状态的能力来衡量的。可探索性意味着你可以迭代地研究并最终理解系统所进入的任何状态——即使你以前从未见过那种状态——而不需要提前预测那些状态可能是什么。同样,可观测性意味着你可以理解和解释系统可能进入的任何状态——无论它多么新奇或奇怪——而无须交付新代码。
监控之所以能持续这么长时间运作良好,是因为系统往往足够简单,工程师可以准确地推断出他们可能需要在哪里寻找问题,以及这些问题可能如何呈现。例如,连接点是相对简单的,当套接字填满时,CPU将超载,解决方案是通过扩展应用程序节点实例或调优数据库等增加更多容量。总的来说,工程师可以预测大多数可能的故障状态,并在应用程序投入生产后通过艰难的方式发现其余的故障。
然而,监控创造了一种完全被动的系统管理方法。你可以捕捉到你已预测和知道要检测的故障情况。如果你知道它会发生,那么你就会检测它。对于你不知道要查看的每种情况,你必须先看到它,处理这个令你不悦的意外情况,尽你所能地调查它,你可能会走入一个死胡同,要求你在正确诊断之前多次看到同样的情况,然后才可以对它进行检测。在这种模式中,工程师被反向刺激,从而对可能造成不可预测的故障的情况产生强烈的反感。这就是一些团队害怕部署新代码的部分原因(稍后将详细介绍该主题)。
还有一点,与代码或用户产生的问题相比,硬件/基础设施问题很简单。从“大多数问题都是组件故障”到“大多数问题都与用户行为或微妙的代码bug及交互有关”的转变,就是为什么即使是单体应用或更简单架构的开发者也应该努力从监控转变到可观测性。
一个生产环境中的软件系统在某种程度上是可观测的,所以你无须做出任意猜测、提前预测各种故障模式,或者发布新的代码来理解该状态就能理解其内部系统状态。这样,我们将控制论的可观测性概念扩展到软件工程领域。
在软件工程上下文中,可观测性确实为那些更传统的架构或简单系统提供了好处。能够跟踪代码并查看时间花在了哪里,或者从用户的角度再现行为,这总是很有帮助的。无论你的软件架构是什么样的,该功能无疑可以避免团队在生产环境中无法发现未预测的故障模式。但是,在现代分布式系统中,可观测性工具提供的“手术刀”和“探照灯”变得绝对不容置疑。
在分布式系统中,新的、罕见的故障模式要远多于可预知的故障模式,这些不可预测的故障模式发生得如此普遍,而且很少重复,以至于大多数团队都无法设置足够适当和相关的监控仪表盘来轻松显示系统状态,可靠性工程团队(SRE)也就无法有效地保证系统持续、稳定、可靠地运行。
我们写这本书时考虑到了这些类型的现代系统。与传统管理方法相比,任何由许多松散耦合的、动态的、难以推理的组件组成的系统都很适合发挥可观测性的优势。如果你管理的生产软件系统符合上述描述,那么本书将描述可观测性对你、你的团队、你的客户和你的业务意味着什么。我们还关注在工程过程的关键领域中开发可观测性实践所需的人为因素。
尽管术语“可观测性”已经提出了几十年,但其在软件系统中应用时还是以一种新方式,并且带来了一些新的考虑和特征。与它们更简单的早期类型相比,现代系统引入了额外的复杂性,使得故障比以往任何时候都更难预测、检测和修复。
为了减轻这种复杂性,工程团队现在必须能够以灵活的方式不断收集遥测数据,使得他们不需要首先预知故障可能如何发生就能调试故障。可观测性使工程师能够以灵活的方式分析遥测数据,从而使他们能够找到以前所未有的方式发生的任何问题的根源。
可观测性通常被错误地描述为,只有当你拥有不同遥测数据类型的“三大支柱”时才能实现,所以我们不喜欢这个模型。但如果我们必须拥有可观测性的三个支柱,那么它们应该是支持高基数、高维度和可探索性的工具。接下来,我们将研究可观测性与传统系统监控方法的不同之处。
[1] Rudolf E. Kálmán,“On the General Theory of Control Systems”( https://oreil.ly/u7BM4), IFACProceedings Volumes 1, no. 1 (August 1960):491–502.
[2] 要获得更深入的分析,请参阅Pete Hodgson的博客文章“Why Intuitive Troubleshooting Has Stopped Working for You”( https://oreil.ly/JXx0c )。