上线第一年,Instagram 的技术栈都有哪些东西?

上线第一年,Instagram 的技术栈都有哪些东西?

// 伯乐在线编注:英文原文写于 2012 年,正好 Instagram 上线一年。

每次在活动中和其他工程师聊天时,被问的最多问题就是“你们的技术栈中都有什么?”。我们认为,从一个高层次来把驱动 Instagram 的东西讲清楚,会很有意思;也许在将来会有这其中某些系统的详细描述。

本文会讲述我们系统上线一年多点时间中的演变,其中部分系统我们已经重做了。大家也可从本文一窥一个仅有小型工程团队的创业公司,在一年多点时间内是如何扩展到 1400 万用户。我们选择某项系统的核心准则是:

  • 越简单越好
  • 不要重复造轮子
  • 尽量用成熟和稳定的技术

我们将自顶向下的进行叙述:

操作系统/托管

我们在 Amazon EC2 上运行 Ubuntu Linux 11.04(“Natty Narwhal”)。我们发现,在高流量下 EC2 安装 Ubuntu 之前的版本,会有各种不可预知的冻结发作,但 Natty 却很稳定。我们只有 3 名工程师,我们的需求仍在不断发展,所以自托管不是一个选择,我们已经深入探索过了。考虑到无与伦比的增长,不过我们可以在未来重新考虑自托管。

负载均衡

每一个对 Instagram 服务器的请求都会通过负载均衡机器。过去我们是运行 2 台 Nginx 机器,然后在机器之间进行轮循DNS。这个方法的缺点是,当其中一个机器需要被替换的时候,需要花时间进行DNS更新。最近,我们改用亚马逊的弹性负载平衡器,3个Nginx实例在后台可供换入换出(当健康检查结果不佳时,会自动跳出机器环)。我们也会在 ELB 级终止SSL,从而减少Nginx上的CPU负载。我们使用亚马逊的 route53 来做 DNS,他们最近在 AWS 控制台增加了一个很好的GUI工具。

应用服务器

接下来是处理请求的应用程序服务器。我们在亚马逊高速CPU超大型机器上运行Django,随着用户的增长,我们使用的机器数目已经从只有几台到超过25台了(幸运的是,这一部分很容易横向扩展,因为它们都是无状态的)。我们已经发现,我们实际的工作负载是受CPU限制,而不是受内存限制的,所以超大实例类型的高速处理器提供了正确的内存和处理器的平衡方式。​

我们使用 http://gunicorn.org / 作为我们的WSGI应用服务器;以前使用的是Apache和mod_wsgi,但是发现Gunicorn更易于配置,并且不那么消耗CPU。为了能立刻在许多机器上立刻执行命令(比如部署代码),我们使用Fabric,它最近增加了一个很有用的并行模式以至于部署任务只需要几秒钟。​

数据存储

我们的大部分数据(用户,照片元数据,标签,等等)都存储在PostgreSQL;以前写过关于我们如何在不同的Postgres实例间分片。我们的主要分片集群包括12个四核超大内存实例(以及放在不同区域的十二个副本。)

我们发现,亚马逊的网络硬盘系统(EBS)每秒磁盘寻道数远不够我们使用,所以把所有工作集放在内存中是非常必要的。为了得到合理的IO性能,我们使用mdadm在软件RAID中建立了EBS驱动器。

还有一个快速提示,我们发现 vmtouch 对管理内存数据是一个非常好的工具,尤其是当不能从一台机器到另一个没有活跃备份的机器的时候。这里有一份脚本,我们用它来解析一台机器上运行的一个vmtouch输出,并打印出相应的vmtouch命令,该命令​用于运行在另一个匹配当前内存状态的系统。​

所有PostgreSQL实例都运行在一个基于流式副本的启动程序中,我们使用EBS快照备份系统。我们使用XFS文件系统,可以在快照时冻结和解冻RAID阵列,以保证快照一致(最初的灵感来自于EC2快照一致)。为了启动副本流,我们的员工最喜欢的工具是repmgr

要从应用程序服务器连接到数据库,我们之前将PostgreSQL连接放入 Pgbouncer 池对性能有巨大影响。我们发现Christophe Pettus的博客有很多Django资源,以及PostgreSQL和Pgbouncer的小建议。

照片直接存储在亚马逊S3,目前存储了几TB的照片数据。我们使用Amazon CloudFront 作为 CDN,这有助于减少来自世界各地用户的图像加载时间(比如在日本,日本是第二个最欢迎我们的国家)。

我们还广泛使用了Redis,它支撑了主要的数据流、活动流、会话系统(这是我们的Django会话后端),以及其他相关系统。所有Redis的数据需要存储在内存中,所以我们也为Redis运行几个超大内存实例,偶尔对于给定的子系统在几个Redis之间分片。我们在一个主副本程序中运行Redis,并让副本不断保存数据库到磁盘上,并使用EBS快照备份这些数据库转储(我们发现在主副本上转储DB太繁重)。因为Redis允许写入副本,它使一个新的Redis机器的在线切换变得很容易,而不需要任何的停机时间。

对于地理搜索API,我们已经使用PostgreSQL几个月了,但一旦我们的媒体条目被分片,我们转而使用Apache Solr。它有一个简单的JSON接口,就我们的应用程序而言,这只是另一个能使用的API。

最后,与其他任何现代Web服务一样,我们使用Memcached缓存,目前有6个Memcached实例,使用 pylibmc 和 libmemcached 连接。亚马逊最近推出一个弹性缓存服务,但它并不比运行我们自己的实例更便宜,所以我们还没有急着去切换到新服务。

任务队列 & 推送通知

当用户决定将 Instagram 上的照片分享到 Twitter 或 Facebook,或者当我们需要通知实时订阅者一个新照片的到来时,我们把任务写入Gearman,Gearman是一个队列系统,起初是由Danga开发的。在任务队列上异步完成意味着媒体上传很快就能完成,而’繁重负载’可以在后台运行。在任意时间,都有大约200台机器(都是用Python写的)消费任务队列的消息,在分享服务之间切换。我们还在Gearman中送出响应,所以发布照片对于一个拥有大量粉丝的用户和一个新用户来讲,都能快速响应。​

为了做推送通知,我们发现最符合成本效益的解决方案:https://github.com/samuraisam/pyapns,一个开源的Twisted服务,它已经为我们处理了超过十亿的推送通知,并且坚如磐石。​

监控

对于100个以上的实例,始终清楚模块之间运行情况非常重要。我们用 Munin 来将各个系统连接成图,也提醒我们是否有异常发生。我们基于 Python-Munin,写了很多常用Munin插件,来连接不是系统级的图(例如,每分钟用户注册数,每秒的上传照片数,等等)。我们使用 Pingdom 作为服务的外部监督,使用 PagerDuty 处理通知和突发事件。​

对于Python的错误报告,我们使用 Sentry,,一个非常赞的开源Django程序,由Disqus的小伙伴开发。在任何时间,我们可以登录并实时看到我们的系统发生了什么错误。​

Comments are closed.