实测 gunicorn 的三种 worker 类型:总共 4 个槽位的配置下 /wait 等待型负载 sync 8、gthread 19、gevent 83 请求/秒,空闲内存、keep-alive 支持情况,以及一个发了一半请求头的慢客户端会让 sync worker 完全卡住而其它两种不受影响。
实测多进程下的全局变量:4 个 worker 各有一份模块级计数(读到的值取决于落到哪个进程)而 redis 计数始终是 40、--preload 让导入时算的随机值从 4 个变成 1 个、进程内缓存同一个 key 被 4 个进程各算一次。
实测后台线程与应用上下文:新线程里读 current_app 与查数据库抛 RuntimeError、在线程里推入应用上下文并传真实的 Flask 对象、线程里的异常只在日志里不经过 errorhandler、worker 优雅停止时 daemon 线程的任务被丢弃(0.3 秒退出)而非 daemon 线程要等到完成(2.4 秒)。
实测请求超时的各层:gunicorn --timeout 对 sync worker 在 2045 ms 杀掉 worker 返回 500 而对 gthread worker 不生效;nginx proxy_read_timeout 到点返回 504 但后端仍然把请求做完;应用层用协作式截止时间在 1074 ms 提前返回 504。
实测 Flask 长轮询:服务端用 threading.Condition 挂起请求,送达延迟中位数 1.0 ms(每秒短轮询是 554 ms);gthread 8 线程被 8 个等待的轮询占满后普通请求要等 2520 ms,gevent 挂着 300 个轮询仍然 5 ms;用 since 游标重连不丢消息。
实测 Server-Sent Events:text/event-stream 的线上格式、真实 Chrome 的 EventSource 断线后自动重连并带 Last-Event-ID=3 从 4 继续、经过 nginx 时没有 X-Accel-Buffering 头则 933 ms 才一次性到达而加了头逐条到达、每个连接占一个线程。
实测 WebSocket 经过 nginx:缺少 Upgrade 头时 websocket-only 客户端连接失败、默认客户端悄悄退化为 polling;proxy_read_timeout 3 秒时默认 ping_interval=25 的连接在约 3.0 秒被断开而 ping_interval=1 保持;websocket 往返 0.38 ms 对 polling 1.57 ms。
逐项实测 Flask 的 test_client:json 与 query_string 的写法与二选一的 ValueError、每个客户端独立的 cookie 罐与 use_cookies=False、follow_redirects 与 history、session_transaction 预置登录状态、environ_overrides 与 base_url、with client 保留请求上下文、buffered=False 逐块读流式响应。
实测 test_request_context():单独推入请求上下文来测试辅助函数、钩子不会自动执行而 teardown 在退出时执行、preprocess_request 与 full_dispatch_request 手动走钩子、url_for 在不同上下文里的行为与 SERVER_NAME、app_context 与请求上下文的区别。
同一个应用在 test_client 与真实 gunicorn 里的差异:响应头(date、server、connection)、路径解码一致、分块上传在 MAX_CONTENT_LENGTH 下被悄悄截断到 1000 字节而不是 413、TESTING 决定异常是传播还是 500、默认的 Host 与 User-Agent、重定向的 Location。
用真实的 pytest 实测 Flask 测试里的 fixture 与应用工厂:函数级 app 让每个测试一份全新的应用与内存数据库、--setup-show 的创建与清理顺序、session 级共用应用时 test_starts_empty 因前一个测试留下的数据失败而倒序却通过、200 个测试里函数级 0.79 秒对会话级 0.56 秒。
在真实的 PostgreSQL 上用 pytest 比较测试数据库隔离策略:不清理会互相污染;drop_all 加 create_all 0.77 秒、TRUNCATE 0.70 秒、外层事务加保存点回滚 0.59 秒(100 个测试);保存点方案里应用代码的 commit 不会留下数据。