想用 StreamingHttpResponse 做 SSE 或长连接,要先弄清两件事:生成器该写成 sync 还是 async?一个长连接会占用什么?这篇在 uvicorn 与 gunicorn 上把四种组合、客户端断开、以及并发长连接都量出来。
ASGI uvicorn + async 生成器 首批 307 ms 最后一批 1511 ms 收到 5 批
ASGI uvicorn + sync 生成器 首批 1526 ms 最后一批 1526 ms 收到 5 批
服务端警告: StreamingHttpResponse must consume synchronous iterators in order to serve them asynchronously. Use an asynchronous iterator instead.
WSGI gunicorn + async 生成器 首批 1513 ms 最后一批 1513 ms 收到 5 批
WSGI gunicorn + sync 生成器 首批 303 ms 最后一批 1508 ms 收到 5 批
服务端警告: StreamingHttpResponse must consume asynchronous iterators in order to serve them synchronously. Use a synchronous iterator instead.缓冲的意思是:服务端把整个生成器读完才开始发,首批到达时间等于总时间。Django 在这两种不匹配的情况下都会打出警告,可以从日志里发现。
客户端在第 1 批之后断开;1.5 s 后 events.log 内容: 'async generator finally 执行了\n'
生成器里的 try/finally 在客户端断开后执行了,适合放释放资源的代码。这是在 uvicorn + async 生成器下测的,别的服务器没有测。
uvicorn 30 个并发 async 流总耗时 1520 ms 全部 200: True gunicorn 6 个并发 sync 流总耗时 9045 ms 全部 200: True
ASGI 下一个进程能同时挂很多长连接(这里 30 个);WSGI 同步 worker 一次只处理一个请求,一个长流会占满一个 worker。这就是长连接场景要用 ASGI(或者多线程 worker)的原因。