浅谈Instagram的Web Service性能

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

  Django是用Python语言写的开源web框架,应用广泛。目前,照片分享应用Instagram采用的开发框架就是Django(用来做逻辑处理)。出于简洁性与实用性的考虑,我们选择Python,这也恰恰符合我们的生活信条——先做简单的事。当然,我们还要权衡性能。仅仅两年时间,Instagram的用户量就已经翻倍了,最近更是突破5亿用户。为了使我们的平台持续平稳扩大,我们急需提升web服务性能。去年,我们优先考虑性能,另外,在下半年,我们不增加Django节点容量,也实现了用户量的持续增长。除此,我们还会在本帖中分享一些我们开发的工具以及如何使用这些工具来优化日常部署。

性能为何如此重要?

像其他软件一样,Instagram也受到类似服务器及数据中心供电等问题的困扰。所以,在性能方面,我们主要有两个目标:

1.如果因自然灾害或某个区域网络故障等因素造成某个数据中心无法正常工作,Instagram应该能及时将数据转出,并正常运行。

2.Instagram能够自由地推出新产品和新功能,不受内存限制。

为了实现这两个目标,我们必须时刻监控我们的系统,同时避免衰退问题。

何为性能

通常,每台服务器的有效CPU时间是Web services性能的瓶颈。这么一来,性能就好比用同等的CPU资源去完成更多的任务,即每秒处理更多的用户请求(RPS requests per second)。那么,我们当前的性能如何,该如何量化?目前,我们使用“每次请求的平均CPU时间”这一指标估计性能。当然,如果使用这一衡量标准,就必然会存在两个局限:

1.设备的多样性。CPU型号和CPU负载都会影响CPU,所以CPU处理时间不是衡量性能的最佳方法。

2.请求影响着数据。无论是轻量级或重量级的请求,新增和移除数据的时候都会影响性能,所以,每次请求的CPU处理时间也不是衡量性能的理想方案。

相比于CPU处理时间这一衡量标准,CPU指令数会更好,因为对于相同的请求,它不受CPU型号和CPU负载的影响,并能得到一致的结果。另外,我们没有将数据关联到所有用户请求上,而是采用一种叫做“每个活动用户”的机制。最终,我们衡量性能的标准是“在高峰期每个活动用户的CPU指令数”。确定了度量标准,我们下一步要做的就是通过分析Django来解决衰退问题。

Django web服务的性能分析

分析Django web 服务,有两个关键问题我们需要作出说明:

1.CPU衰退会发生吗?

2.发生CPU衰退的原因是什么以及如何应对?

要回答第一个问题,我们需要跟进“每个活动用户CPU指令数”这一指标。如果该指标增加,那么就发生了CPU衰退。

为此,我们开发了名为Dynostats的工具。一方面, Dynostats借助Django中间件,记录一些关键性能,包括CPU总指令数、端到端请求时延以及访问内存、缓存、数据库的时间等性能指标,并匀速采样用户请求数据。另一方面,每个用户请求都有大量可供我们聚合的元数据,例如端点名称、HTTP请求返回码、服务该请求的服务器名称以及最新请求的提交哈希码。对于单个请求记录来说,以上两个方面真的太赞了,因为我们可以在不同的维度上进行切割,这也将帮助我们加快找到CPU衰退的原因。比如,根据它们的端点名称聚合所有请求,得到下面时间序列图,从图中我们可以很容易判断是否发生了衰退、哪个端点发生了衰退。

wave.png

CPU指令数对衡量性能固然重要,但它们也很难获得。Python没有公共库来支持直接访问CPU硬件计数器(CPU 硬件计数器,即可编程CPU寄存器,用于度量像CPU指令数等性能指标)。然而,Linux内核提供了perf_event_open系统调用。通过Python ctypes桥接技术,调用标准C库中的系统调用函数syscall,因为该调用函数兼容C的数据类型,所以我们就可以进行硬件计数器编程并读取数据了。

