第八章 监控
正如我之前所展示的,将系统拆分成更小的、细粒度的微服务会带来很多好处。然而,它 也增加了生产系统的监控复杂性。在本章中,我将带大家看看细粒度的系统在系统监控和 定位问题上所面临的挑战,同时还会介绍一些应对方法,让鱼和熊掌兼得!
我们现在有多个服务需要监控,有多个日志需要筛选,多个地方有可能因为网络延迟而出 现问题。该如何应对呢?我们得好好梳理一下,否则很可能导致混乱,成为一团乱麻,而 这是周五下午(或在任何时间!)我们最不想面对的情况。
这里的答案很简单:监控小的服务,然后聚合起来看整体。我们从最简单的系统--个节点,来展示该如何做。
首先,我们希望监控主机本身。CPU、内存等所有这些主机的数据都有用。我们想知道, 系统健康的时候它们应该是什么样子的,这样当它们超出边界值时,就可以发出警告。如 果我们想运行自己的监控软件,可以使用Nagios,或者使用像New Relic这样的托管服务 来帮助我们监控主机。
接下来,我们要查看服务器本身的日志。如果用户报告了一个错误,这些日志应该可以告 诉我们,在何时何地发生了这个错误。这个时候,对于单台主机来说,只需要登录到主机 上使用命令行工具扫描日志就可以了。我们甚至可以更进一步,使用logrotate帮助我们 移除旧的日志,避免日志占满了磁盘空间。(日志切割之Logrotate)
最后,我们可能还想要监控应用程序本身。最低限度是要监控服务的响应时间。你可以通 过查看运行服务的Web服务器,或者服务本身的日志做到这一点。如果我们想更进一步, 可能还需要追踪报告中错误出现的次数。
随着时间的推移,负载增加,我们发现系统需要扩容……
8.2单一服务,多个服务器
现在我们服务的多个副本实例,运行在多个独立的主机上。如图8-2所示,通过负载均衡 分发不同的请求到不同的服务实例。事情慢慢变得有点棘手。我们仍然需要监控与之前一 样的内容,但为了定位问题,我们的做法会有所不同。
当CPU占用率高时,如果这个问题发生在所有的主机上,那么可能是服务的问题,但如 果只发生在一台主机上,那么可能是主机本身的问题,也许是一个流氓操作进程?
图8-2:单一服务的实例运行在多个主机上
在这种情况下,我们依然想追踪有关主机的数据,根据它们来发出警告。但现在,除了要 查看所有主机的数据,还要查看单个主机自己的数据。换句话说,我们既想把数据聚合起 来,又想深入分析每台主机。Nagios允许以这样的方式组织我们的主机,到目前为止一切 还好。类似的方式也可以满足我们对应用程序的监控。
接下来就是日志。我们的服务运行在多个服务器上,登录到每台服务器查看日志,可能会 让我们感到厌倦。如果只有几个主机,我们还可以使用像ssh-multiplexers这样的工具,在 多个主机上运行相同的命令。用一个大的显示屏,和一个grep "Error" app.log,我们就 可以定位错误了。
对干像响应时间这样的监控,我们可以在负载均衡器中进行追踪,很容易就能拿到聚合 后的数据。不过负载均衡器本身也需要监控;如果它的行为异常,也会导致问题。对于 服务本身的监控,我们可能更关心健康的服务是什么样的,这样当我们配置负载均衡器 的时候,就可以从应用程序中移除不健康的节点。希望我们到这里的时候至少有一些想 法了……
8.3多个服务,多个服务器
在图8-3中,事情变得更有趣。多个服务合作为我们的用户提供功能,这些服务运行在多 个物理的或虚拟的主机上。你如何在多个主机上的、成千上万行的日志中定位错误的原 因?如何确定是一个服务器异常,还是一个系统性的问题?如何在多个主机间跟踪一个错 误的调用链,找出引起这个错误的原因?
图8-3:互相合作的多个服务分布在多台主机上
答案是,从日志到应用程序指标,集中收集和聚合尽可能多的数据到我们的手上。
8.4日志,日志,更多的曰志
现在,运行服务的主机数量成为一个祧战。现在再使用SSH multiplexing检索日志,已经 无法缓解这个问题了,况且也没有一个足够大的屏幕显示每台主机的终端。我们希望用专 门获取日志的子系统来代替它,让日志能够集中在一起方便使用。这方面的一个例子是 logstash (http://logstash.net),它可以解析多种日志文件格式,并将它们发送给下游系统进行进一步调查。
Kibana (https://www.elastic.co/products/kibana)是一个基于 ElasticSearch 查看日志的系统,
如图8-4所示。你可以使用查询语法来搜索日志,它允许在查询时指定时间和日期范围, 或使用正则表达式来查找匹配的字符串。Kibana甚至可以把你发给它的日志生成图表,只 需看一眼就能知道已经发生了多少错误。
图8-4:使用Kibana查看聚合的日志
8.5多个服务的指标跟踪
与查看不同主机上的日志遇到的挑战类似,我们也需要寻找更好的方式来收集和查看指 标。当我们观察一个复杂系统的指标时,很难知道什么样是好的。我们的网站每秒会有大 约50条4XX的HTTP错误状态码,这是个问题吗?午餐过后,产品目录服务的CPU负载 增加了 20%,是有什么问题发生了吗?要想知道什么时候该紧张,什么时候该放松,秘诀 是收集系统指标足够长的时间,直到有清晰的模式浮现。
在更复杂的环境中,我们会频繁地重建服务的新实例,所以我们希望选择一个系统能够方 便地从新的主机收集指标。我们希望能够看到整个系统聚合后的指标(例如,平均的CPU 负载),但也会想要给定的一些服务实例聚合后的指标,甚至某单个服务实例的指标。这 意味着,我们需要将指标的元数据关联,用来帮助推导出这样的结构.
Graphite就是一个让上述要求变得很容易的系统。它提供一个非常简单的API,允许你 实时发送指标数据给它。然后你可以通过查看这些指标生成的图表和其他展示方式来了 解当前的情况。它处理容量的方式很有趣.。通过有效地配置,它可以减少旧指标的精 度,以确保容量不要太大。例如,最近的十分钟,每隔10秒记录一次主机CPU的指标, 然后在过去的一天,以分钟为单位对数据进行聚合,而在过去的几年,减少到以30分钟 为单位进行聚合。通过这种方式,你不需要大量的存储空间,就可以保存很长一段时间 内的信息。
Graphite也允许你跨样本做聚合,或深入到某个部分,这样就可以查看整个系统、一组服 务或一个单独实例的响应时间。如果由于一些原因,你不能使用Graphite,在选择其他任 何工具时,要确保这些工具具备跟Gmphite类似的功能。另外,要确保你可以获得原始的 数据,以便在需要之时生成自己的报告或仪表盘。
了解趋势另一个重要的好处是帮助我们做容量规划。我们的系统到达极限了吗?多久之后 需要更多的主机?在过去,当我们还在使用物理主机时,通常一年才会考虑一次这个问 题。在供应商提供按需计算的IaaS (Infrastructure as a Service,基础设施即服务)的新时 代,我们可以在几分钟内(如果不是秒级的话)实现扩容和缩容。这意味着,如果了解我 们的使用模式,就可以确保恰好有足够的基础设施来满足我们的需求。在跟踪趋势和理解 应该如何使用这些数据方面,使用的方式越智能,我们的系统就越省钱,而且响应性也就 越好。
8.6服务指标
当你在Linux机器上安装collectd并让它指向Graphite时,会发现我们运行的操作系统会 生成大量的指标。同样,像Nginx或Vamish这样的支撑子系统,也会暴露很多有用的信 息,例如响应时间或缓存命中率。不过,我们自己的服务呢?
我强烈建议你公开自己服务的基本指标。作为Web服务,最低限度应该暴露如响应时间和 错误率这样的一些指标。如果你的服务器前面没有一个Web服务器来帮忙做的话,这一点 就更重要了。但是你真的应该做得更多。例如,账户服务会想要暴露客户查看过往订单的 次数,而网络商店可能希望知道过去的一天赚了多少钱。
为什么我们要关心这个呢?嗯,原因很多。首先,有一句老话,80%的软件功能从未使 用过。我无法评论这个数字是否准确,但是作为一个已在软件行业工作近20年的程序员, 我知道自己花了很多时间在一些从未被真正使用的功能上。如果能知道这些未被使用的功 能是什么,不是很好的事情吗?
其次,可以通过了解用户如何使用我们的系统得知如何改进,在这个方面,我们比以往任 何时候做得都要好。指标可以反映出系统的行为,因此在这个方面可以帮助我们。当我们 发布网站的一个新版本后,发现在产品目录服务上根据类型搜索的数量大幅上升。这是一 个问题,还是我们的期望?
最后,我们永远无法知道什么数据是有用的!很多次,直到机会已经错过很久后,我才发 现如果当时记录了数据,事情就容易理解得多。所以我倾向于暴露一切数据,然后依靠指 标系统对它们进行处理。
很多平台都存在一些库来帮助服务发送指标到一个标准系统中。Codahale的Metrics库 (http://metrics.codahale.com/)就是这样一个运行在JVM上的库。它允许你存储一些指标, 例如计数器、计时器或计量表(gauge);支持带时间限制的指标(这样你就可以指定如 “过去五分钟的订单数量”这样的指标);它还为将数据发送到Graphite和其他汇总报告系 统提供现成的支持。
8.7综合监控
我们可以通过定义正常的CPU级别,或者可接受的响应时间,判断一个服务是否健康。 如果我们的监控系统监测到实际值超出这些安全水平,就可以触发警告。类似像Nagios这 样的工具,完全有能力做这个。
然而,在许多方面,这些测量结果离我们真正关心的内容仍有一步之遥,即系统是否在正 常工作?服务之间的交互越复杂,就越难回答这个问题。如果我们的监控系统能像最终用 户那样及时地发现并报告问题,那该多好!
我第一次做这个尝试是在2005年。当时我是ThoughtWorks小团队中的一员,该团队为一 家投资银行构建系统。整个交易日中,代表市场变化的大量事件涌入。我们的工作就是响 应这些变化,了解它们对银行投资组合的影响。我们的上线日期相当紧迫,因为目标是当 事件收到后,在10秒时间内完成所有的计算。系统本身大约有五个独立的服务,其中至 少有一个运行在一个计算网格中,这个网格除其他事情外,还会循环利用银行灾备中心大约250台中未被使用的桌面主机的CPU
系统中存在大量的组件,意味着我们会收集到很多低层次的指标,它们会带来很大的噪 声。我们没有机会逐渐扩展系统,或让系统运行几个月来理解什么样的指标是好的,例如 CPU率或一些单个组件的延迟等。我们的方法是,对下游系统没有登记的部分投资组合, 生成假的价格事件。每一分钟左右,Nagios执行一个命令行任务,插入一个假事件到队列 中。我们的系统会正常响应这个事件并做相应处理,唯一不同的是结果仅用于测试。如果 在给定的时间内没有看到重新定价,Nagios会认为这是一个问题。
我们创建的这个假事件就是一个合成事务的例子。使用此合成事务来确保系统行为在语义 上的正确性,这也是这种技术通常被称为语义监控的原因。
在实践中,我发现使用这样合成事务执行语义监控的方式,比使用低层指标的告警更能表 明系统的问题。当然,它们不会取代低层次的指标,那些细节有助于我们了解为什么语义 监测会报告问题。
实现语义监控
在过去,实现语义监控是一个相当困难的任务,但现在这个事情简直就是信手拈来!你的 系统有测试的,对吧?如果没有,请阅读完第7章再回来。现在有测试了吧?很好!
系统中存在计对指定服务的端到端测试,甚至是针对整个系统的端到端测试,仔细看看就 会发现,这些都是实现语义监控所需要的。而且,我们的系统已经开放了启动测试和查看 结果所需要的钩子(hook)。所以,为什么不在运行的系统上运行这些测试的子集,作为 系统语义监控的一种方式呢?
当然,还有些事情需要我们做。首先,要非常小心地准备数据以满足测试的要求。我们的 测试需要找到一个方法来适配不同的实时数据,因为这些数据会随着时间的推移而改变, 或者设置一个不同数据来源。例如,我们可以在生产环境上设置一组假用户和一些已知的 数据集。
同样,我们必须确保不会触发意料之外的副作用。一个朋友告诉我,有一个电子商务公 司不小心在其订单的生产系统上跑测试,直到大量的洗衣机送达总部,它才意识到这个 错误。
8.8关联标识
最终用户看到的任何功能都由大量的服务配合提供,一个初始调用最终会触发多个下游的 服务调用。例如,考虑客户注册的例子。客户填写表单的所有信息,然后点击提交。界面 背后,我们使用支付服务检查信用卡信息的有效性,告知邮寄服务在邮局寄送一个欢迎礼包,并调用我们的电子邮件服务发送欢迎邮件。现在,如果支付服务的调用发生了一个奇 怪的错误,该如何处理呢?我们将在第11章详细讨论如何处理失败,这里先考虑诊断时 会遇到的困难。
如果看一下日志,就会发现只有支付服务注册了一个错误。如果我们足够幸运,可以找到 引发问题的请求,甚至可以看看当时调用的参数。但这只是简单的情况,更为复杂的初始 请求有可能生成一个下游的调用链,并且以异步的方式处理触发的事件。我们如何才能重 建请求流,以重现和解决这个问题呢?通常我们需要在初始调用更大的上下文中看待这个 错误;换句话说,就像查看栈跟踪那样,我们也想查看调用链的上游。
在这种情况下,一个非常有用的方法是使用关联标识(ID)。在触发第一个调用时,生成 一个GUID。然后把它传递给所有的后续调用,如图8-5所示。类似日志级别和日期,你 也可以把关联标识以结构化的方式写入日志。使用合适的日志聚合工具,你能够对事件在 系统中触发的所有调用进行跟踪:
15-02-2014 16:01:01 Web-Frontend INFO [abc-123] Register
15-02-2014 16:01:02 RegisterService INFO [abc-123] RegisterCustomer "•
15-02-2014 16:01:03 PostalSysten INFO [abc-123] SendWelcomePack ...
15-02-2014 16:01:03 EnailSysten INFO [abc-123] SendWelconeEmail 15-02-2014 16:01:03 PaynentGateway ERROR [abc-123] ValidatePaynent ...
图8-5:使用关联标识来跟踪跨多个服务的调用
当然,你需要确保每个服务知道应该传递关联标识。此时你需要标准化,强制在系统中执 行该标准。一旦这样做了,你就可以创建工具来跟踪各种交互。这样的工具可以用干跟踪 事件风暴、不常发生的特殊场景,甚至识別出时间过长的事务,因为你能勾勒出整个级联 的调用。
像Zipkin (http://twitter.github.io/zipkin/)这样的软件,也可以跨多个系统边界跟踪调用。 基于Google自己的跟踪系统Dapper的创意,Zipkin可以提供非常详细的服务间调用的追踪信息,还有一个界面帮助显示数据。我个人觉得Zipkin有点重量级,需要自定义客户端 并且支持收集系统。既然因为其他目的,你已经把日志聚合,所以更简单的方式应该是重 用已经收集的数据,而不是必须再附加一个数据源。也就是说,如果你发现需要一个更先 进的工具跟踪服务间的调用,可能才需要Zipkin这样的软件。
使用关联标识时,一个现实的问题是,你常常直至问题出现才知道需要它,而且只有在开 始时就存在关联标识才可能诊断出问题!这个问题非常棘手,因为在后面很难加装关联标 识;你需要以标准化的方式处理它们,这样才能够轻易重建调用链。虽然开始的时候它似 乎是一些额外的工作,但我还是强烈建议你尽早考虑使用它,尤其是如果你的系统使用事 件驱动的架构模式,因为这种模式会导致一些奇怪的意外行为。
传递关联标识时需要保持一致性,这是使用共享的、薄客户端库的一个强烈的信号。团队 达到一定规模时,很难保证每个人都以正确的方式调用下游服务以收集正确的数据。只需 服务链中的某个服务忘记传递关联标识,你就会丢失重要的信息。如果你决定创建一个内 部客户端库来标准化这样的芏作,请确保它很薄且不依赖提供的任何特定服务。例如,如 果你正在使用HTTP作为通信协议,只需包装标准的HTTP客户端库,添加代码确保在 HTTP头传递关联标识即可。
8.9级联
级联故障特别危险。想象这样一个情况,我们的音乐商店网站和产品目录服务之间的网 络连接瘫痪了,服务本身是健康的,但它们之间无法交互。如果只查看某个服务的健康 状态,我们不会知道已经出问题了。使用合成监控(例如,模拟客户搜索一首歌)会将 问题暴露出来。但为了确定问题的原因,我们需要报告这一事实是一个服务无法访问另 一个服务。
因此,监控系统之间的集成点非常关键。每个服务的实例都应该追踪和显示其下游服务的 健康状态,从数据库到其他合作服务。你也应该将这些信息汇总,以得到一个整合的画 面。你会想了解下游服务调用的响应时间,并检测是否有错误。
你可以使用库实现一个断路器网络调用,以帮助你更加优雅地处理级联故障和功能降级, 我们在11章将讨论更多这方面的内容。一些库,例如JVM上的Hystrix,便很好地提供了 这些监控功能。
8.10标准化
正如我们前面提过的,一个需要持续做出的平衡,是仅规范单个服务,还是规范整个系 统。在我看来,监控这个领域的标准化是至关重要的。服务之间使用多个接口,以很多不 同的方式合作为用户提供功能,你需要以整体的视角查看系统。
你应该尝试以标准格式的方式记录日志。你一定想把所有的指标放在一个地方,你可能 需要为度量提供一个标准名称的列表;如果一个服务指标叫作ResponseUne,另一个叫作 RspTineSecs,而它们的意思是一样的,这会非常令人讨厌。
和以前一样,工具在标准化方面可以提供帮助。正如我之前说的,关键是让做正确的事情 变得容易,所以为什么不提供预配置的虚拟机镜像,镜像内置logstash和collectd,还有一 个公用的应用程序库,使得与Graphite之间的交互变得非常容易?
8.11考虑受众
我们收集这些数据都是为了一个目的。更具体地说,我们为不同的人收集这些数据,帮助 他们完成工作;这些数据会触发一些事件。有些数据会触发支持团队立即采取行动,比如 我们的一个综合监控测试失败了。其他数据,比如CPU负载在过去一周增加了 2%,我们 在做容量规划的时候可能才会对其感兴趣。同样,你的老板可能想立即知道,上次发布后 收入下降了 25%,但可能不需要知道,“Justin Bieber”搜索在最近一小时上涨了 5%。
人们现在希望看到并立即处理的数据,与当进行深入分析时所需要的是不同的。因此,对 于查看这些数据的不同类型的人来说,需考虑以下因素:
•他们现在需要知道什么 •他们之后想要什么 •他们如何消费数据
提醒他们现在需要知道的东西。在房间的某个角落放置一个大显示屏来显示此信息,并使 得以后需要做深入分析数据时,他们也能够很方便地访问。花时间了解他们想要使用的数 据。讨论定量信息的图形化显示所涉及的所有细微差别已经超出了本书的范围,一个不错 的起点是 Stephen Few 的优秀图书:Information Dashboard Design: Displaying Data for Ata-Glance Monitoring。
8.12 未来
在很多组织中,我看到指标被孤立到不同的系统中。如订单数量这样的应用程序级指标, 只放在Omniture等专有的分析系统上(通常只供业务的重要部分使用),或者进入可怕的 数据仓库,然后再也无人问津。在这类系统中,报告通常不是实时的,尽管这种情况已经 开始有所变化。与此同时,系统指标,如响应时间、错误率和CPU负载,都存储在运维 团队可以访问的系统上。通常这些系统提供实时报告,目的是及时触发行动。
过去,我们在一两天后找出关键业务指标是可以接受的,因为通常我们无法根据这些数据 快速地做出反应。不过,在当前的世界,我们中的许多人每天可以发布多个版本。团队现在衡量的不是他们完成多少点,而是代码从笔记本电脑到生产环境上需要多长时间。在这 种环境下,所有指标都需要在我们手上以方便采取正确的行动。具有讽刺意味的是,存储 业务指标的系统通常无法直接、实时地访问,但存储运营指标的系统却可以。
为什么不能以同样的方式处理运营指标和业务指标?最终,两种类型的指标分解成事件 后,都说明在X时间点发生了一些事情。如果我们可以统一收集、聚合及存储这些事件的 系统,使它们可用于报告,最终会得到一个更简单的架构。
Riemann (http://riemann.io/)是一个事件服务器,允I午高级的聚合和事件路由,所以该工 具可以作为上述解决方案的一部分。Suro (https://github.com/Netflix/suro)是Netflix的数 据流水线,其解决的问题与Riemami类似。Suro明确可以处理两种数据,用户行为的相 关指标和更多的运营数据(如应用程序日志)。然后这些数据可以被分发到不同的系统中, 像Storm的实时分析、离线批处理的Hadoop或日志分析的Kibana。
i午多组织正在朝一个完全不同的方向迈进:不再为不同类型的指标提供专门的工具链,而 是提供伸缩性很好的更为通用的事件路由系统。这些系统能提供更多的灵活性,同时还能 简化我们的架构。
8.13 小结
本章涵盖了很多内容。下面我试图把本章的内容总结成一些方便实施的建议。
对每个服务而言,
•最低限度要跟踪请求响应时间。做好之后,可以开始跟踪错误率及应用程序级的指标。
•最低限度要跟踪所有下游服务的健康状态,包括下游调用的响应时间,最好能够跟踪错 误率。一些像Hystrix这样的库,可以在这方面提供帮助。
•标准化如何收集指标以及存储指标。
•如果可能的话,以标准的格式将日志I己录到一个标准的位置。如果每个服务各自使用不 同的方式,聚合会非常痛苦!
•监控底层操作系统,这样你就可以跟踪流氓进程和进行容量规划。
对系统而言,
•聚合CPU之类的主机层级的指标及应用程序级指标。
•确保你选用的指标存储工具可以在系统和服务级别做聚合,同时也允许你查看单台主机 的情况。
•确保指标存储工具允许你维护数据足够长的时间,以了解你的系统的趋势。
•使用单个可查询工具来对日志进行聚合和存储。
•强烈考虑标准化关联标识的使用。
• 了解什么样的情况需要行动,并根据这些信息构造相应的警报和仪表盘。
•调查对各种指标聚合方式做统一化的可能性,像Suro或Riemann这样的工具可能会对 你有用。
我还试图描绘了系统监控发展的方向:从专门只做一件事的系统转向通用事件处理系 统,从可以全面地审视你的系统。这是一个令人激动的新兴空间,虽然全面讲解已经 超出了本书的范围,但希望我介绍的已足够你起步。如果你想知道更多,我早期出版的 Lightweight Systems for Realtime Monitoring 一书中有一些我的想法和更详细的介绍。
在下一章,我们将从不同的系统视角看看,细粒度的架构在安全方面独有的优势和挑战。