Why is Cassandra hanging when I connect to it via

2019-09-17 15:48发布

问题:

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