想象一下,有一天早上醒来,你发现腰带上的最后一个洞塞不进去了。你走到体重秤前,发现一夜之间体重增加了很多。你开始紧急节食和健身。几周后,看了看体重秤,发现自己的体重不知怎么增加了更多。这是怎么回事?你需要了解的是你的身体正在发生什么变化。如果我们的身体有内置可观察性功能,我们的身体就会有指标,就可以在仪表盘上绘制激素水平。如果能看到我们的激素水平突然失衡,在所有条件都相同的情况下,可以推测激素失衡一定是根本原因。但是,由于无法看到发生了什么变化,为了查出问题所在需要做很多的改变,每种改变都有各自的效果。我们让系统可可观测,这样就可以提出问题深入了解系统,并调试意料之外的问题。这意味着我们可以解决以前没有发生过的任意问题。接下来,我们将实现服务的可观测,这样我们就可以理解服务正发生什么变化。三类遥测数据
可观测性是从外部输出了解系统内部(它的行为和状态)的一种方式。我们使用指标(metrics)、结构化日志(logs)和链路跟踪(trace)作为输出,使我们的系统可观察。虽然有三种类型的遥测数据,每一种都有自己的用途,我们将讨论,它通常来自相同的事件。例如,每当web服务处理一个请求时,它可能会增加“已处理请求”指标,为该请求发出日志,并对请求链路进行跟踪。指标(Metrics)
指标:度量随时间变化的数据,例如失败的请求数量或每个请求花费的时间。诸如此类的度量有助于定义服务级别。我们将使用指标来报告系统的运行状况,触发内部告警,并在看板上绘制图形,以便一目了然地了解系统的运行情况。因为指标是数值类型数据,你可以逐渐降低分辨率,以减少存储需求和查询时间。例如,如果我们经营一家图书出版公司,就会对每一本书的购买都有指标。为了运送客户的图书,我们需要知道客户的订单,但是在交付了图书并且通过了退货政策之后,我们不再关心订单。当我们对业务做分析时会存在很多的细节。最终,我们只需要季度收益就可以计算纳税、计算年增长率,并知道是否可以雇佣更多的编辑和作者来扩大业务。- 跟踪事件发生的次数,比如请求失败的次数,或者系统中某些操作的总和,比如系统处理的字节数。经常需要使用计数器来获得一个速率:在一定时间范围内事件发生的次数。谁会关心服务接受请求的总次数?我们关心的是在过去的一秒或一分钟内处理了多少请求——如果请求数量显著下降,你就需要检查系统的延迟。你需要知道请求错误率何时达到峰值,这样就可以发现哪里出了问题并进行修复。
- 直方图显示的是数据的分布情况。使用直方图来测量请求持续时间和大小的百分比。
- 追踪某事物的当前值。可以完全替换该值。Gauge对于饱和类型的度量很有用,比如主机的磁盘使用率或与云提供商的限制相比负载均衡的数量。
我们可以测量任何东西,那么应该测量什么数据呢?”什么指标将在您的系统上提供有价值的信号?以下是谷歌提出的衡量系统状态的四个黄金信号:- 延时:服务处理请求所需的时间。如果延迟出现峰值,通常需要通过升级一个具有更多内存、cpu或IOPS的计算实例来垂直扩展你的系统,或者通过向你的负载均衡添加更多实例来水平扩展你的系统。
- 流量:你的服务需求量。对于典型的web服务,这可能是每秒处理的请求数。对于在线视频游戏或视频流服务,它可以是并发用户数。这些指标是可以用来炫耀你的系统规模的,但更重要的是,它们可以帮助你了解自己的系统状态,以及何时需要新的系统设计来应对大规模流量。
- 饱和度:衡量服务的能力。例如,如果您的服务将数据持久化到磁盘,按照当前的输入速率,会很快耗尽硬盘空间吗?如果你有一个内存存储,与可用内存相比,你的服务使用了多少内存?
虽然大多数调试故事都是从参数开始的—要么是通过警报,要么是有人注意到看板上的异常—但都需要查看日志和跟踪,以了解有关问题的更多细节。让我们接下来看看日志。结构化日志
日志描述系统中的事件。我们应该记录任何可以帮助了解服务的事件。日志能帮助我们排除故障、审计和配置文件,这样我们就可以了解出了什么问题以及为什么,谁运行了什么操作,以及这些操作花费了多长时间。例如,gRPC服务日志可以记录每个RPC调用:{ "request_id": "f47ac10b-58cc-0372-8567-0e02b2c3d479", "level": "info", "ts": 1600139560.3399575, "caller": "zap/server_interceptors.go:67", "msg": "finished streaming call with code OK", "peer.address": "127.0.0.1:54304", "grpc.start_time": "2020-09-14T22:12:40-05:00", "system": "grpc", "span.kind": "server", "grpc.service": "log.v1.Log", "grpc.method": "ConsumeStream", "peer.address": "127.0.0.1:54304", "grpc.code": "OK", "grpc.time_ns": 197740 }
在这个日志中,我们可以看到调用者的IP地址、他们调用的服务和方法、调用是否成功以及请求花费了多长时间。在分布式系统中,请求ID有助于拼凑由多个服务处理的请求的完整图像。这个gRPC日志是一个JSON格式的结构化日志。结构化日志是一组格式一致的键值对,便于程序读取。结构化日志使我们能够分离日志捕获、传输、持久化和查询。例如,我们可以用protobuf捕获和传输我们的日志,然后用Parquet格式对其进行编码,并将它们持久化到数据库中。我建议在像Kafka这样的事件流平台上收集结构化日志,以便对日志进行任意处理和传输。例如,可以将Kafka与BigQuery这样的数据库连接起来查询日志,同时将Kafka与GCS这样的对象存储连接起来维护历史副本。在日志记录太少和没有调试问题所需的信息之间,或者日志记录太多,被太多的信息淹没和错过重要的东西之间,需要一个平衡。我建议对错误日志记录详细,并减少无用的日志。这样一来,就不太可能缺少故障排除或审核问题所需的信息。链路追踪(trace)
跟踪捕获请求的生命周期,对请求流经系统时跟踪请求。使用Jaegar、Stackdriver、和Lightstep等用户界面,可以直观地表示请求在系统中花费的时间。在分布式系统中,当请求在多个服务上被处理时,这尤其有用。你可以用详细信息标记链路追踪,以更多地了解每个请求。一个常见的例子是用用户ID标记每个跟踪,这样如果用户遇到问题,您就可以轻松地找到他们的请求。链路追踪包括一个或多个跨度。它可以是父/子关系,也可以是兄弟姐妹关系。每个跨度表示请求执行的一部分。具体如何分割这些部分取决于你自己。通常跨所有服务端到端跟踪请求,从服务的入口点开始到出口点结束。然后深入每个服务并跟踪重要的方法调用。