Instagram 如何仅靠 3 名工程师就将用户数量扩大到 1400 万

从 2010 年 10 月到 2011 年 12 月,Instagram 在短短一年多的时间里,用户数量从 0 增长到 1400 万。做到这一点的只有 3 名工程师。他们之所以能做到这一点,是因为遵循了 3 个关键原则并拥有可靠的技术栈。

前端
用户打开 Instagram 应用程序。

Instagram 最初是在 2010 年推出的 iOS 应用程序。由于 Swift 于 2014 年发布,因此我们可以认为 Instagram 是使用 Objective-C 和 UIKit 等其他语言编写的。

负载平衡

  • 打开应用后,抓取主 feed 照片的请求会被发送到后台,并在后台碰到 Instagram 的负载平衡器。
  • Instagram 使用亚马逊的弹性负载平衡器。他们有 3 个 NGINX 实例,根据它们是否健康进行交换。
  • 每个请求在路由到实际应用服务器之前都会首先到达负载平衡器。

后台

  • 负载平衡器将请求发送到应用服务器,应用服务器拥有正确处理请求的逻辑。
  • Instagram 的应用服务器使用了 Django,它是用 Python 编写的,Gunicorn 是他们的 WSGI 服务器。
  • WSGI(Web Server Gateway Interface,网络服务器网关接口)将请求从网络服务器转发到网络应用程序。
  • Instagram 使用 Fabric 同时在多个实例上并行运行命令。这样就能在几秒钟内部署代码。

这些实例运行在超过 25 台亚马逊高CPU超大型机器上。由于服务器本身是无状态的,因此当他们需要处理更多请求时,可以添加更多机器。

一般数据存储
应用服务器发现请求需要主馈数据。为此,假设它需要

  • 最新的相关证件照片
  • 与这些照片 ID 匹配的实际照片
  • 这些照片的用户数据。

数据库Postgres
应用服务器从 Postgres 中获取最新的相关照片 ID。
应用服务器会从 PostgreSQL 中提取数据,因为 PostgreSQL 中存储了 Instagram 的大部分数据,如用户和照片元数据。

PostgreSQL 和 Django 之间的连接使用 Pgbouncer 进行池化。

由于 Instagram 接收的数据量很大(每秒超过 25 张照片和 90 个赞),他们对数据进行了分片。他们使用代码将几千个 "逻辑 "分片映射到几个物理分片上。

Instagram 面临并解决的一个有趣挑战是生成可按时间排序的 ID。他们生成的可按时间排序的 ID 是这样的:

  • 以毫秒为单位的 41 位时间(通过自定义纪元,我们可以得到 41 年的 ID)
  • 13 位表示逻辑分片 ID
  • 10 位表示自动递增序列,模数为 1024。这意味着我们可以为每个分区每毫秒生成 1024 个 ID

得益于 Postgres 中可按时间排序的 ID,应用服务器成功接收到了最新的相关照片 ID。

照片存储:S3 和 Cloudfront
应用服务器通过快速 CDN 链接获取与这些照片 ID 匹配的实际照片,以便快速加载给用户。

亚马逊 S3 中存储了数 TB 的照片。这些照片通过亚马逊 CloudFront 快速提供给用户。

缓存:Redis 和 Memcached
为了从 Postgres 中获取用户数据,应用服务器(Django)使用 Redis 将照片 ID 与用户 ID 进行匹配。
Instagram 使用 Redis 存储了约 3 亿张照片与创建这些照片的用户 ID 的映射,以便在获取主 feed、活动 feed 等的照片时知道要查询哪个分片。所有 Redis 都存储在内存中,以减少延迟,并在多台计算机上进行分片。

通过巧妙的散列处理,Instagram 能够在不到 5 GB 的空间内存储 3 亿个密钥映射。

要知道查询哪个 Postgres 分片,就需要照片 ID 到用户 ID 的键值映射。

由于使用了 Memcached 进行高效缓存,从 Postgres 获取用户数据的速度非常快,因为响应是最近缓存的。
对于一般缓存,Instagram 使用了 Memcached。当时他们有 6 个 Memcached 实例。将 Memcached 层叠到 Django 上相对简单。

有趣的事实是:2 年后的 2013 年,Facebook 发布了一篇具有里程碑意义的论文,介绍了他们如何扩展 Memcached 以帮助他们处理每秒数十亿次的请求。

主副本设置
Postgres 和 Redis 都在主副本设置中运行,并使用亚马逊 EBS(弹性块存储)快照来频繁备份系统。

推送通知和异步任务
现在,假设用户关闭了应用,但又收到推送通知说朋友发布了一张照片。
这个推送通知是使用 pyapns 和 Instagram 已经发送的其他十亿多条推送通知一起发送的。Pyapns 是一个开源、通用的苹果推送通知服务(APNS)提供商。

用户非常喜欢这张照片!所以他决定在 Twitter 上分享。
在后台,任务被推送到 Gearman 任务队列,该队列将工作分派给更合适的机器。Instagram 有大约 200 个 Python 工作者在使用 Gearman 任务队列。

Gearman 用于执行多个异步任务,例如向用户的所有关注者推送活动(如发布新照片)(称为 fanout)。

监测
Instagram 应用程序崩溃了,原因是服务器出错,发送了错误的响应。Instagram 的三名工程师立即收到警报。
Instagram 使用开源 Django 应用程序 Sentry 实时监控 Python 错误。

Munin 被用来绘制全系统指标图,并对异常情况发出警报。Instagram 有大量自定义 Munin 插件,用于跟踪应用程序级指标,如每秒发布的照片。

Pingdom 用于外部服务监控,PagerDuty 用于处理事件和通知。