我正在尝试按照这个简单的方法在我的Flask应用程序中实现Server-Sent Events:http://flask.pocoo.org/snippets/116/
为了服务应用程序,我使用gunicorn与gevent worker。
我的代码的最小版本如下所示:
import multiprocessing
from gevent.queue import Queue
from gunicorn.app.base import BaseApplication
from flask import Flask, Response
app = Flask('minimal')
# NOTE: This is the global list of subscribers
subscriptions = []
class ServerSentEvent(object):
def __init__(self, data):
self.data = data
self.event = None
self.id = None
self.desc_map = {
self.data: "data",
self.event: "event",
self.id: "id"
}
def encode(self):
if not self.data:
return ""
lines = ["%s: %s" % (v, k)
for k, v in self.desc_map.iteritems() if k]
return "%s\n\n" % "\n".join(lines)
@app.route('/api/events')
def subscribe_events():
def gen():
q = Queue()
print "New subscription!"
subscriptions.append(q)
print len(subscriptions)
print id(subscriptions)
try:
while True:
print "Waiting for data"
result = q.get()
print "Got data: " + result
ev = ServerSentEvent(unicode(result))
yield ev.encode()
except GeneratorExit:
print "Removing subscription"
subscriptions.remove(q)
return Response(gen(), mimetype="text/event-stream")
@app.route('/api/test')
def push_event():
print len(subscriptions)
print id(subscriptions)
for sub in subscriptions:
sub.put("test")
return "OK"
class GunicornApplication(BaseApplication):
def __init__(self, wsgi_app, port=5000):
self.options = {
'bind': "0.0.0.0:{port}".format(port=port),
'workers': multiprocessing.cpu_count() + 1,
'worker_class': 'gevent',
'preload_app': True,
}
self.application = wsgi_app
super(GunicornApplication, self).__init__()
def load_config(self):
config = dict([(key, value) for key, value in self.options.iteritems()
if key in self.cfg.settings and value is not None])
for key, value in config.iteritems():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
if __name__ == '__main__':
gapp = GunicornApplication(app)
gapp.run()
问题是订阅者的列表似乎对每个工作人员都不同。这意味着如果worker#1处理/api/events
端点并将新订户添加到列表中,则客户端将仅接收在worker#1也处理/api/test
端点时添加的事件。
奇怪的是,每个工作人员的实际列表对象似乎相同,因为id(subscriptions)
在每个工作人员中返回相同的值。
有解决方法吗?我知道我可以使用Redis,但应用程序应该尽可能自包含,所以我试图避免任何外部服务。
更新: 问题的原因似乎是我嵌入 gunicorn.app.base.BaseApplication
(new feature in v0.19)。当使用gunicorn -k gevent minimal:app
从命令行运行应用程序时,一切都按预期工作
更新2:以前的怀疑结果是错误的,唯一的原因是因为gunicorn的默认工作进程数是1
,在调整数量以适应代码时通过-w
参数,它表现出相同的行为。
答案 0 :(得分:1)
你说:
实际列表对象似乎对每个工作者都是相同的,因为 id(subscriptions)在每个worker中返回相同的值。
但我认为事实并非如此,每个工人的subscriptions
不是同一个对象。每个工人都是一个独立的过程,有自己的记忆空间。
对于自包含系统,您可以开发一个像Redis
的简单版本一样的小型系统。例如,使用SQLite或ZeroMQ在这些工作者之间进行通信。