PyCon 简介
PyCon 是全世界最大的以 Python 编程语言为主题的技术大会。大会由 Python 社区组织,每年举办一次。在大会上,来自世界各地的 Python 用户与核心开发者齐聚一堂,共同分享 Python 世界的新鲜事、Python 语言的应用案例、使用技巧等等内容。
Instagram 简介
Instagram 是一款移动端的照片与视频分享软件,由 Kevin Systrom 和 Mike Krieger 在 2010 年创办。Instagram 在发布后开始快速流行。于 2012 年被 Facebook 以 10 亿美元的价格收购。而当时 Instagram 的员工仅有区区 13 名。
如今,Instagram 的总注册用户达到 30 亿,月活用户超过 7 亿(作为对比,微信最新披露的月活跃用户为 9.38 亿)。而令人吃惊的是,这么高的访问量背后,竟完全是由以速度慢著称的 Python + Django 支撑。
在 Python 2017 上,Instagram 的工程师们带来了一个有关 Python 在 Instagram 的主题演讲,同时还分享了 Instagram 如何将整个项目运行环境升级到 Python 3 的故事。
本文为该次演讲的内容摘要。
Python+Django如何支撑Instagram?
Python @Instagram
为什么选择 Python 和 Django
Instagram 选择 Django 的原因很简单,Instagram 的两位创始人 (Kevin Systrom and Mike Krieger) 都是产品经理出身。在他们想要创造 Instagram 时,Django 是他们所知道的最稳定和成熟的技术之一。
时至今日,即使已经拥有超过 30 亿的注册用户。Instagram 仍然是 Python 和 Django 的重度使用者。Instagram 的工程师 Hui Ding 说到: 『一直到用户 ID 已经超过了 32bit int 的限额(约为 20 亿),Django 本身仍然没有成为我们的瓶颈所在。』
不过,除了使用 Django 的原生功能外,Instagram 还对 Django 做了很多定制化工作:
扩展 Django Models 使其支持 Sharding (一种数据库分片技术),Instagram Engneering 博客专门为这件事情写过一篇博客,可参阅:Sharding & IDs at Instagram
手动关闭 GC(垃圾回收)来提升 Python 内存管理效率,他们同样也写过一篇博客来说明这件事情:Dismissing Python Garbage Collection at Instagram
在位于不同地理位置的多个数据中心部署整套系统
Python 语言的优势所在
Instagram 的联合创始人 Mike Krieger 说过: 『我们的用户根本不关心 Instagram 使用了哪种关系数据库,他们当然也不关心 Instagram 是用什么编程语言开发的。』
所以,Python 这种 简单 而且 实用至上 的编程语言最终赢得了 Instagram 的青睐。他们认为,使用 Python 这种简单的语言有助于塑造 Instagram 的工程师文化,那就是:
专注于定位问题、解决问题– 而不是工具本身的各种花花绿绿的特性
使用那些经过市场验证过的成熟技术方案– 而不用被工具本身的问题所烦扰
用户至上:专注于用户所能看到的新特性,为用户带去价值
但是,即使使用 Python 语言有这么多好处,它还是很慢,不是吗?
不过,这对于 Instagram 不是问题,因为他们认为:『Instagram 的最大瓶颈在于开发效率,而不是代码的执行效率』
At Instagram, our bottleneck is development velocity, not pure code execution.
所以,最终的结论是:你完全可以使用 Python 语言来实现一个超过几十亿用户使用的产品,而根本不用担心语言或框架本身的性能瓶颈。
如何提升运行效率
但是,即使是选用了拥有诸多好处的 Python 和 Django。在 Instagram 的用户数迅速增长的过程中,性能问题还是出现了:服务器数量的增长率已经慢慢的超过了用户增长率。Instagram 是怎么应对这个问题的呢?
他们使用了这些手段来缓解性能问题:
开发工具来帮助调优:Instagram 开发了很多涵盖各个层面的工具,来帮助他们进行性能调优以及找到性能瓶颈。
使用 C/C++ 来重写部分组件:把那些稳定而且对性能最敏感的组件,使用 C 或 C++ 来重写,比如访问 memcache 的 library。
使用 Cython:Cython 也是他们用来提升 Python 效率的法宝之一。
除了上面这些手段,他们还在探索异步 IO 以及新的 Python Runtime 所能带来的性能可能性。
升级到 Python 3
在相当长的一段时间,Instagram 都跑在 Python 2.7 + Django 1.3 的组合之上。在这个已经落后社区很多年的环境上,他们的工程师们还打了非常非常多的小 patch。难道他们要被永远卡在这个版本上吗?
所以,在经过一系列的讨论后,他们最终做出一个重大的决定:升级到 Python 3!!
事实上,Instagram 目前已经完成了将运行环境迁移到 Python 3 的工作 – 他们的整套服务已经在 Python 3 上跑了好几个月了。那么他们是怎么做到的呢?接下来便是由 Instagram 工程师 Lisa guo 带来的 Instagram 如何迁移到 Python 3 的故事。
Instagram 升级到 Python 3 的故事
为什么要升级到 Python 3
对于 Instagram 来说,下面这些因素是推动他们将运行环境迁移到 Python 3 的主要原因:
- 新特性:类型注解 Type Annotations
看看下面这段代码:
def compose_from_max_id(max_id):
‘‘‘@param str max_id‘‘‘
图中函数的 max_id 参数究竟是什么类型呢?int?tuple?或是 list? 等等,函数文档里面说它是 str 类型。
但随着时间推移,万一这个参数的类型发生变化了呢?如果某位粗心的工程师修改代码的同时忘了更新文档,那就会给函数的使用者带来很×××烦,最终还不如没有注释呢。
- 性能
Instagram 的整个 Django Stack 都跑在 uwsgi 之上,全部使用了同步的网络 IO。这意味着同一个 uwsgi 进程在同一时间只能接收并处理一个请求。这让如何调优每台机器上应该运行的 uwsgi 进程数成了一个麻烦事:
为了更好利用 CPU,使用更多的进程数?但那样会消耗大量的内存。而过少的进程数量又会导致 CPU 不能被充分利用。
为此,他们决定跳过 Python 2 中哪些蹩脚的异步 IO 实现 (可怜的 gevent、tornado、twisted 众),直接升级到 Python 3,去探索标准库中的 asyncio 模块所能带来的可能性。
- 社区
因为 Python 社区已经停止了对 Python 2 的支持。如果把整个运行环境升级到 Python 3,Instagram 的工程师们就能和 Python 社区走的更近,可以更好的把他们的工作回馈给社区。
确定迁移方案
在 Instagram,进行 Python 3 的迁移需要必须满足两个前提条件:
不停机,不能有任何的服务因此不可用
不能影响产品新特性的开发
但是,在 Instagram 的开发环境中,要满足上面这两点来完成迁移到 Python 3.6 这种庞大的工程是非常困难的。
基于主分支的开发流程
即便使用了以多分支功能著称的 git,Instagram 所有的开发工作都是主要在 master 分支上进行的,Instagram 所奉行的开发哲学是:『不管是多大的新特性或代码重构,都应该拆解成较小的 Commit 来进行。』
那些被合并进 master 分支的代码,都将在一个小时内被发布到线上环境。而这样的发布过程每天将会发生上百次。在这么频繁的发布频率下,如何在满足之前的那两个前提下来完成迁移变得尤其困难。
被弃用的迁移方案
创建一个新分支
很多人在处理这类问题时,第一个蹦进脑子的想法就是: 『让我们创建一个分支,当我们开发完后,再把分支合并进来』
但在 Instagram 这么高的迭代频率上,使用一个独立分支并不是好主意:
Instagram 的 Codebase 每天都在频繁更新,在开发 Python 3 分支的过程中,让新分支与现有 master 分支保持同步开销极大,同时极易出错
最终将 Python 3 分支这个改动非常多的分支合并回 Master 拥有非常高的风险
只有少数几个工程师在 Python 3 分支上专职负责升级工作,其他想帮助迁移工作的工程师无法参与进来
挨个替换接口
还有一个方案就是,挨个替换 Instagram 的 API 接口。但是 Instagram 的不同接口共享着很多通用模块。这个方案要实施起来也非常困难。
微服务
还有一个方案就是将 Instagram 改造成微服务架构。通过将那些通用模块重写成 Python 3 版本的微服务来一步步完成迁移工作。
但是这个方案需要重新组织海量的代码。同时,当发生在进程内的函数调用变成 RPC 后 ,整个站点的延迟会变大。此外,更多的微服务也会引入更高的部署复杂度。
所以,既然 Instagram 的开发哲学是:小步前进,快速迭代。他们最终决定的方案是:一步一步来,最终让 master 分支上的代码同时兼容 Python 2 和 Python 3。
开始迁移工作
既然要让整个 codebase 同时兼容 Python 2 和 Python 3,那么首先要符合这点的就是那些被大量使用的第三方 package。针对第三方 package,Instagram 做到了下面几点:
拒绝引入所有不兼容 Python 3 的新 package
去掉所有不再使用的 package
替换那些不兼容 Python 3 的 package
在代码的迁移过程中,他们使用了工具 modernize 来帮助他们。
使用 modernize 时,有一个小技巧:每次修复多个文件的一个兼容问题,而不是一下修复一个文件中的多个兼容问题。 这样可以让 Code Review 过程简单很多,因为 Reviewer 每次只需要关注一个问题。
使用单元测试来帮助迁移
对于 Python 这种灵活性极强的动态语言来说,除了真正去执行代码外,几乎没有其他比较好的检查代码错误的手段。
前面提到,Instagram 所有被合并到 master 的代码提交会在一个小时内上线到线上环境,但这不是没有前提条件的。在上线前,所有的提交都需要通过成千上万个单元测试。
于是,他们开始加入 Python 3 来执行所有的单元测试。一开始,只有极少数的单元测试能够在 Python 3 环境下通过,但随着 Instagram 的工程师们不断的修复那些失败的单元测试,最终所有的单元测试都可以在 Python 3 环境下成功执行。
单元测试的局限性
但是,单元测试也是有局限性的:
Instagram 的单元测试没有做到 100% 的代码覆盖率
很多第三方模块都使用了 mock 技术,而 mock 的行为与真实的线上服务可能会有所不同
所以,当所有的单元测试都被修复后,他们开始在线上正式使用 Python 3 来运行服务。
这个过程并不是一蹴而就的。首先,所有的 Instagram 工程师开始访问到这些使用 Python 3 来执行的新服务,然后是 Facebook 的所有雇员,随后是 0.1%、20% 的用户,最终 Python 3 覆盖到了所有的 Instagram 用户。
原文地址:https://blog.51cto.com/14248401/2365560