通过Dynostats,我们已经可以找出并能深挖造成CPU衰退的原因,例如哪个端点受到的影响最多,谁提交了真正会导致CPU衰退的变更等。但是,即便开发者发现他们的变更已经导致一次CPU衰退发生,他们通常也难以找出问题所在。如果问题很明显,那么衰退可能就不会在一开始就被提交!

只要Dynostats检测到刷退,开发者就可以利用Python分析器查明衰退问题的根源,这也正是为什么我们非得要一个Python分析器的原因。另外,我们决定将一个现成的Pytho分析器改造为cProfile 。通常,cProfile 会提供一组数据来显示一个程序不同片段的执行时长及执行频率。但我们没有及时度量,而是采用cProfile并取代CPU指令计数器(其从硬件计数器中读取)。采样请求数据后,我们将得到的这些数据发送到数据管道。同时,我们还会发送一些元数据,比如Dynostats中涉及到的服务器名称、集群、区域、端名称等。

在数据管道的尾部,我们设置了一个跟踪器来消费数据。这个跟踪器可以解析cProfile的统计数据并创建能够表示Python函数级别的 CPU指令的实体。这样一来,我们可以通过Python函数来聚合CPU指令,从而使得分析CPU衰退的成因更加简单。

监控与警报机制

在Instagram,我们每天要将后端代码部署30-50次。每次部署都可能导致CPU衰退。既然每次数据迁移都会包含至少一个diff指令,那么我们就可以很容易的找到衰退的成因。在每次发布数据前后,性能监控机制都会在Dynostats中进行CPU指令扫描来监控变更,一旦变更超出某个阈值,警报机制就会发出警告。由于CPU衰退发生时间较长,一些端点负载过重,我们进行每天和每周的扫描监控。

当然,导致CPU衰退的绝不仅仅是部署时产生的变更。在绝大多数情况下,新的功能及新的代码路径依赖于全局环境变量。针对一部分用户发布一些新的功能就是一个常见的例子。这种数据信息,我们把它当作是用户在Dynostats及cProfile统计数据之外的元数据字段。通过这些字段可以发现,有些CPU衰退是因为环境变量的更改。居于这个原因,我们就可以在造成性能降低之前捕获衰退。

接下来呢?

Dynostats 、cProfile以及监控和警报机制,能够有效地找出大多数导致CPU衰退的元凶。这些进展已经帮助我们避免了超过50%的不必要的CPU衰退,不然我们连发现CPU衰退都很难。

当然,我们还有改进的地方,比如,可以试着将其嵌入到Instagram的日常部署中,并尽量简化:

1.虽然CPU指令数目这一指标比其它指标(如CPU时间)更稳定,但仍存在一些差异会干扰警报机制。保持信噪比相对合理非常重要,因为开发者们需要专注于真正的衰退问题。或许,引入置信区间或者仅在信噪比过高的情况下警报能够改善这一问题。除此,我们可以为不同端点设置不同阈值。

2.通过更改环境变量来应对CPU衰退问题存在一个局限,那就是我们必须手动启用Dynostats中的比较日志。随着环境变量数目的增加以及更多新功能的开发,这肯定会妨碍规模的扩大。但是,我们能够利用一个自动化框架来调度这些比较日志,并对所有环境变量进行遍历,一旦发现衰退就发出警告。

3.cProfile处理封装函数以及其子函数的能力有待增强改善。

我们已经极力搭建高性能的Instagram web服务,同时坚信可以使用Python来扩大我们的服务设施规模。另外,我们也开始投入更多的时间精力来改善Python语言本身,并着手从版本2到版本3的升级。最后,为了持续不断的改善服务,为了让开发者能更有效地进行开发,我们会加倍努力,不断求索,期待不久就能跟大家分享。

本文作者Min Ni是Instagram 的软件工程师。


英文原文:https://engineering.instagram.com/web-service-efficiency-at-instagram-with-python-4976d078e366#.ucrono6mm
译者:Troy_BonneChance
 

2月15日11:00到13:00网站停机维护,13:00前恢复
iPy智能助手 双击展开
查看更多聊天记录
(Ctrl+回车)换行