详细摘要 摘要
生成:2025-06-21 18:38摘要详情
- 音频文件
- 2020-10-15 | DjangoCon 2020 | How To Break Django: With Async - Andrew Godwin
- 摘要类型
- 详细摘要
- LLM 提供商
- openai
- LLM 模型
- gemini-2.5-pro
- 温度
- 0.3
- 已创建
- 2025-06-21 18:38:27
摘要内容
概览/核心摘要 (Executive Summary)
Andrew Godwin 在 DjangoCon 2020 的演讲《How To Break Django: With Async》中,深入剖析了在 Django 中使用异步编程的潜在风险和复杂性。他首先肯定了 Django 3.1 已正式支持 async 视图,允许同步与异步代码混合使用,但在便利背后,异步编程的并发特性引入了诸多难以察觉的陷阱。演讲核心是通过一系列他亲身经历的错误案例,揭示异步代码可能导致的静默失败 (silent failures)、性能退化、竞争条件 (race conditions) 和死锁 (deadlocks)。
关键问题包括:在异步视图中调用同步 ORM 会阻塞事件循环,反而降低效率;忘记 await 关键字会导致协程不执行;并行执行有依赖关系的写操作会因执行顺序不确定而破坏数据一致性;跨线程的数据库事务会失效。Godwin 强调,Django 的设计哲学是提供“安全护栏 (guardrails)”,例如通过抛出 SynchronousOnlyOperation 异常,将隐蔽的性能问题转化为明确的程序错误。他强烈建议开发者启用 Python 的 asyncio 调试模式来检测长时间运行和未被等待的协程。最后,他提出的最佳实践是:默认使用同步编程,仅在性能分析确认瓶颈后,才将特定部分重构为异步代码,从而在享受异步带来的性能提升的同时,最大限度地规避其固有的复杂性和风险。
异步在 Django 中的现状与挑战
好消息:Django 已支持异步视图
- 功能实现:Django 3.1 及以上版本已原生支持异步视图,开发者可以直接在视图函数前使用
async def。 - 混合模式:框架能够无缝地处理同步与异步视图的混合。
- 在 WSGI 模式下,同步视图正常运行,异步视图会在一个迷你的事件循环中执行,虽非性能最优,但仍可实现并行化。
- 在 ASGI 模式下,同步和异步视图都能高效运行,其中同步视图会被分配到独立的线程中执行,避免阻塞事件循环。
- 核心价值:这种混合支持允许开发者逐步迁移,只在需要性能优化的特定视图上采用异步,而无需重写整个项目。
核心困境:异步编程的内在复杂性
- 同步编程的安全性:代码按顺序执行,逻辑清晰,易于理解和调试。
- 异步编程的难度:作为并发编程的一种形式,异步引入了与多线程、多进程、分布式系统类似的复杂问题,如状态管理、执行顺序不确定性等。
"in many ways, asynchronous programming is inviting those same problems into your code base, where normally things were safer."
- 学习方式:演讲者通过分享自己曾犯过的错误,以实例来展示异步编程的常见陷阱,并建议听众在非生产环境中尝试复现这些问题以加深理解。
破坏 Django 的异步编程实例解析
1. 阻塞事件循环:在异步视图中误用同步代码
- 错误示范:在一个
async def视图中,直接调用了同步的 Django ORM 方法,如Book.objects.get()。 - 问题根源:同步的数据库调用会阻塞整个事件循环 (event loop),因为在调用期间没有
await来释放控制权。这使得服务器在等待数据库响应时无法处理任何其他请求。 - 后果:这是一种“静默失败”,代码能运行、通过单元测试,但在生产环境中会导致严重的性能下降,甚至比纯同步代码效率更低。
- Django 的护栏:为了防止这种情况,Django 会主动检测并在异步上下文中调用同步代码时,抛出
SynchronousOnlyOperation异常。"This turns a silent failure or a silent performance slowdown into an explicit failure. And in my opinion, that's the way to go about things."
- 正确做法:应使用
await关键字和专门的异步方法(演讲中以假想的Book.objects.aget()为例)。
2. 竞争条件:并行执行带有副作用的操作
- 错误示范:使用
asyncio.gather同时执行两个有依赖关系的操作,例如“创建用户账户”和“发送欢迎邮件”。 - 问题根源:
asyncio.gather不保证任务的执行顺序。进程可能在创建用户后、发送邮件前崩溃,反之亦然。 - 后果:这会导致系统状态不一致(例如,邮件已发送但数据库中无此用户),增加了系统的失败模式。
- 推荐做法:对于有隐式依赖的写操作(副作用),应按顺序
await它们,以确保逻辑的原子性和可预测的失败模式。并行化更适用于无副作用的独立数据查询。
3. 失效的数据库事务
- 错误示范:在一个标准的
transaction.atomic()上下文管理器内部,调用了两个独立的、用sync_to_async包装的数据库操作函数。 - 问题根源:
sync_to_async会在不同的工作线程中执行同步代码,而 Django 的数据库连接和事务是线程绑定 (thread-bound) 的。这意味着事务在主异步线程中开启,但实际的数据库操作在没有事务保护的子线程中执行。 - 后果:这是另一个极其危险的“静默失败”,事务实际上并未生效,可能导致数据损坏,并且极难通过测试发现。
4. 共享状态下的竞争条件
- 错误示范:在并行获取多个 URL 的任务中,多个协程同时读写一个共享的计数器字典。具体操作为:
读取值 -> await 网络请求 -> 写入新值。 - 问题根源:在
await期间,协程会交出控制权。多个协程可能在任一协程完成写操作前都读取了旧的计数值,导致最终结果远小于预期。 - 异步的优势与解决方案:在
asyncio中,两个await之间的代码块是原子的。只需调整代码顺序,将读操作和写操作放在一起,中间不包含await,即可解决此问题。 - 与线程的对比:这在线程编程中是无效的,因为线程可能在任何指令之间被中断,必须使用锁(Lock)来保护。
5. 死锁与无限循环
- 错误示范:一个协程在
while循环中等待某个条件变量被另一个协程改变,但循环体内使用了同步的time.sleep()或根本没有await。 - 问题根源:循环没有通过
await交出控制权,导致事件循环被永久阻塞,其他协程无法执行,条件变量永远不会被改变,形成死锁。 - 正确做法:必须使用异步版本的
sleep,即await asyncio.sleep(),以确保在等待期间事件循环可以运行其他任务。
6. sync_to_async 的线程敏感性
- 历史问题:旧版 Django 中,连续调用
sync_to_async包装的函数可能会被分配到不同的线程中,导致依赖于同一数据库连接状态(如设置了连接级别的参数)的代码失效。 - 已修复:新版本的 Django 已经修复了这个问题。默认情况下 (
thread_sensitive=True),来自同一协程的sync_to_async调用会被安排在同一个线程中执行,保证了连接的一致性和安全性。如果开发者确认安全且追求极致性能,可以手动设置为thread_sensitive=False。
防御策略与最佳实践
1. 启用 Python 的 asyncio 调试模式
- 开启方式:通过设置环境变量或在代码中启用
python -m asyncio的调试模式。 - 核心功能:
- 检测长时间运行的协程:如果一个协程在两次
await之间运行时间过长(默认 > 100ms),系统会发出警告。这通常意味着其中包含了未被发现的同步阻塞调用。 - 检测未被等待的协程:如果一个协程被创建后从未被
await,它将不会执行。调试模式会捕获这种情况并报警,防止因忘记await导致的静默失败。
- 检测长时间运行的协程:如果一个协程在两次
2. 依赖 Django 的“安全护栏”
- 核心理念:Django 框架主动承担起保护开发者的责任,通过内置检查将潜在的、难以发现的错误转化为明确的异常。
- 典型例子:
SynchronousOnlyOperation异常。 - 可配置性:这些安全特性默认开启,但如果开发者明确知道自己在做什么,可以选择关闭它们。
3. 推荐的开发工作流
演讲者强烈建议遵循以下步骤来引入异步代码:
1. 首先编写同步代码:确保逻辑正确,并建立清晰的心智模型。
2. 编写完善的测试套件:保证代码的正确性和健壮性。
3. 进行性能分析:识别出真正的性能瓶颈。
4. 最后重构为异步:只针对已确认的瓶颈部分,将其重构为异步代码。
"You want to have the ability to like lift up, Oh, like this one view, we're going to make it async. And that's one of the reasons Jango supports a hybrid mode."
结论:Django 的哲学与未来展望
- Django 的承诺:Django 的核心价值在于提供一个安全、高效的开发框架。这一哲学同样延伸到其异步功能的设计中,即默认安全,允许选择性地为性能牺牲部分安全。
- 普遍性问题:异步编程的复杂性并非 Django 或 Python 独有,而是所有支持并发的语言和框架(如 Node.js, Rust)共同面临的挑战。
- 未来期望:演讲者希望未来编程语言和工具能发展到让异步编程变得更安全、更不容易出错的程度,但在此之前,开发者需要保持谨慎。
- 社区参与:鼓励对异步开发感兴趣的开发者参与 Django 论坛的讨论,共同推动其发展。