Debug

头像
Xinli
    阅读 13 分钟

    为什么要进行检测

    在检测的过程中一方面可以帮助我们理解我们的系统,更重要的是,这可以为客户提供卓越运营的体验。帮助我们更了解我们给客户的体验是什么样的。
    在amazon.com购物时,高延迟会对客户造成非常差的购物体验,从而降低转换率。对使用AWS的用户,他们需要的是高可用性和低延迟。
    在Amazon,我们不仅仅去考虑平均延迟,而是会去更加关注延迟的异常值。比如P99.9或是P99.99这样的指标。之所以这样,是因为在降低P99之后,平均延迟也同样会降低,但是降低了平均延迟,P99并不一定会降低。
    更加关注高百分位延迟的一个原因是,某个服务中的高延迟可能会在其他服务中产生乘法效应。即,如果调用链深处的服务的latency增加,最终用户可能会感受到很长的延迟。
    Amazon的大型系统是由许多协同工作的服务组成。每个服务都由一支单独的团队开发和运营。拥有服务的团队称为service owner. 无论担任任何职位,该团队的每个成员均会像服务的owner护额operator一样思考。作为service owner,团队会设定拥有的每个service的运营绩效相关的目标。团队还需要确保可以看到service的运行状况,以保证我们达成这些目标、处理出现的任何问题和促使自己在第二年达成更高的目标。为了设定目标和获得这种可见性,团队必须对系统进行检测。检测还促使我们能够有策略的探测和应对运营事件。
    Instrumentation feeds data into operational dashboard, so that operators can view real-time metrics. It also feeds data into alarms. 运营者使用详细的检测输出快速诊断出错的原因,这样一来,我们可以迅速想办法先暂时缓解问题,并记下该问题,之后进行处理,防止该问题再次复发。Without good instrumentation throughout the code, we sink precious time into diagnosing problems.

    检测什么

    为了获得必须的metrics,service owner必须从多个方面衡量operational performance,以便从多个角度了解各组件的端到端具体表现。比如,customer通过Load Balancer调用某个service,该服务会与remote cache和remote database通信。We want each component to emit metrics about its behavior. We also want metrics on how each component perceives the behavior of other components. 将来自所有这些角度的metrics汇集起来时,service owner就能迅速查明问题的root cause。
    许多AWS服务都会自动提供一些resource的运营建议。例如,DDB提供有关成功率,错误率和延迟的Amazon CloudWatch metrics。但是,当我们构建使用这些服务的系统时,我们需要获得高得多的visibility,以便了解系统的具体表现。检测需要显示代码来记录任务要执行多长时间,某些代码路径多久执行一次,与任务执行的工作相关的元数据和任务的那些部分成功或失败。如果团队未添加显式检测,则service owner将被迫把自己的服务作为黑盒来运营。

    举个栗子🌰

    如果我们实现了一个按产品ID来检索产品信息的 service API,代码可能会类似下面的情况。此代码依次在local cache、remote cache和database中查找产品信息:

    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;
    }

    如果我们运营此服务,则需要在此代码中进行大量检测才能了解该服务在production中的behavior. 我们需要能够对失败或latency过高的请求进行故障排查,以及监控不同依赖项的调用情况。下面是相同业务功能的代码,但是带有了一些问题注释。在真正开发时,需要能够解答这些与整个开发相关或针对特定请求的问题:

    public GetProductInfoResponse getProductInfo(GetProductInfoRequest request) {
    
      // Which product are we looking up?
      // Who called the API? What product category is this in?
    
      // Did we find the item in the local cache?
      ProductInfo info = localCache.get(request.getProductId());
      
      if (info == null) {
        // Was the item in the remote cache?
        // How long did it take to read from the remote cache?
        // How long did it take to deserialize the object from the cache?
        info = remoteCache.get(request.getProductId());
        
        // How full is the local cache?
        localCache.put(info);
      }
      
      // finally check the database if we didn't have it in either cache
      if (info == null) {
        // How long did the database query take?
        // Did the query succeed? 
        // If it failed, is it because it timed out? Or was it an invalid query? Did we lose our database connection?
        // If it timed out, was our connection pool full? Did we fail to connect to the database? Or was it just slow to respond?
        info = db.query(request.getProductId());
        
        // How long did populating the caches take? 
        // Were they full and did they evict other items? 
        localCache.put(info);
        remoteCache.put(info);
      }
      
      // How big was this product info object? 
      return info;
    }

    用于解答所有这些问题和更多问题的代码比实际业务逻辑长很多。某些库可以帮助减少我们需要自己写的检测代码的数量,但开发人员必须询问自己这些问题,然后必须将检测代码写在servcice中。
    在对流经分布式系统的请求进行故障排查时,如果我们只是根据一次交互来查看该请求,可能难以了解发生的情况。要整合全局,我们发现将有关所有这些系统的测量结果汇集到一处很有用。在我们这样做之前必须收集结果,即,必须给每个任务一个trace ID,并将该trace ID传播到协同完成该任务的其他每个service中。这样我们就可以根据需要在事后跨系统针对特定trace ID进行集中检测,也可以使用 AWS X-Ray 之类的服务近实时的进行集中检测。

    Drilling down

    通过检测,我们就可以在多个级别上进行故障排查了。从浏览metrics以了解是否存在因过于微小而无法出发警报的任何异常,到进行调查以找出这些异常的原因。
    从大的方面来看,检测的结果可以聚合到metrics中,然后通过metrics我们可以设置alarm,或者再次封装成dashboard。这些聚合指标可以让operators monitor the overall request rate, the latency of the service calls, and the error rates. These alarms and metrics make use aware of anomalies or changes that should be investigated。
    在看到异常后,我们通过建立的多个metrics就可以找出发生异常的原因。通过检测执行请求处理过程的不同部分所需的时间,我们可以看到处理过程中哪个部分比平常慢或哪个部分比平常更频繁的触发错误。
    虽然aggregate timer and metrics可能可以帮助我们排查问题或确定重点调查领域,但它们并不总能提供全面的解释。例如,我们可能可以从metric中知道错误来自某特定的API操作,但这些metrics可能并未充分揭示该操作为何失败的细节。At this point, we look at the raw, detailed log data emitted by the service for that time window. The raw logs then show the source of problem--either the particular error that is happening, or particular aspects of the request that are triggering some edge case.

    如何进行检测

    毋庸置疑,需要编写代码来支持检测。这意味着我们在实现新功能时,需要花时间添加额外的代码,以指出发生的事情、它已经成功还是已经失败、耗用了多长时间等。由于检测是这样一种常见的编码任务,多见来Amazon内部形成了一套做法来处理通用模式:standardization for common instrumentation libraries, and standardization for structured log-based metric reporting.
    Standardization of metrics instrumentation libraries helps library authors give consumers of their libraries visibility into how the library is operating。例如,常用的HTTP客户端与这些通用库集成在一起,因此,如果服务团队执行对其他服务的远程调用,他们将自动获得有关这些调用的检测。
    在被检测的应用程序运行并执行工作时,生成的遥测数据将写入到结构化的日志中。通常,该数据作为每个“工作单元”的一个日志条目发送,而不管这是对某个HTTP service的请求还是从队列中取出的消息。
    在Amazon,应用程序中的测量结果并不聚合起来,但有时会刷新到指标聚合系统中。各部分工作的所有计时器和计数器均写入到日志文件中。通过这样做,另一个系统将在事后处理日志和计算聚合指标。这样一来,我们最终就获得了各种数据(从高级别的聚合运营指标到请求级别的详细故障排查数据),而这是通过代码检测的方法实现的。在Amazon内部,我们首先记录日志,之后再产生聚合指标。

    通过日志记录进行检测

    我们通常在检测服务后发送两种类型的日志数据:request data and debugging data.
    请求日志数据通常表示为结构化日志条目(每个工作单元一个条目)。此数据包含有关以下各项的属性:request, who made the request, what the request was for, counters of how often things happened, and timers of how long things took. 对于在服务中发生的每件事情,请求日志起着审计日志和跟踪的作用。
    调试数据包含应用程序发送的任何调试行的非结构化数据或松散结构化数据。通常,这些数据是非结构化条目,例如Log4j错误或警告日志行。
    在Amazon内部,这两种类型的数据通常发送到不同的日志文件中,这部分一方面是出于历史原因,另一个更重要的原因是使用同构的日志条目格式进行日志分析可能比较方便。
    像 CloudWatch Logs Agent 这样的代理实时处理这两种类型的日志数据,并将日志发送给CloudWatch Logs。接下来,CloudWatch Logs会近乎实时的产生有关服务的聚合指标。Amazon CloudWatch Alarms 读取这些聚合指标并触发警报。
    虽然为每个请求记录如此多的细节可能成本高昂,但在Amazon内部,我们发现这样做非常重要。毕竟我们需要调查可用性波动、延迟激增和customer报告的问题。如果没有详细的日志,我们无法向客户提供答案,而且无法改善他们的服务。

    深入了解详细信息

    The topic of monitoring and alarming is vast.在本文中不会讨论以下这些主题:

    1. setting and tuning alarm thresholds,
    2. organizing operational dashboards,
    3. measuring performance from both the server-side and client side,
    4. continuously running "canary" applications, and
    5. choosing the right system to use to aggregate metrics and analyze logs.

    This article focuses on the need to instrument our applications to produce the right raw measurement data。文中将说明Amazon团队在检测应用程序时努力做到或避免的事情。

    请求日志最佳实践

    本节将讲解在Amazon的记录“每个工作单元”的结构化数据相关的良好习惯。符合这个标准的日志包含:表示事情多久发生一次的计数器、包含事情占用时间的时间长度的计时器、包含每个工作单元相关元数据的属性。

    记录log的方式

    1. Emit one request log entry for every unit of work. A unit of work is typically a request our service received or a message it pulls from a queue. 我们为我们的服务收到的每个请求编写一个服务日志条目。我们不会将多个工作单元组合在一起。这样,当我们对失败的请求进行故障排除时,我们可以查看单个日志条目。该请求的相关输入参数(指示请求尝试做的事情)、发起者是谁的相关信息以及所有计时和计数器信息都包含在这个条目中。
    2. Emit no more than one request log entry for a given request. 每个工作单元具有多个日志条目会使日志分析变得困难,并会使已经很高的日志记录开销成倍增长。如果我们编写新的非阻塞服务,我们会尝试预先规划指标的日志记录生命周期。这是因为过后再重构和修复会非常困难。
    3. Break long-running tasks into multiple log entries. 与前面的建议相反,如果我们有一个长时间运行(数分钟或数小时)且类似于工作流程的任务,我们可能会决定定期发送一个单独的日志条目,以便我们能确定是否正取得进展或者再哪个地方减慢了速度。
    4. Record details about the request before doing stuff like validation. 我们发现充分记录有关请求的信息,以便我们知道请求尝试完成的事情对故障排除和审计日志记录非常重要。我们也发现尽早记录这些信息(在请求可能被验证、身份验证或限制逻辑拒绝之前)很重要。如果我们记录传入请求中的信息,我们会确保在记录之前对输入进行清理(编码、转义、截断等)。例如,如果发起者传入一个服务日志条目,我们不希望它包含长度为1MB的字符串。否则会出现填满磁盘的风险,并导致我们在日志存储方面付出超过预期的代价。另一个清理示例是筛选出与日志格式相关的ASCII控制字符或转义序列。如果发起者传入自己服务日志条目,而且能够将其注入到我们的日志中,可能会造成混乱。
    5. Plan for a way to log at increased verbosity. 对于某些类型的问题进行故障排除时,日志中未包含与造成问题的请求相关的充足细节,导致无法找出请求失败的原因。或者服务中可能提供了这些信息,但信息量可能太大,以至于无法抑制能证明记录这些信息是合理的。这时,以下方法可能有用:设置一个配置旋钮,让我们可以在调查问题时旋转这个旋钮,以暂时提高日志的详细程度。我们可以在单个主机上或者为服务机群的子集上按照某个采样率旋转此旋钮。但是切记,在完成后必须重新将旋钮转回原处。
    6. Keep metric names short (but not too short). Amazon 使用相同的服务日志序列化已经超过了15年。在此序列化中,每个计数器和计时器名称均在每个服务日志条目中以纯文本的形式重复。为了帮助最大程度上减小日志记录的开销,我们使用简短但具有描述性的计时器名称。
    7. Ensure log volumes are big enough to handle logging at max throughput. 我们在最大持续负载甚至过载的情况下对服务进行数小时的负载测试。我们需要确保在我们的服务处理超限流量时,服务仍然具有资源按照日志产生新日志条目的速度发走日志,否则磁盘最终将会填满。我们还可以将日志记录配置为发生在与根分区不同的文件系统分区上,这样系统就就不会在出现过多日志记录时崩溃。后文将会讨论一些缓解措施(例如使用与吞吐量成正比的动态采样),但无论采用哪种策略,测试都是至关重要的。
    8. Consider the behavior of the system when disks fill up. 在服务器的磁盘填满时,服务器无法将日志记录到磁盘中。如果发生这种情况,服务是应停止接受请求,还是应删除日志并在无监控的情况下继续运行?在无日志记录的情况下运行服务回充满风险,因此我们对系统进行测试,确保会检测到服务磁盘将满的状况。
    9. Synchronize clocks. 众所周知,分布式系统中“时间”的概念非常复杂。我们在分布式算法中并不依赖时钟同步,但对于日志来说时钟同步是必须的。Amazon运行Chrony或ntpd这样的守护进程来进行时钟同步,并且我们监控服务器的时钟偏差。为了更轻松的做到这一点,可以参阅这里。
    10. Emit zero counts for availability metrics. 错误计数很有用,但错误百分比同样很有用。要通过检测确定“可用性百分比”指标,一个很有用的策略是:在请求成功时发送1,而在请求失败时发送0。所产生的指标的“平均”统计数据即为可用率。在其他情况下,特意发送数据0点也可能很有用。通过这种方式,可以在缺少指标数据时发出“无数据”警报。这往往在服务出现大规模问题(线程死亡、网络隔离、领导者选举无效等)情况下或是监控系统出现问题时触发。例如,如果应用程序执行领导选举,那么在某个进程成为领导时定期发送1,而在该进程不是领导时发送0,可能对于监视跟随者的状况很有用。这样以来,如果某进程停止发送0,可以更容易的知道该进程中出现了错误,而且,如果领导发生了状况,该进程将无法接任领导。
    11. Make a child metrics object for each thread, and have each thread close its own metrics. 如果有多个线程处理同一个请求,可能会遇到一个线程运行时间长于另一个线程的情况,这时可能会看到尝试将指标写入已关闭指标对象时抛出异常,或者我们忽略这些异常,但会丢失指标数据。

    记录log的内容包括?

    1. Log the availability and latency of all of dependencies. 我们发现这在解答“为什么请求很慢”或是“为什么请求失败了”的问题上非常有用。如果没有此日志,我们只能将依赖关系的图形与服务的图形进行比较,并且猜测是否因以来服务的延迟激增导致我们正在调查的请求的失败。许多服务和客户端框架都会自动联结指标,但一些框架(例如AWS SDK)需要手动进行检测。
    2. Break out dependency metrics per call, per resource, per status code, etc. 如果我们在同一工作单元中多次与相同的依赖项交互,我们将分别包含每个调用的指标,并明确每个请求是哪个资源互动。例如,在调用Amazon DynamoDB时,一些团队发现包含每个表、每个错误代码甚至每个重试次数的时间和延迟指标很有帮助。这样可以更轻松的解决由于条件检查失败而导致服务因重试而变慢的情况。这些指标还揭示了客户端感知延迟增加实际上是由于节流重试或结果集分页而不是数据包丢失或网络延迟的情况。
    3. Record memory queue depths when accessing them. 如果请求与队列进行交互,而且我们正从队列中取出对象或将对象放入队列中,我们会在处理它时将当前队列深度记录到指标对象中。对于内存队列,获取它的成本非常低。对于分布式队列,在响应API调用时可以免费获得此元数据。此日志记录将在未来帮助查找积压工作和延迟的来源。此外,当我们从队列中取出对象时,我们会测量对象在队列中停留的时间长度。这意味着我们首先需要将自己的“入队时间”指标添加到消息中,然后再将其入队。
    4. Add an additional counter for every error reason. 考虑添加代码,对每个失败请求的特定错误原因进行计数。应用程序日志将包含导致失败的信息和详细的异常消息。但是,我们还发现,查看指标中错误原因随时间推移的趋势很有帮助,无需在应用程序日志中挖掘该信息。从每个失败异常类的单独指标开始是很方便的。
    5. Organize errors by category of cause. 如果所有错误均归入同一指标,该指标会变得具有干扰性且无用。至少,我们发现将“client's fault” 和 “server's fault”这两种错误区分开很重要。除此之外,进一步细分可能很有用。例如,在DDB中,客户端可能会发起条件写入请求,如果这些请求修改的项不符合请求中的前提条件,这些请求会返回错误。这些错误是特意造成的,而且我们预计它们会偶尔发生。但是,来自客户端的“invalid request”错误极有可能是我们需要修复的错误。
    6. Log important metadata about the unit of work. 在结构化指标日志中,我们还包含了有关请求的充足元数据,以便我们以后能确定请求来自谁和请求尝试做什么。这包括了客户端在遇到问题向我们求助时希望我们的日志包含的元数据。例如,DDB会记录请求正在交互的表的名称,以及类似于读取操作是否是一致性读取的元数据。但是,它不会记录存储到数据库或从数据库检索的数据。
    7. Protect logs with access control and encryption. 由于日志包含一定程度的敏感信息,我们需要采取措施保护这些数据。这些措施包括对日志进行加密、限制对问题进行故障排除的操作员的访问以及定期检查这种访问是否符合基准。
    8. Avoid putting overly sensitive information in logs. 日志需要包含一些有用的敏感信息。在Amazon,我们发现日志包含足够的信息以了解给定请求来自谁很重要,但我们不会包含过于敏感的信息,例如不影响路由或不影响处理请求的行为的请求参数。比如,如果代码对客户消息进行解析,但解析失败,则很重要的是不要记录消息内容,以便保护客户隐私,尽管这可能会对之后的故障排除造成影响。我们使用工具以选择加入而不是选择移出的方式来决定可以记录哪些信息,以防止记录以后添加的新敏感参数。API Gateway等服务允许配置可以包含在其访问日志中的数据,这起到了有效的选择加入机制的作用。
    9. Log a trace ID and propagate it in backend calls. 给定的客户请求可能涉及许多许多协同工作的服务。许多AWS请求可能仅涉及两三个服务,而amazon.com请求可能会涉及多得多的服务。为了在排除分布式系统的故障时清楚了解所发生的事情,我们在这些系统之间传播同一个trace ID,以便将来自不同系统的日志排列起来,从而看到故障在哪里发生。trace ID是一种meta request ID,由作为工作单元起点的“front door”服务贴附到分布式工作单元上。AWS X-Ray是一种通过提供一些这种传播来提供帮助的服务。我们发现将跟踪传递给我们的依赖项很重要。在多线程环境中,让框架代表我们进行此传播非常困难,而且很容易出错,因此我们已经习惯了在方法签名中传递trace ID和其他请求内容(例如metrics object)。我们还发现在方法签名中传递上下文对象很方便,这样我们就不必在将来找到类似的模式传递时进行重构。对于AWS团队来说,这不仅仅是解决我们的系统问题,还涉及客户解决他们的系统问题。在AWS服务代表客户彼此交互时,客户依赖在AWS服务之间传递的AWS X-Ray跟踪。它要求我们在服务之间传播客户AWS X-Ray跟踪ID,以便他们能获得完整的跟踪数据。
    10. Log different latency metrics depending on status code and size. 错误响应的速度通常很快,例如访问被拒绝、限制或是验证错误响应。如果客户端开始以很高的速度受到限制,这可能会让人产生延迟良好的错觉。为了避免这样的指标污染问题,我们为成功响应记录一个单独的计时器,并在我们的dashboards和alarms中关注该指标,而不是使用通用的时间指标。类似的,如果存在根据输入大小或响应大小而变慢的操作,我们会考虑发出分类的延迟指标,例如SmallRequestLatency 和 LargeRequestLatency。此外,我们确保我们的请求和响应得到适当的限制,以避免复杂的故障模式,但即使在精心设计的服务中,这种metric-bucketing technique 也能隔离客户行为和阻止令人分心的干扰信息进入dashboards。

    Application log best practices

    下面介绍的是 非结构化调试日志数据 相关的best practices!

    1. Keep the application log free of spam. 为了帮助在测试环境中进行开发和测试,我们可能会在请求路径上包含INFO和DEBUG级别的日志语句,但是,我们考虑在生产环境中禁用这些日志级别。我们并不依赖应用程序日志来获取请求跟踪信息,而是将服务日志视为跟踪信息的存放地,我们在其中可以轻松产生指标和查看一段时间内的总体趋势。但是,这里不存在非黑即白的规则。我们的方法是:不断审查日志,看看日志的干扰性是过高还是不够高,然后随时间的推移调整日志级别。例如,当我们在深入研究日志时,经常发现干扰性过高的日志语句出现,或者我们希望拥有的指标没有。幸运的是,这些改进通常易于运行。
    2. Include the corresponding request ID. 在我们对应用程序日志中的错误进行故障排查时,我们常常希望看到与触发该错误的请求或发起者有关的详细信息。如果两个日志均包含相同请求的ID,则我们可以轻松的从一个日志跳转到另一个日志。如果正确配置了应用程序日志记录库,这些库将会写出对应的请求ID,而且该请求ID将设置为ThreadLocal。如果应用程序是多线程的,在线程开始处理新请求时特别要注意设置正确的请求ID。
    3. Rate-limit an application log's error spam. 通常,服务不会向应用程序日志发送大量信息,但如果它突然开始显示大量错误,它可能会突然开始以很高的速度写入非常大的日志条目(带有堆栈跟踪)。我们发现了一种预防此问题的方法,即限制指定的日志记录程序记录日志的频率。
    4. Prefer format strings over String#format or string concatenation. 较旧的应用程序日志API操作接受单个字符串消息而不是log4j2的varargs格式字符串API。如果使用DEBUG语句来检测代码,但在ERROR级别下配置生产环境,则格式化会被忽略的DEBUG消息字符串可能是浪费功夫。某些日志记录API操作支持传入任何对象,这些对象只有在写出日志条目时才会调用它们的toString()方法。没懂
    5. Log request IDs from failed service calls. 如果调用某个服务且该服务返回错误,它可能会返回请求ID。我们发现字啊日志中包含请求ID很有用,这样做能让我们在需要跟进该服务拥有者时有办法让他们轻松找到自己的响应服务日志条目。不过超时错误会带来麻烦,原因是服务可能尚未返回请求ID,或者客户端库可能未解析该ID。尽管如此,如果我们获得了服务返回请求的ID,我们会记录该ID。

    高吞吐量服务的best practices

    对于Amazon 的绝大多数服务,为每个请求进行日志记录不会强行加上不合理的成本开销。吞吐量较高的服务会进入一个灰色地带,但我们通常仍会为每个请求进行日志记录。例如,DDB最高峰时每秒处理超过2000万个请求(仅仅是amazon内部的流量),因此人们很自然会假定该服务不会记录非常多的信息,但实际上,出于排除故障以及审计和合规的原因,它会记录每个请求。下面提供了一些在amazon使用的高级技巧,以更高的单主机吞吐量下提高日志记录的效率:

    1. Log sampling. 考虑每隔N个条目写入一个条目,而不是写入每个条目。每个条目还要包括跳过了多少条目,以便指标聚合系统可以估计它计算的指标中的真实日志量。蓄水池采样之类的其他采样算法提供了更具代表性的样本。在其他算法中,日志记录错误或缓慢的请求优先于成功的快速请求。然而,通过采样,失去了帮助客户和排除特定故障的能力。某些合规要求完全禁止这样做。
    2. Offload serialization and log flushing to a separate thread. 这是一个简单且常用的方法。
    3. Frequent log rotation. 每小时转换一次日志文件的日志看起来很方便,能减少您需要处理的文件,不过每分钟轮换一次能在几个方面带来改进。例如,读取和压缩日志文件的代理将从页面缓存而不是磁盘读取文件,而且压缩和发送日志所需的CPU和IO操作将分散到整个小时中执行,而不总是在小时结束时触发。
    4. Write logs pre-compressed. 如果日志发送代理在向存档服务发送日志之前压缩日志,则系统CPU和磁盘将定期出现操作高峰。通过将压缩的日志流式传输到磁盘,可以分摊此开销并将磁盘IO操作减少一半。不过这会带来一些风险。我们发现,使用能在应用程序崩溃时处理截断的文件的压缩算法很有用。
    5. Write to a ramdisk / tmpfs. 让服务将日志写入到内存(直至日志从服务器中发走)可能比写入到磁盘更为容易。根据我们的经验,这在每分钟而不是每小时轮换一次日志的情况下效果最好。
    6. In-memory aggregates. 如果必须在单台计算机上每秒处理数万个事务,则为每个请求写入一个日志条目可能会因为代价过高而不可行。但是,逃过这一步会显著降低可见性,因此我们发现不要过早优化是有帮助的。
    7. Monitor resource utilization. 我们密切关注我们离某些扩展限制还有多近。我们测量每个服务器的IO和CPU资源使用情况,以及日志记录代理消耗了多少资源。当我们执行负载测试时,我们会运行它们足够长时间,以便我们可以证明我们的日志发送代理能够跟得上我们的吞吐量。

    部署合适的日志分析工具

    在amazon内部,我们运营自己编写的服务,因此,我们全都需要成为它们的故障排除专家。这包括能够毫不费力的进行日志分析。我们可以使用许多工具 - 从用于查看相对较少日志的本地日志分析,到用于筛选并聚合来自海量日志的结果的分布式日志分析。
    我们发现,投资于团队的工具和Runbook非常重要。如果现在日志很小,但服务预计会随时间的推移而增长,我们会关注我们当前的工具何时会停止扩展,以便我们可以投资采用分布式日志分析解决方案。

    本地日志分析

    为了执行日志分析过程,可能需要具备使用不同Linux命令行实用程序的经验。例如,对于常见的“在日志中找到最大流量生产者的IP地址”任务,只需要执行以下命令:

    cat log | grep -P "^RemoteIp=" | cut -d= -f2 | sort | uniq -c | sort -nr | head -n20
    

    但是还可以使用许多其他实用工具来解答有关日志的更复杂的问题,这些工具包括:

    分布式日志分析

    任何大数据分析服务都可以用来执行分布式日志分析,这些服务包括Amazon EMR、Athena、Aurora、Redshift等。但是,某些服务已经福袋了日志记录系统,例如CloudWatch Logs。

    结论

    作为service owner和SDE,我们会花费大量时间来查看检测的输出(例如:graphs on dashboards, individual log files)和实用分布式日志分析工具(例如CloudWatch Logs Insights)。当我们研究日志时,首先可以提出一些问题,例如 “为何此指标会在这里出现峰值”,或者“可以降低次操作的延迟吗”。在我们无法回答这些问题时,我们可以找出一些对代码很有用的某些检测,然后添加该检测、进行测试并给组员发出CR。

    尽管我们使用的托管服务附带了许多metrics,但我们需要花费大量的心思来检测我们自己的服务,以便获得更加有效运营这些服务所需的可见性。如果出现运营事件,我们需要迅速确定发生问题的原因,以及能缓解该问题的对策。dashboards上包含合适的metrics十分重要,这可以让我们快速进行诊断。此外,由于我们不断改变服务、添加新功能以及改变服务与其依赖项交互的方式,因此,更新和添加正确工具的行为将会永远持续进行。


    Xinli
    1 声望0 粉丝