I have a Flask app that connects to Cassandra. When I run this app under Gunicorn and invoke Gunicorn as a flask-script command python manage.py gunicorn
, it hangs. But when I run this same app on the command line as gunicorn manage:app
, it succeeds. Why?
可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
回答1:
Explanation
Gunicorn forks off workers to handle incoming requests. If the Cassandra session (connection pool) is created before the worker fork (e.g., during app creation using an application factory pattern), the workers will have problems using Cassandra. DataStax recommends that each worker get its own session, and so you need to defer session creation until after the fork. This is a problem when you bundle Gunicorn and Flask together as a custom application, but presumably on the command line Gunicorn can obviously initialize and fork fully before creating the Flask app.
Example
To see the two behaviors, manually change bad=False to bad=True.
from cassandra.cluster import Cluster
from flask import Flask
from flask_script import Command, Manager
from gunicorn.app.base import BaseApplication
from gunicorn.six import iteritems
class CassandraClient:
def __init__(self, bad=False):
self.session = None
self.cluster = None
if bad:
self.connect()
def connect(self):
self.cluster = Cluster(['127.0.0.1'])
self.session = self.cluster.connect('keyspace')
def execute(self):
if not self.session:
self.connect()
query = '''
select now()
from system.local
'''
return self.session.execute(query)[0]
class GunicornApp(BaseApplication):
'''
Bundle Gunicorn and Flask together, so that we can call it as a
flask-script command.
http://docs.gunicorn.org/en/stable/custom.html
'''
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super(GunicornApp, self).__init__()
def load_config(self):
config = dict(
[(key, value) for key, value in iteritems(self.options)
if key in self.cfg.settings and value is not None])
for key, value in iteritems(config):
self.cfg.set(key.lower(), value)
def load(self):
return self.application
class GunicornCommand(Command):
'''
Modeled off of flask_script.Server
'''
def __init__(self, app, options):
self.app = app
self.options = options
def __call__(self, *args, **kwargs):
GunicornApp(self.app, self.options).run()
app = Flask(__name__)
app.cassandra = CassandraClient()
@app.route('/')
def hello():
return str(app.cassandra.execute())
if __name__ == '__main__':
manager = Manager(app)
gunicorn_options = {
'bind': '127.0.0.1',
'port': 8000,
'workers': 4
}
manager.add_command("gunicorn", GunicornCommand(app, gunicorn_options))
manager.run()
Versions
Flask==0.12.1
Flask-Script==2.0.5
gunicorn==19.7.1
cassandra-driver==3.8.1
References
- http://docs.gunicorn.org/en/stable/custom.html
- https://datastax.github.io/python-driver/faq.html
- How to use Flask-Script and Gunicorn
- https://groups.google.com/a/lists.datastax.com/forum/#!topic/python-driver-user/XuSjjWVnE9Y