我们将发布有关Instagram如何与Python配合使用的系列文章中下一篇文章的翻译的第一部分。 本系列的
第一篇文章讨论了Instagram服务器代码的功能,它是一个经常更改的整体,以及静态类型检查工具如何帮助管理此整体。
第二种材料是关于输入HTTP API的。 在这里,我们将讨论解决Instagram使用Python在其项目中遇到的一些问题的方法。 该材料的作者希望,Instagram的经验将对可能遇到类似问题的人有所帮助。

情况概述
让我们看一下以下模块,乍一看,它看起来完全是无辜的:
import re from mywebframework import db, route VALID_NAME_RE = re.compile("^[a-zA-Z0-9]+$") @route('/') def home(): return "Hello World!" class Person(db.Model): name: str
如果有人导入此模块,将执行什么代码?
- 首先,将执行与将字符串编译成模板对象的正则表达式关联的代码。
- 然后将执行
@route
装饰器。 如果我们依靠所看到的内容,那么我们可以假设在这里,相应的表示形式已在URL映射系统中注册。 这意味着该模块的常规导入导致以下事实:应用程序的全局状态在其他地方发生了变化。 - 现在,我们将执行
Person
类的所有主体代码。 它可以包含任何东西。 基类Model
可能具有一个元类或__init_subclass__
方法,这些方法又可能包含一些其他代码,这些代码在导入我们的模块时执行。
问题#1:服务器启动和重启速度慢
导入该模块时(可能)不执行的唯一代码行将
return "Hello World!"
。 没错,我们不能肯定地说! 结果,事实证明,通过导入这个由八行组成的简单模块(甚至在我们的程序中甚至没有使用它),我们可能导致启动数百甚至数千行Python代码。 更不用说该模块的导入会导致对位于程序其他位置的全局URL映射的修改。
怎么办 摆在我们面前的是Python是一种动态解释语言这一事实的后果的一部分。 这使我们能够使用
元编程方法成功解决各种问题。 但是,此代码有什么问题呢?
实际上,这段代码是完美的。 只要有人在相对较小的代码库中使用它,小型程序员团队便可以在此基础上使用代码。 只要保证使用该代码的人在使用Python功能时保持一定的纪律,就不会造成麻烦。 但是,如果数百名程序员正在从事的项目中有成千上万的代码行,而其中许多人不具备丰富的Python知识,则这种动态性的某些方面可能会成为问题。
例如,Python的一大特色是分阶段开发所涉及的步骤的速度。 即,代码更改的结果可以在进行更改后立即在字面上看到,而无需编译代码。 但是,如果我们谈论的是一个数百万行的项目(以及该项目的一个相当混乱的依赖关系图),那么Python的这个优点就变成了减号。
启动我们的服务器需要20秒钟以上的时间。 有时,当我们没有适当注意优化时,这个时间会增加到大约一分钟。 这意味着开发人员需要20到60秒才能看到对代码所做更改的结果。 这适用于您在浏览器中看到的内容,甚至适用于运行单元测试的速度。 不幸的是,这段时间足以使一个人分心而忘了自己以前做过的事情。 从字面上看,大部分时间都花在了导入模块以及创建函数和类上。
从某种意义上讲,这与等待以某种其他语言编写的程序的编译结果相同。 但是通常可以逐步进行编译。 关键是您只能重新编译已更改的内容,而哪些内容直接取决于已更改的代码。 因此,通常在对项目进行少量更改后即可快速进行项目编译。 但是,在使用Python时,由于导入命令可能会产生任何副作用,因此没有可靠且安全的方法来逐步重新启动服务器。 同时,更改的规模并不重要,每次我们必须完全重新启动服务器,导入所有模块,重新创建所有类和函数,重新编译所有正则表达式等等时,都是如此。 通常,从最后一次服务器重新启动时起,99%的代码没有更改,但是我们仍然必须反复做同样的事情才能输入更改。
除了减慢开发人员的速度之外,这还导致大量系统资源的非生产性浪费。 事实是我们正在以不断部署变更的方式工作,这意味着不断重载生产服务器代码。
实际上,这是我们的第一个问题:服务器启动和重启速度慢。 由于系统必须在代码导入期间不断执行大量重复动作,因此会出现此问题。
问题2:不安全导入命令的副作用
事实证明,这是开发人员在导入模块时通常要解决的另一项任务。 这是从配置的网络存储中加载设置:
MY_CONFIG = get_config_from_network_service()
除了减慢服务器启动速度之外,它也不安全。 如果网络服务不可用,则不仅会导致我们将收到有关无法满足某些请求的错误消息。 这将导致服务器无法启动。
让我们加粗颜色,想象一下有人在模块中添加了负责初始化重要网络服务的代码,这些代码在导入期间执行。 开发人员根本不知道将代码添加到何处,因此他将其放置在启动服务器的早期阶段导入的模块中。 原来,该方案有效,因此该解决方案被认为是成功的,并且继续进行。
但是后来有人在其他地方添加了进口小组,乍看之下这无害。 结果,通过具有十二个模块深度的导入链,导致以下事实:从网络加载设置的模块现在被导入到初始化相应网络服务的模块中。
现在事实证明,我们正在尝试在服务初始化之前使用它。 系统自然崩溃。 在最好的情况下,如果我们谈论的是一个完全确定性的交互系统,那么这可能导致开发人员将花费一两个小时来弄清楚微小的更改是如何导致某件事情失败的,似乎没有联系。 但是在更复杂的情况下,这可能会导致项目“停产”。 但是,没有通用的方法可以使用
短绒棉纱来解决此类问题或加以预防。
问题的根源在于两个因素,其相互作用导致毁灭性后果:
- Python允许模块在导入期间发生任意和不安全的副作用。
- 代码的导入顺序未明确设置且不受控制。 从项目的规模来看,一种“综合导入”就是由所有模块中包含的导入命令组成的。 在这种情况下,模块的导入顺序可能会因所使用系统的输入点而异。
待续...亲爱的读者们! 您是否遇到过有关Python项目启动缓慢的问题?
