flask-socketio:部署

葛磊
2023-12-01

flask-socketio

部署

部署 Flask-SocketIO 服务器有很多选择,从简单到极其复杂。在本节中,描述了最常用的选项。

嵌入式服务器

socketio.run(app)最简单的部署策略是通过调用如上例所示来启动 Web 服务器 。这将查看为最佳可用 Web 服务器安装的软件包,在其上启动应用程序。当前评估的 Web 服务器选择是 eventlet,gevent和 Flask 开发服务器。

如果 eventlet 或 gevent 可用,则socketio.run(app)使用这些框架之一启动生产就绪服务器。如果这两个都没有安装,则使用 Flask 开发 Web 服务器,在这种情况下,该服务器不打算用于生产部署。

不幸的是,当将 gevent 与 uWSGI 一起使用时,此选项不可用。有关此选项的信息,请参阅下面的 uWSGI 部分。

Gunicorn 网络服务器

另一种方法socketio.run(app)是使用 gunicorn作为 Web 服务器,使用 eventlet 或 gevent worker。对于这个选项,除了 gunicorn 之外,还需要安装 eventlet 或 gevent。通过 gunicorn 启动 eventlet 服务器的命令行是:

gunicorn --worker-class eventlet -w 1 module:app

如果你更喜欢使用 gevent,启动服务器的命令是:

gunicorn -k gevent -w 1 module:app

当 gunicorn 与 gevent worker 和 gevent-websocket 提供的 WebSocket 支持一起使用时,必须更改启动服务器的命令以选择支持 WebSocket 协议的自定义 gevent Web 服务器。修改后的命令是:

gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module:app

Gunicorn 的第三种选择是使用线程工作者,以及用于 WebSocket 支持的 simple-websocket 包。对于 CPU 繁重或与 eventlet 和 gevent 使用绿色线程不兼容的应用程序,这是一个特别好的解决方案。启动线程 Web 服务器的命令是:

gunicorn -w 1 --threads 100 module:app

在所有这些命令中,module是定义应用程序实例的 Python 模块或包,并且app是应用程序实例本身。

由于 gunicorn 使用的负载均衡算法有限,因此在使用此 Web 服务器时不能使用多个工作进程。因此,上述所有示例都包含该选项。-w 1

将多个工作进程与 gunicorn 一起使用的解决方法是启动多个单工作进程实例并将它们放在功能更强大的负载均衡器(例如nginx )后面。

uWSGI 网络服务器

当结合 gevent 使用 uWSGI 服务器时,Socket.IO 服务器可以利用 uWSGI 的原生 WebSocket 支持。

uWSGI 服务器的配置和使用的完整说明超出了本文档的范围。uWSGI 服务器是一个相当复杂的包,它提供了大量且全面的选项集。它必须使用 WebSocket 和 SSL 支持进行编译,才能使 WebSocket 传输可用。作为介绍,以下命令在端口 5000 上为示例应用程序 app.py 启动 uWSGI 服务器:

$ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app

使用 nginx 作为 WebSocket 反向代理

可以将 nginx 用作将请求传递给应用程序的前端反向代理。但是,只有 nginx 1.4 和更新的版本支持 WebSocket 协议的代理。以下是代理 HTTP 和 WebSocket 请求的基本 nginx 配置:

server {
    listen 80;
    server_name _;

    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:5000;
    }

    location /static {
        alias <path-to-your-application>/static;
        expires 30d;
    }

    location /socket.io {
        include proxy_params;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass http://127.0.0.1:5000/socket.io;
    }
}

下一个示例添加了对多个 Socket.IO 服务器负载平衡的支持:

upstream socketio_nodes {
    ip_hash;

    server 127.0.0.1:5000;
    server 127.0.0.1:5001;
    server 127.0.0.1:5002;
    # to scale the app, just add more nodes here!
}

server {
    listen 80;
    server_name _;

    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:5000;
    }

    location /static {
        alias <path-to-your-application>/static;
        expires 30d;
    }

    location /socket.io {
        include proxy_params;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass http://socketio_nodes/socket.io;
    }
}

虽然上述示例可以作为初始配置工作,但请注意,nginx 的生产安装需要更完整的配置,涵盖其他部署方面,例如 SSL 支持。

使用多个工人

从 2.0 版开始,Flask-SocketIO 支持负载均衡器后面的多个工作人员。部署多个 worker 使使用 Flask-SocketIO 的应用程序能够在多个进程和主机之间传播客户端连接,并以这种方式扩展以支持大量并发客户端。

