1 深入研究日志
第一次工作遇到代码报错尝试解决并没有做好,而且我不清楚是哪里做错了。一位同事建议我打开日志看看是哪里出错了。而要这样做,他说我应该“cat 日志文件”。 当时我真以为同事们在和我开玩笑,或者在说一个关于猫的笑话,只不过我没听懂。我在大学里使用 Linux 只是为了编译、使用源代码控制和使用文本编辑器。所以,我不知道“cat”其实是一个命令,用来将文件内容输出到终端,之后,这些内容可以输入到其他程序中,以便查找模式。
同事们向我推荐了 cat、grep、sed 和 awk 等工具。我使用这套新工具深入研究服务器日志。经检测,Web 服务器应用程序已将各种有用信息发送到其日志中。这样一来,我可以通过查看日志知道缺少了哪项配置,导致 Web 服务器无法启动,或者导致我无法知道其可能在哪个地方崩溃或未能与下游服务通信。整个网站由许多动态部分组成,而且一开始对我来说基本上就是一个黑盒。但是,在深入研究系统之后,我学会了如何仅通过查看检测的输出来确定服务器的工作方式以及与其依赖关系交互的方式。
2 为何要进行检测
这些年来,我先后在不同公司不同团队中工作,我发现检测是很有价值的透视工具,让我和其他员工能清楚了解系统的工作方式。但是,检测的作用并不只是了解系统状况,卓有成效的检测能帮助我们了解我们带给客户的体验。
在与 amazon.com 关联的服务中,延迟增加会造成糟糕的购物体验,从而降低转化率。对于使用 AWS 的客户,他们依赖于 AWS 服务的高可用性和低延迟。
在 Amazon 内部,我们不只是考虑平均延迟。我们 更密切地关注延迟异常值,如第 99.9 和第 99.99 百分位。因为若 1000 或 10000 个请求中有一个很慢,则仍然是糟糕的体验。我们发现,如降低系统中的高百分位延迟,会带来降低中值延迟的副作用。相比之下,如降低中值延迟,较少会导致高百分位延迟降低。
关注高百分位延迟的另一个原因是:某个服务中的高延迟可能会在其他服务中产生乘数效应。Amazon 建立在面向服务的架构上。许多服务彼此协作来完成工作,例如在 amazon.com 上呈现网页。因此,如果调用链深处的服务的延迟增加(即使增加发生在高百分位中),会对最终用户感受到的延迟造成巨大连锁反应。
Amazon 的大型系统由许多协同工作的服务组成。每个服务都由一支单独的团队开发和运营(大型“服务”由后台的多个服务或组件组成)。拥有服务的团队称为 服务拥有者。该团队的每个成员均像服务的拥有者和运营者一样思考,无论该成员是软件开发人员、网络工程师、经理还是任何其他角色。作为拥有者,团队设定与所有关联服务的运营绩效相关的目标。我们还确保能看到服务运营状况,以保证我们达成这些目标、处理出现的任何问题和促使自己在第二年达成更高的目标。为了设定目标和获得这种可见性,团队必须对系统进行检测。
检测还使我们能够有策略地探测和应对运营事件。检测向运营控制面板提供数据,以便运营者能够查看实时指标。它还向警报提供数据,当系统发生意外行为时,警报便会触发并引起运营者关注。运营者使用详细的检测输出快速诊断出错的原因。这样一来,我们可以缓解问题,以后再设法防止问题复发。如果整个代码中未包含有效的检测机制,我们将要花费宝贵的时间来诊断问题。
3 测量对象
为了按照我们针对可用性和延迟制定的高标准来运营服务,作为服务拥有者,我们需要测量系统的具体表现。
为获得必需的遥测数据,服务拥有者从多个位置测量运营绩效,以便从多个角度了解各组件的端到端具体表现。这即使在简单的架构中也很复杂。思考一下客户通过负载均衡器调用的某个服务:该服务与远程缓存和远程数据库通信。我们希望每个组件都发送有关其行为的指标。我们还希望获得反映每个组件对其他组件的行为有何感受的指标。将来自所有这些角度的指标汇集起来时,服务拥有者能迅速查明问题的起源并挖掘出原因。
许多 AWS 服务都会自动提供有关您的资源的运营见解。如,Amazon DynamoDB 提供有关成功率、错误率和延迟的 Amazon CloudWatch 指标(由该服务测量)。但是,当我们构建使用这些服务的系统时,我们需要获得高很多的可见性,以便了解系统的具体表现。检测需要显式代码来记录任务要执行多长时间、某些代码路径多久执行一次、与任务执行的工作相关的元数据和任务的哪些部分成功或失败。如果团队未添加显式检测,则检测可能会被迫将其自己的服务作为黑盒来运营。
如实现了一个按产品 ID 来检索产品信息的服务 API 操作。此代码依次在本地缓存、远程缓存和数据库中查找产品信息:
public GetProductInfoResponse getProductInfo(GetProductInfoRequest request) {
// check our local cache
ProductInfo info = localCache.get(request.getProductId());
// check the remote cache if we didn't find it in the local cache
if (info == null) {
info = remoteCache.get(request.getProductId());
localCache.put(info);
}
// finally check the database if we didn't have it in either cache
if (info == null) {
info = db.query(request.getProductId());
localCache.put(info);
remoteCache.put(info);
}
return info;
}
若我运营此服务,我要在此代码中进行大量检测才能了解此服务在生产系统中的行为。
我要能对失败或缓慢请求排查及监视不同依赖关系未达下限或行为不当的趋势和迹象。
下面是相同代码,但有一些问题注释(我需要能够解答这些与整个生产系统相关或针对特定请求的问题):
public GetProductInfoResponse getProductInfo(GetProductInfoRequest request) {
// 要查找的产品是哪个?
// 谁调用了 API?这属于哪个产品类别?
// 我们是否在本地缓存中找到了该项?
ProductInfo info = localCache.get(request.getProductId());
if (info == null) {
// 该项是否在远程缓存中?
// 从远程缓存中读取所用的时间有多长?
// 从缓存中反序列化对象所用的时间有多长?
info = remoteCache.get(request.getProductId());
// 本地缓存有多满?
localCache.put(info);
}
// 如果我们在任一缓存中都没有找到,最后检查数据库
if (info == null) {
// 数据库查询用了多长时间?
// 查询是否成功?
// 如果失败,是因为超时了吗?还是查询无效?我们失去了数据库连接吗?
// 如果超时,是因为连接池已满了吗?我们连接数据库失败了吗?还是数据库响应较慢?
info = db.query(request.getProductId());
// 填充缓存用了多长时间?
// 缓存是否已满,是否逐出了其他项?
localCache.put(info);
remoteCache.put(info);
}
// 该产品信息对象有多大?
return info;
}
解答这些问题和其它更多问题的代码比实际业务逻辑长很多。某些库可帮助减少检测代码数量,但开发仍须提出与这些库将需要的可见性有关的问题,然后须特意在检测中写出这些问题。
在对分布式系统的请求进行排查时,如只是根据一次交互查看该请求,难以了解情况。要整合全局,将有关所有这些系统的测量结果汇集到一处,这就很有用了。这样做之前,须检测每个服务,以记录每个任务的跟踪 ID,并将该跟踪 ID 传播到协同完成该任务的其他每个服务。可按需在事后跨系统针对特定跟踪 ID 集中进行检测,也可用 AWS X-Ray 之类的服务近实时地集中进行检测。
4 向下钻取
通过检测,可在多级别(从浏览指标以了解是否存在因过于微小而无法触发警报的任何异常,到进行调查以找出这些异常的原因)进行故障排查。
在最高级,检测聚合到可触发警报并显示在控制面板上的指标中。这些聚合指标可让运营者监视总体请求速率、服务调用的延迟和错误率。借助这些警报和指标,能知道应调查的异常或变化。
看到异常后,要找出发生异常的原因。依靠通过更多检测建立的指标来解答该问题。通过检测执行请求处理过程的不同部分所需的时间,可见处理过程的哪个部分比平常慢或更频繁地触发错误。
虽然聚合计时器和指标可能帮助我们排除原因或确定重点调查领域,但并不总能提供全面解释。如可能可从指标中知道错误来自某特定 API,但这些指标可能并未充分揭示该操作为何失败的细节。此时,查看服务发送的该时间窗口的原始详细日志数据。原始日志会显示问题起源 – 所发生特定错误或触发某些边缘情况的特定请求方面。
5 如何进行检测
需要编写代码才能检测。这意味着在我们实现新功能时,要花时间添加额外的代码,以指出发生的事情、它已成功还是已失败以及耗用了多长时间。
由于检测是这样一种常见的编码任务,多年来 Amazon 内部形成了一套做法来处理通用模式:
- 通用检测库标准化
- 基于结构化日志的指标报告标准化
指标检测库标准化帮助库作者向库使用者提供库工作方式的可见性。如常用 HTTP 客户端与这些通用库集成,因此,如果服务团队执行对其他服务的远程调用,他们将自动获得有关这些调用的检测。
在被检测的应用程序运行并执行工作时,生成的遥测数据将写入到结构化日志文件。通常,该数据作为每个“工作单元”的一个日志条目发送,而不管这是对 HTTP 服务的请求还是从队列中取出的消息。
在 Amazon 内部,应用程序中的测量结果并不聚合起来,但有时会刷新到指标聚合系统。各部分工作的所有计时器和计数器均写入到日志文件。这样做,另一个系统将在事后处理日志和计算聚合指标。这样一来,我们最终获得各种数据(从高级别的聚合运营指标到请求级别的详细故障排查数据),而这是通过一种代码检测方法实现的。在 Amazon 内部:
- 先记录日志
- 再产生聚合指标
6 通过日志记录进行检测
通常在检测服务后发送两种日志数据:
-
请求数据
通常表示为结构化日志条目(每个工作单元一个条目)。此数据包含有关以下各项属性:请求、请求发出者、请求目的、事情多久发生一次的计数器和事情占用多长时间的计时器。对在服务中发生的每件事情,请求日志起着审计日志和跟踪作用
-
调试数据
调试数据包含应用程序发送的任何调试行的非结构化数据或松散结构化数据。通常,这些数据是非结构化日志条目,例如 Log4j 错误或警告日志行。在 Amazon 内部,这两种类型的数据通常发送到不同的日志文件中,这部分是出于历史原因,但另一个原因是使用同构的日志条目格式进行日志分析可能比较方便。
像 CloudWatch Logs 代理这样的代理实时处理这两种类型的日志数据,并将日志发送给 CloudWatch Logs。接下来,CloudWatch Logs 会近实时地产生有关服务的聚合指标。Amazon CloudWatch Alarms 读取这些聚合指标并触发警报。
虽然为每个请求记录如此多的细节可能成本高昂,但在 Amazon 内部发现这样做非常重要。毕竟,我们需要调查可用性波动、延迟激增和客户报告的问题。如无详细日志,无法向客户提供答案,无法改善他们的服务。
7 深入了解详细信息
监视和警报是一个很大的主题。在本文中,我们不讨论类似以下这些的主题:设置和调整警报阈值、组织运营控制面板、从服务器端和客户端测量性能、持续运行“金丝雀”应用程序以及选择适用于聚合指标和分析日志的系统。
本文重点讨论检测应用程序以产生正确原始测量数据的必要性。说明 Amazon 内部团队在检测应用程序时努力做到或避免的事情。
标签:info,服务,检测,实践,Amazon,日志,延迟 From: https://blog.51cto.com/JavaEdge/8761253