You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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进程无法复用主进程的连接,最终只有主进程的连接有效,导致日志中仅显示一个连接。

解决步骤

  1. 移除Gunicorn的--preload参数
    修改Gunicorn启动命令,去掉--preload,让每个Worker进程独立加载应用并建立Socket连接:

    gunicorn --workers 3 your_project.wsgi:application
    
  2. 延迟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',
      ]
      
  3. 优化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()
      
  4. 验证连接状态
    修改配置后重启Gunicorn,查看Socket服务器日志,应该会看到3个新的连接(对应3个Worker进程),此时每个Worker都能独立处理Socket消息,聊天服务即可正常运行。


内容的提问来源于stack exchange,提问作者Hyeonwoo Park

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:37:03