bigywater.tech
Django · 116

同步 Middleware 对异步请求的影响

ASGI 下只要有一个中间件不支持 async,Django 就得为它做适配。这篇量一下适配的代价、日志里怎么看出来,以及「sync 中间件会不会毁掉并发」这个常见担心有没有在实验里成立。

+0.15 ms
加 1 个只支持 sync 的中间件后,单个请求的耗时增量(uvicorn,DEBUG=True)
0.685 ms
5 个 sync 中间件的耗时,几乎与 1 个相同
420 vs 413 ms
20 并发下 sync / async 中间件里等 0.2 s 的总耗时

适配的代价

实测 · 本机执行输出
  没有中间件                       0.536 ms
  5 个 async 中间件               0.550 ms
  1 个只支持 sync 的中间件            0.681 ms
  5 个只支持 sync 的中间件            0.685 ms

额外耗时约 0.15 ms,而且第一个 sync 中间件之后几乎不再增加——推测是连续的 sync 中间件共用了同一次线程切换(我没有查源码验证)。这里的请求耗时是 DEBUG=True 时串行测的,数字只用来比较,不是绝对性能。

怎么在日志里发现

实测 · 本机执行输出
  Asynchronous handler adapted for middleware proj.mw.SyncMW.
  没有同步中间件时(async5)日志里 adapted 行数: 0

sync 中间件会毁掉并发吗?

实测 · 本机执行输出
  async 中间件里 await asyncio.sleep(0.2)      20 并发总耗时    413 ms  全部 200: True
  sync 中间件里 time.sleep(0.2)                20 并发总耗时    420 ms  全部 200: True

在这个测法下不会:20 个并发请求,sync 中间件里 time.sleep(0.2) 与 async 中间件里 await asyncio.sleep(0.2) 总耗时相同。原因是每个请求的同步部分在自己的线程里(第 112 篇)。但线程数不是无限的(第 113 篇量到过线程池上限),并发数远高于 20 时未必仍然成立,本文没有测。

分步演示

同步中间件与 ASGI
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。uvicorn 1 个进程;中间件是自己写的 4 种极简类(sync / sync sleep / async / async sleep);延迟是 400 次串行请求的中位数(先预热 30 次)
    • 没有验证:第三方中间件的真实开销、中间件里访问 ORM / session 的情形、并发数很高时的线程池耗尽、sync 与 async 中间件混排时的适配次数(本文只测了全 sync 与全 async)
    京ICP备2026067502号-1京公网安备11010502063487号