Instagram 如何仅用 3 名工程师扩展到 1400 万用户
原文链接:How Instagram scaled to 14 million users with only 3 engineers (substack.com) (opens new window)
# 译文
(其他说明:使用edge浏览器的自带的翻译功能,居然把题目的意思都翻译错了。所以使用浏览器插件SingleFile保存为html文件,然后上传文件到poe的claude2-100k中,让它做的翻译,内容相对准确)
Instagram 在 2010 年 10 月到 2011 年 12 月的一年多时间里,用户数从 0 扩展到了 1400万。而这仅仅依靠 3 名工程师完成。
他们遵循了 3 个关键原则,并采用了可靠的技术栈。
Instagram 的指导原则:
保持极简。
不要重造轮子。
尽可能使用成熟可靠的技术。
技术栈简单解释:
早期 Instagram 的基础设施运行在 AWS 上,使用 EC2 的 Ubuntu Linux。 EC2 是亚马逊提供的可租用虚拟计算机的服务。
为了简单起见,我们从用户角度来看一个用户会话的流程。(标记为 Session)
前端
Session: 用户打开 Instagram 应用。
Instagram 最初在 2010 年作为 iOS 应用推出。由于 Swift 在 2014 年才发布,我们可以假设 Instagram 是使用 Objective-C 和一些其他技术如 UIKit 开发的。
负载均衡
Session: 在打开应用后,一个获取主 Feed 照片的请求被发往后端,先被 Instagram 的负载均衡器接收。
Instagram 使用的是亚马逊的 Elastic Load Balancer。他们有 3 个 NGINX 实例,根据健康状况被轮流使用。
每个请求首先到达负载均衡器,然后再被路由到实际的应用服务器。
后端
Session: 负载均衡器将请求发往应用服务器,应用服务器负责正确处理请求的逻辑。
Instagram 的应用服务器使用 Django 和 Python 开发,使用 Gunicorn 作为 WSGI 服务器。WSGI(Web Server Gateway Interface)用来将请求从 web 服务器转发到 web 应用。
Instagram 使用 Fabric 在许多实例上并行执行命令。这使得代码可以在几秒钟内被部署。
这些服务器运行在超过 25 台亚马逊的高性能 Extra-Large 机器上。由于服务器本身是无状态的,当需要处理更多请求时,他们可以添加更多机器。
数据存储
Session: 应用服务器发现请求需要主 Feed 的数据。例如需要:
最新的相关照片 ID
匹配这些照片 ID 的实际照片
这些照片的用户数据
数据库:Postgres
Session: 应用服务器从 Postgres 获取最新的相关照片 ID。
应用服务器会从 PostgreSQL 获取数据,PostgreSQL 存储了 Instagram 大部分的数据,比如用户和照片元数据。
Postgres 和 Django 之间的连接通过 Pgbouncer 池化。
由于接收数据量巨大(每秒超过 25 张照片和 90 个赞),Instagram 对数据进行了分片。他们使用代码将几千个逻辑分片映射到少数物理分片。
Instagram 面临并解决的一个有趣的挑战是生成可以按时间排序的 ID。他们生成的可按时间排序的 ID 如下:
41 bit 表示时间戳(毫秒),可以支持 41 年的 ID (自定义时期)
13 bit 表示逻辑分片 ID
10 bit 表示一个自动增长的序列,模数 1024。这意味着我们可以每毫秒内每个分片生成 1024 个 ID
(可以在这里阅读更多)
得益于 Postgres 中可按时间排序的 ID,应用服务器成功获取到了最新的相关照片 ID。
照片存储:S3 和 Cloudfront
Session: 应用服务器使用快速的 CDN 链接获取匹配这些照片 ID 的实际照片,使其可以快速加载。
数 TB 的照片存储在亚马逊 S3 中。这些照片通过亚马逊 CloudFront 快速提供给用户。
缓存:Redis 和 Memcached
Session: 为了从 Postgres 获取用户数据,应用服务器(Django)使用 Redis 中的映射匹配照片 ID 和用户 ID。
Instagram 在 Redis 中存储大约 3 亿条照片到创建它们的用户 ID 的映射,以便知道在获取主 Feed、活动 Feed 等的时候需要查询哪个分片。所有的 Redis 数据存储在内存中以降低延迟,并分片在多台机器上。
通过一些巧妙的散列,Instagram 能够在不到 5GB 的空间里存储 3 亿条键值映射。
这个照片 ID 到用户 ID 的键值映射对于知道需要查询哪个 Postgres 分片是必需的。
Session: 通过使用 Memcached 高效的缓存,从 Postgres 获取用户数据很快,因为响应已经被缓存。
对于通用缓存,Instagram 使用了 Memcached。他们当时有 6 个 Memcached 实例。在 Django 上加 Memcached 相对简单。
有趣的事实:在 2013 年,也就是 2 年后,Facebook 发表了一篇论文,讲述了他们如何扩展 Memcached 从而帮助他们处理每秒数十亿次请求。
Session: 用户现在看到主界面上显示自己关注的人发布的最新的图片。
主从复制
Postgres 和 Redis 都以主从方式运行,并使用亚马逊 EBS (Elastic Block Store) 快照频繁备份系统。
推送通知和异步任务
Session: 现在,假设用户关闭了应用,但随后收到一个好友发布照片的推送通知。
这个推送通知通过 pyapns 发送,和 Instagram 已经发送出的数十亿推送通知一样。pyapns 是一个开源的通用苹果推送服务提供商。
Session: 用户非常喜欢这张照片!所以他决定把它分享到 Twitter 上。
后端将这个任务推入 Gearman,一个任务队列,将工作分发给更适合的机器。Instagram 有大约 200 个 Python 工作进程消费 Gearman 任务队列。
Gearman 用于多个异步任务,比如向一个用户所有的关注者推送活动(像是新照片发布),这称为 fanout。
监控
Session: 呀,Instagram 应用崩溃了,因为服务器端出了错误导致发送了错误的响应。3 名 Instagram 工程师立即收到警报。
Instagram 使用开源的 Django 应用 Sentry 来实时监控 Python 错误。
Munin 用于图形化系统指标并在异常时报警。 Instagram 有大量定制的 Munin 插件来跟踪应用级指标,比如每秒发布照片数量。
Pingdom 用于外部服务监控,PagerDuty 用于处理事件和通知。
最终架构概览
非常感谢您的阅读!就像早期的 Instagram,我也尽量保持简单。