使用多个 Flask-SocketIO 工作者有两个要求:

  • 负载均衡器必须配置为将来自给定客户端的所有 HTTP 请求始终转发到同一个工作线程。这有时被称为“粘性会话”。对于 nginx,使用ip_hash指令来实现这一点。Gunicorn 不能与多个 worker 一起使用,因为它的负载均衡器算法不支持粘性会话。
  • 由于每个服务器只拥有客户端连接的一个子集,因此服务器使用 Redis 或 RabbitMQ 等消息队列来协调广播和房间等复杂操作。

使用消息队列时,需要安装其他依赖项:

  • 对于 Redis,redis必须安装包 ( )。pip install redis
  • 对于 RabbitMQ,kombu必须安装包 ( )。pip install kombu
  • 对于 Kafka,kafka-python必须安装包 ( )。pip install kafka-python
  • 对于 Kombu 支持的其他消息队列,请参阅Kombu 文档 以了解需要哪些依赖项。
  • 如果使用 eventlet 或 gevent,则通常需要对 Python 标准库进行猴子修补,以强制消息队列包使用协程友好的函数和类。

对于 eventlet,猴子修补是通过以下方式完成的:

import eventlet
eventlet.monkey_patch()

对于 gevent,您可以使用以下方法对标准库进行修补:

from gevent import monkey
monkey.patch_all()

在这两种情况下,建议您在主脚本的顶部应用猴子补丁,甚至在您的导入之上。

要启动多个 Flask-SocketIO 服务器,首先必须确保消息队列服务正在运行。要启动 Socket.IO 服务器并让它连接到消息队列,请将message_queue参数添加到SocketIO 构造函数:

socketio = SocketIO(app, message_queue='redis://')

参数的值message_queue是所使用的队列服务的连接 URL。对于与服务器在同一主机上运行的 redis 队列,'redis://'可以使用 URL。同样,对于默认的 RabbitMQ 队列,'amqp://'可以使用 URL。对于 Kafka,使用kafka://URL。Kombu 包有一个文档部分 ,描述了所有受支持队列的 URL 格式。

从外部进程发出

对于许多类型的应用程序,有必要从不是 SocketIO 服务器的进程发出事件,例如 Celery worker。如果 SocketIO 服务器或服务器被配置为侦听消息队列,如上一节所示,那么任何其他进程都可以创建自己的 SocketIO实例并使用它以与服务器相同的方式发出事件。

例如,对于在 eventlet Web 服务器上运行并使用 Redis 消息队列的应用程序,以下 Python 脚本将事件广播到所有客户端:

socketio = SocketIO(message_queue='redis://')
socketio.emit('my event', {'data': 'foo'}, namespace='/test')

以这种方式使用SocketIO实例时,Flask 应用程序实例不会传递给构造函数。

channel参数 toSocketIO可用于选择通过消息队列的特定通信通道。当有多个独立的 SocketIO 服务共享同一个队列时,需要使用自定义通道名称。

Flask-SocketIO 在使用 eventlet 或 gevent 时不应用猴子补丁。但是在使用消息队列时,如果 Python 标准库没有进行猴子补丁,与消息队列服务对话的 Python 包很可能会挂起。

需要注意的是,想要连接到 SocketIO 服务器的外部进程并不需要像主服务器那样使用 eventlet 或 gevent。让服务器使用协程框架,而外部进程不是问题。例如,Celery worker 不需要配置为使用 eventlet 或 gevent,因为主服务器需要。但是,如果您的外部进程出于某种原因确实使用了协程框架,那么可能需要进行猴子补丁,以便消息队列访问协程友好的函数和类。

跨域控制

出于安全原因,此服务器默认执行同源策略。实际上,这意味着以下内容:

  • 如果传入的 HTTP 或 WebSocket 请求包含Origin标头,则此标头必须与连接 URL 的方案和主机匹配。如果不匹配,将返回 400 状态代码响应并拒绝连接。
  • 对不包含标头的传入请求没有任何限制 Origin

如有必要,该cors_allowed_origins选项可用于允许其他来源。此参数可以设置为字符串以设置单个允许的来源,或设置为列表以允许多个来源。可以使用一个特殊的值'*'来指示服务器允许所有来源,但这应该小心,因为这可能使服务器容易受到跨站点请求伪造 (CSRF) 攻击。

 类似资料: