从 0 到 1400 万用户,3 名工程师如何撑起 Instagram?
Instagram,是 Meta 于 2010 年推出的一款免费提供在线图片及视频分享的社群应用软件。在推出短短一年的时间里,Instagram 迅速“蹿红”,它的用户数量从 0 增长到了 1400 万。而在这背后,只有三名工程师在支撑。
Instagram 的成功,成为很多软件产品学习的楷模,那么它究竟是怎么做到的?背后运用了哪些技术栈?近日,一篇文章初探了其中的秘密。
来源:https://engineercodex.substack.com/p/how-instagram-scaled-to-14-million
以下为译文:
从 2010 年 10 月到 2011 年 12 月,Instagram 在短短一年多的时间里,用户数量从 0 增长到了 1400 万。他们仅仅依靠 3 名工程师实现了这一壮举。
他们之所以能够做到这一点,是因为遵循了 3 个关键原则并采用了可靠的技术堆栈。
Instagram 的指导原则:
1. 保持极度简单。
2. 不要重新发明轮子。
3. 在可能的情况下使用经过验证的、稳定的技术。
Instagram 背后的技术堆栈
早期的 Instagram 基础设施在 AWS 上运行,使用了带有 Ubuntu Linux 的 EC2。EC2 是亚马逊的一项服务,允许开发人员租用虚拟计算机。
为了简化说明,这里从工程师的角度来看用户会话的生命周期(下文标注为“Session:”),从而捋清楚 Instagram 所用到的一些技术。
前端
Session:用户打开 Instagram 应用。
Instagram 最初于 2010 年作为 iOS 应用推出。由于 Swift 于 2014 年发布,我们可以猜测 Instagram 是使用 Objective-C 以及其他如 UIKit 的组合技术来编写的。
负载均衡
Session:打开应用后,向后端发送请求以获取主要的动态照片,该请求将被发送到 Instagram 的负载均衡器。
Instagram 使用了亚马逊的弹性负载均衡器。工程师租用了 3 个 NGINX 实例,并根据它们的健康状态进行交替切换。
每个请求首先会被发送到负载均衡器,然后再路由到实际的应用服务器。
后端
Session:负载均衡器将请求发送到应用服务器,应用服务器负责保存处理请求的逻辑。
Instagram 的应用服务器使用 Django 框架以及它是用 Python 编程语言编写的,并选择 Gunicorn 作为他们的 WSGI 服务器。
值得一提的是,WSGI(Web 服务器网关接口)将请求从 Web 服务器转发到 Web 应用程序。
Instagram 使用 Fabric 在多个实例上并行运行命令。这允许他们在几秒内部署代码。
这些应用服务器运行在超过 25 台亚马逊高性能 CPU 大型服务器上。由于服务器本身是无状态的,当需要处理更多请求时,他们可以添加更多服务器。
通用数据存储
Session:应用服务器意识到请求需要主要动态数据。为此,让我们假设它需要:
最新相关的照片 ID
与这些照片 ID 匹配的实际照片
这些照片的用户数据。
数据库:Postgres
Session:应用服务器从 Postgres 中获取最新相关的照片 ID。
应用服务器会从存储了大部分 Instagram 数据的 PostgreSQL 中提取数据,包括用户和照片元数据。
Postgres 和 Django 之间的连接使用 Pgbouncer 进行连接池化管理。
由于 Instagram 收到的数据量很大(每秒超过 25 张照片和 90 个点赞),工程师对数据进行了分片。他们使用代码将数千个“逻辑”分片映射到少数物理分片上。
Instagram 面临并解决的一个有趣挑战是生成可以按时间排序的 ID。他们生成的可按时间排序的 ID 如下:
用 41 位表示毫秒级时间(使用自定义时期,可提供 41 年的 ID)
用 13 位表示逻辑分片 ID
用 10 位表示自动递增序列,模数为 1024。这意味着我们可以在每个分片中每毫秒生成 1024 个 ID
由于在 Postgres 中使用可按时间排序的 ID,应用服务器已成功接收到最新相关的照片 ID。
照片存储:S3 和 Cloudfront
Session:应用服务器获取与这些照片 ID 匹配的实际照片,并使用快速 CDN 链接,以便为用户快速加载这些照片。
数千字节的照片存储在 Amazon S3 中。这些照片通过 Amazon CloudFront 迅速提供给用户。
缓存:Redis 和 Memcached
Session:为了从 Postgres 中获取用户数据,应用服务器(Django)使用 Redis 将照片 ID 与用户 ID 匹配。
Instagram 使用 Redis 存储了大约 3 亿张照片与创建它们的用户 ID 的映射,以便在获取主要动态数据、活动动态数据等时知道要查询哪个分片。所有的 Redis 都存储在内存中,以降低延迟,并分布在多台机器上。
通过一些巧妙的哈希处理,Instagram 成功地将 3 亿个键映射存储在不到 5GB 的空间中。
为了知道要查询哪个 Postgres 分片,需要这种 photoID 到 user ID 的键-值映射。
Session:由于最近已经缓存响应,通过高效的 Memcached 缓存获取用户数据,从 Postgres 获取数据的速度很快。
对于一般的缓存,Instagram 使用了 Memcached。那时他们有 6 个 Memcached 实例。在 Django 上使用 Memcached 相对简单。
有趣的事实:2 年后,即在 2013 年,Facebook 发布了一篇关于如何扩展 Memcached 以帮助他们处理每秒数十亿请求的重要论文。
Session:用户现在可以看到主页动态,其中包含他正在关注的人的最新照片。
主从设置
Postgres 和 Redis 都在主从设置下运行,并使用 Amazon EBS(弹性块存储)的快照功能来频繁备份系统。
推送通知和异步任务
Session:现在,假设用户关闭了应用,但后来收到了一个朋友发布了一张照片的推送通知。
这个推送通知是使用 pyapns 发送的,以及 Instagram 发送出的十亿多条其他推送通知。Pyapns 是一个开源的通用苹果推送通知服务(APNS)提供商。
Session:用户非常喜欢这张照片!所以他决定在 Twitter 上分享它。
在后端,这个任务被推送到 Gearman,这是一个任务队列,用于分配适合的机器来处理工作。Instagram 拥有大约 200 个 Python 工作程序来消耗 Gearman 任务队列。
Gearman 被用于多个异步任务,比如向所有用户的粉丝(称为粉丝传播)推送活动信息(比如发布新照片)。
监控
Session:糟糕!Instagram 应用程序崩溃了,因为服务器上出现了错误,并发送了错误的响应。三名 Instagram 工程师立即收到警报。
Instagram 使用 Sentry,一个开源的 Django 应用程序,实时监控 Python 错误。
Munin 用于绘制系统范围的指标和警告异常。Instagram 有很多自定义的 Munin 插件来跟踪应用级别的指标,比如每秒发布的照片数量。
Pingdom 用于外部服务监控,PagerDuty 用于处理事故和通知。
最终架构概述
推荐阅读:
▶ 余承东何小鹏隔空「互怼」,车圈大佬热议 AEB;马斯克的 AI 大模型来了;OpenAI 首届开发者大会被“剧透”|极客头条
▶Gartner 2024 年十大战略技术趋势,半数与 AI 相关