部署 Flask-SocketIO 服务器有很多选择,从简单到极其复杂。在本节中,将描述最常用的选项。
一、嵌入式服务器
最简单的部署方式是通过socketio.run(app)
来实现。在运行socketio.run(app)
的时候,会自动选择Web服务器,选择顺序是 eventlet
,gevent然后才是Flask自带的开发服务器。
如果 eventlet 或 gevent 可用,则socketio.run(app)
使用这些框架之一启动生产服务器。如果这两个都没有安装,则使用 Flask 开发 Web 服务器,在这种情况下,该服务器不打算用于生产部署。
不幸的是,当将 gevent 与 uWSGI 一起使用时,此选项不可用。有关此选项的信息,请参阅下面的 uWSGI 部分。
二、Gunicorn Web服务器
另一种方法socketio.run(app)
是使用gunicorn作为 Web 服务器,使用 eventlet 或 gevent 工作人员。对于这个选项,除了 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 网络服务器
当将 uWSGI 服务器与 gevent 结合使用时,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 worker 有两个要求:
-
负载均衡器必须配置为将来自给定客户端的所有 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 工作线程。如果 SocketIO 服务器或服务器配置为侦听消息队列,如上一节所示,则任何其他进程都可以创建自己的 SocketIO实例并使用它以与服务器相同的方式发出事件。
例如,对于在 eventlet Web 服务器上运行并使用 Redis 消息队列的应用程序,以下 Python 脚本向所有客户端广播事件:
socketio = SocketIO(message_queue='redis://')
socketio.emit('my event', {'data': 'foo'}, namespace='/test')
SocketIO以这种方式使用实例时,Flask 应用程序实例不会传递给构造函数。
该channel
参数SocketIO可以用来选择通过消息队列通信的特定信道。当多个独立的 SocketIO 服务共享同一个队列时,需要使用自定义通道名称。
当使用 eventlet 或 gevent 时,Flask-SocketIO 不会应用猴子补丁。但是在使用消息队列时,如果 Python 标准库没有打猴子补丁,那么与消息队列服务通信的 Python 包很可能会挂起。
需要注意的是,想要连接到 SocketIO 服务器的外部进程不需要像主服务器那样使用 eventlet 或 gevent。让服务器使用协程框架,而外部进程则不是问题。例如,Celery worker 不需要配置为使用 eventlet 或 gevent 仅仅因为主服务器这样做。但是,如果您的外部进程出于某种原因确实使用了协程框架,那么可能需要猴子补丁,以便消息队列访问协程友好的函数和类。
七、跨域控制
出于安全原因,此服务器默认强制执行同源策略。实际上,这意味着以下内容:
- 如果传入的 HTTP 或 WebSocket 请求包含Origin标头,则该标头必须与连接 URL 的方案和主机匹配。如果不匹配,将返回 * 400 状态代码响应并拒绝连接。
- 对不包含
Origin
标头的传入请求没有任何限制 。
如有必要,该cors_allowed_origins
选项可用于允许其他来源。此参数可以设置为字符串以设置单个允许的来源,或设置为列表以允许多个来源。'*'
可以使用特殊值来指示服务器允许所有来源,但这应该小心完成,因为这可能会使服务器容易受到跨站点请求伪造 (CSRF) 攻击。