【问题标题】:Python-socketio not emitting events sometimes on FastAPI Heroku serverPython-socketio 有时不会在 FastAPI Heroku 服务器上发出事件
【发布时间】:2021-06-09 11:25:43
【问题描述】:

我有一个在 Heroku 上运行的 FastAPI 服务器,使用 python-socketio(5.3.0) 来处理套接字连接。我有一个端点,当需要将事件发送到某个套接字时调用该端点。我面临的问题是事件不会每次都发出。它只发出一些时间。它在本地运行良好。

带有 python-socketio 的 FastAPI 服务器:

import uvicorn
from fastapi import FastAPI
import socketio
from socketio.asyncio_namespace import AsyncNamespace
import json

notification_users = {}
notification_sockets = {}

class NotificationConnector(AsyncNamespace):

   async def on_connect(self, sid, *args, **kwargs):
     print("User connected: ", sid)

   async def on_disconnect(self, sid):
     user_id = chat_sockets.get(sid)
     print("User unsubscribed to chats: ", user_id)
     connection_list = chat_users.get(user_id)
     for socket in connection_list:
         if(socket==sid):
             connection_list.remove(sid)
    
     if connection_list == []:
         chat_users.pop(user_id)
     else:
         chat_users[user_id] = connection_list
    
     chat_sockets.pop(sid)

   async def on_subscribe(self, sid, data):
     user_id = str(data.get("userID"))
     print("User subscribed to notifications: ", user_id)
     if user_id in notification_users.keys():
         notification_users[user_id].append(sid)
     else:
         notification_users[user_id] = [sid]

     notification_sockets[sid] = user_id

fast_app = FastAPI()

sio = socketio.AsyncServer(
  async_mode='asgi',
  cors_allowed_origins='*',
  logger=True,
  engineio_logger=True
  )

sio.register_namespace(NotificationConnector("/notifications"))

app = socketio.ASGIApp(
  socketio_server=sio,
  other_asgi_app=fast_app,
  socketio_path='/socket.io/'
  )

@fast_app.post("/send")
async def send_notification(item: NotificationData):
  user_id = item.userId
  notification_content = json.dumps(item.notification)
  socket_list = notification_users.get(user_id,[])
  print(f"Socket list for {user_id} is {socket_list}")
  for socket in socket_list:
      print(f"Sending '{notification_content}' to socket Id: {socket}")
      await sio.emit('notifications', notification_content, room=socket, 
      namespace="/notifications")

return {"success":True},200

【问题讨论】:

  • 您的应用程序是否在 heroku 上运行多个工作人员?请记住,单独的工作人员 = 不共享内存的单独进程,因此如果用户连接到套接字,他最终将连接到单个工作人员,并且他将仅接收也在同一工作人员上触发的事件。
  • 是的,这正是正在发生的事情。将工人人数更改为 1 并解决了问题。我可能不应该为此使用全局变量。
  • 是的,您可能需要一些外部队列才能正确处理。

标签: python python-3.x socket.io fastapi python-socketio


【解决方案1】:

这个问题与 python-socketio 无关。这似乎是由 uvicorn 处理全局变量的方式引起的。默认情况下,uvicorn 创建多个 worker,每个 worker 都有自己的全局变量副本。

因此,当工作人员处理/send POST 请求时,notification_users 的副本可能没有事件需要发送到的套接字 id 的信息。

将工作人员的数量更改为 1 解决了该问题。可能会看看 Redis 而不是使用全局变量。

A better explanation of how uvicorn workers deal with global variables.

【讨论】: