Gunicorn多Worker环境下DRF集成python-socketio仅单连接问题排查
在DRF+Gunicorn多Worker环境下维护SocketIO客户端连接的问题
我在Django REST Framework(DRF)中使用python-socketio客户端,DRF通过Gunicorn在CentOS服务器上以3个Worker运行。启动服务后仅能看到一个Socket连接,导致聊天服务无法正常使用。请问如何在DRF中确保Socket客户端连接正常维护?
相关代码
settings.py
from socket_utils import SocketClientClass sio = SocketClientClass() sio.connect_to_server()
socket_utils.py
import os import time import socketio class SocketClientClass: def __init__(self): self.sio = socketio.Client() self.socket_url = os.environ.get("SOCKET_URL") def connect_to_server(self): socket_connect_cnt = 0 while not self.sio.connected: socket_connect_cnt += 1 try: self.sio.connect(self.socket_url) time.sleep(0.05) logger.info(f"[socket_client] connect success!") except Exception as ex: logger.error(f"[socket_client] connect exception : {str(ex)}") if socket_connect_cnt > 1: break def disconnect_to_server(self): if self.sio.connected: try: self.sio.disconnect() time.sleep(0.05) logger.info(f"[socket_client] disconnect success!") except Exception as ex: logger.error(f"[socket_client] disconnect exception : {str(ex)}") def message(self, event, data): try: if self.sio.connected: self.sio.emit(event, data) logger.info(f"[socket_client] send message success! : {data}") else: self.connect_to_server() logger.error(f"[socket_client] not connected to server!") except Exception as ex: if self.sio.connected: self.disconnect_to_server() self.connect_to_server() logger.error(f"[socket_client] send message exception : {str(ex)}")
服务器日志
1) Gunicorn服务日志
● gunicorn.service - gunicorn daemon Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: disabled) Active: active (running) since Tue 2024-06-18 09:37:14 KST; 6min ago Main PID: 1269661 (python3.11) Tasks: 7 (limit: 23684) Memory: 260.6M CGroup: /system.slice/gunicorn.service ├─1269661 /home/dev/anaconda3/envs/snappy/bin/python3.11 -m gunicorn --preload --workers> ├─1269666 /home/dev/anaconda3/envs/snappy/bin/python3.11 -m gunicorn --preload --workers> ├─1269667 /home/dev/anaconda3/envs/snappy/bin/python3.11 -m gunicorn --preload --workers> └─1269668 /home/dev/anaconda3/envs/snappy/bin/python3.11 -m gunicorn --preload --workers>
2) Socket服务器日志
... [2024-06-18 09:37:14,061][INFO][pid:1269257] [disconnect] sid: ddOuhjZZM1bOeSUBAAAT [2024-06-18 09:37:14,065][INFO][pid:1269257] [disconnect] sid: IyrGaoh0s7zxJ-ICAAAV [2024-06-18 09:37:14,927][INFO][pid:1269257] [connect] sid: Jojy3GmCCCWYAppkAAAX, remote: localhost:5002
3) Socket客户端日志
... [2024-06-18 09:37:15,019][INFO][pid:1269661] [socket_client] connect success!
问题原因与解决方法
核心原因
你使用了Gunicorn的--preload参数,该参数会在fork Worker进程前预加载整个应用。settings.py中的sio.connect_to_server()在预加载阶段仅由主进程执行并建立Socket连接,而fork出的Worker进程会继承这个连接,但Socket连接是进程绑定的,Worker进程无法复用主进程的连接,最终只有主进程的连接有效,导致日志中仅显示一个连接。
解决步骤
移除Gunicorn的
--preload参数
修改Gunicorn启动命令,去掉--preload,让每个Worker进程独立加载应用并建立Socket连接:gunicorn --workers 3 your_project.wsgi:application延迟Socket连接初始化
不在settings.py中直接初始化连接,而是通过Django的AppConfig在Worker进程启动后再建立连接:- 创建/修改应用的
apps.py:from django.apps import AppConfig from socket_utils import SocketClientClass import os class YourAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'your_app_name' def ready(self): # 仅在Worker进程中执行,避免主进程重复初始化 if os.environ.get('RUN_MAIN') == 'true': global sio sio = SocketClientClass() sio.connect_to_server() - 在
settings.py的INSTALLED_APPS中替换为自定义的AppConfig:INSTALLED_APPS = [ # ...其他应用 'your_app_name.apps.YourAppConfig', ]
- 创建/修改应用的
优化Socket连接维护逻辑
- 增加连接重试次数与间隔:
def connect_to_server(self): socket_connect_cnt = 0 max_retry = 3 while not self.sio.connected and socket_connect_cnt < max_retry: socket_connect_cnt += 1 try: self.sio.connect(self.socket_url) time.sleep(0.05) logger.info(f"[socket_client] connect success! (attempt {socket_connect_cnt})") except Exception as ex: logger.error(f"[socket_client] connect exception (attempt {socket_connect_cnt}) : {str(ex)}") time.sleep(1) - 添加心跳与断开重连监听:
def __init__(self): self.sio = socketio.Client() self.socket_url = os.environ.get("SOCKET_URL") @self.sio.on('connect') def on_connect(): logger.info("[socket_client] connected to server") @self.sio.on('disconnect') def on_disconnect(): logger.warning("[socket_client] disconnected from server, reconnecting...") self.connect_to_server()
- 增加连接重试次数与间隔:
验证连接状态
修改配置后重启Gunicorn,查看Socket服务器日志,应该会看到3个新的连接(对应3个Worker进程),此时每个Worker都能独立处理Socket消息,聊天服务即可正常运行。
内容的提问来源于stack exchange,提问作者Hyeonwoo Park
相关产品推荐
相关产品推荐

