FastAPI中WebSocket在Docker环境下无法实时返回数据问题
问题背景
开发基于Whisper AI的音频转录API,通过FastAPI WebSocket实现转录进度实时推送。本地Windows环境用uvicorn main:app --reload运行时,前端可正常实时展示进度;但部署到Docker容器后,WebSocket需等待转录进程完全结束才一次性返回所有进度数据。Docker日志能实时打印进度内容,说明print语句执行正常,但await websocket.send_text()未实时生效。已尝试websocket.send_bytes()、设置websocket.write_buffer_size=0等方法,问题仍存在。
相关代码
WebSocket实现代码
@app.websocket("/ws/transcribe") async def transcribe_websocker(websocket: WebSocket): await websocket.accept() process = subprocess.Popen(['python', 'ai.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=False) while True: line = process.stdout.readline() if not line: break # Encode and send the line to the WebSocket client encoded_line = line.encode('utf-8') print(f"logs: {encoded_line}") await websocket.send_text(encoded_line) # Close the process and the WebSocket connection process.stdout.close() process.wait() await websocket.close()
Dockerfile配置
FROM python:3.10-slim WORKDIR /python-docker RUN apt-get update && apt-get install git -y RUN pip3 install fastapi RUN pip3 install "uvicorn[standard]" RUN pip3 install -U git+https://github.com/jianfch/stable-ts.git RUN apt-get install -y ffmpeg RUN pip3 install moviepy RUN pip3 install streaming_form_data RUN pip3 install websockets COPY . . ENV PYTHONUNBUFFERED=1 #COPYING MODELS COPY base.pt /root/.cache/whisper/base.pt EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
解决方案
1. 修正WebSocket发送参数类型错误
websocket.send_text()要求传入字符串类型,但原代码中传入了encoded_line(bytes类型),这会导致消息发送异常,在部分环境下可能被缓冲直到连接关闭。修正为直接发送字符串:
# 替换原send_text行 await websocket.send_text(line.strip())
2. 强制子进程无缓冲输出
即使设置了PYTHONUNBUFFERED=1,子进程ai.py的输出仍可能存在缓冲。启动子进程时添加-u参数,强制Python使用无缓冲输出:
process = subprocess.Popen(['python', '-u', 'ai.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=False)
同时,在ai.py的打印语句后手动刷新输出,确保进度信息实时写入stdout:
import sys # 替换ai.py中的print语句 print(progress_content, flush=True)
3. 让出事件循环避免阻塞
Docker环境下事件循环可能被长时间占用,导致WebSocket消息无法及时推送。在发送消息后添加await asyncio.sleep(0),强制让出CPU资源,触发事件循环处理发送操作:
await websocket.send_text(line.strip()) await asyncio.sleep(0) # 新增此行,需提前import asyncio
4. 调整Uvicorn运行参数
Docker中默认的Uvicorn多worker模式可能导致消息缓冲,指定单worker运行:
修改Dockerfile中的CMD命令:
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
内容的提问来源于stack exchange,提问作者Moiz Hassan

