当查询很复杂时,Flask / MySQL应用程序似乎阻止了并发请求

时间:2012-08-08 08:15:19

标签: mysql nginx flask blocking tornado

我有一个简单的单页Flask(v0.8)应用程序,它查询MySQL数据库并根据不同的请求参数显示每个请求的结果。该应用程序使用Tornado而非Nginx提供。

最近我注意到,当数据库查询仍在运行时,应用程序似乎阻止来自不同客户端的并发请求。例如 -

  1. 客户端使用复杂的数据库查询发出请求,需要一段时间才能完成(> 20秒)。
  2. 另一个客户端向服务器发出请求,并在第一个查询返回之前被阻止。
  3. 基本上,应用程序的行为就像是为每个人服务的单个进程。我当时认为问题出在服务器上的共享数据库连接上,所以我开始使用dbutils模块进行连接池。这没有用。我想我可能遗漏了架构或服务器配置方面的一些重要内容,所以我很感激任何反馈。

    这是执行db查询(简化)的Flask的代码:

    #... flask imports and such
    
    import MySQLdb
    from DBUtils.PooledDB import PooledDB
    
    POOL_SIZE = 5
    
    class DBConnection:
    
        def __init__(self):
            self.pool = PooledDB(MySQLdb, 
                                 POOL_SIZE, 
                                 user='admin', 
                                 passwd='sikrit', 
                                 host='localhost', 
                                 db='data',
                                 blocking=False,
                                 maxcached=10,
                                 maxconnections=10)
    
        def query(self, sql):
            "execute SQL and return results"
    
            # obtain a connection from the pool and
            # query the database
            conn   = self.pool.dedicated_connection()
            cursor = conn.cursor()
            cursor.execute(sql)
    
            # get results and terminate connection
            results = cursor.fetchall()
            cursor.close()
            conn.close()
            return results
    
    
    global db
    db = DBConnection()
    
    @app.route('/query/')
    def query():
        if request.method == 'GET':
            # perform some DB querying based query params
            sql     = process_request_params(request)
            results = db.query(sql)
            # parse, render, etc...
    

    这是龙卷风包装(run.py):

    #!/usr/bin/env python
    import tornado
    from tornado.wsgi import WSGIContainer
    from tornado.httpserver import HTTPServer
    from tornado.ioloop import IOLoop
    from myapplication import app
    from tornado.options import define, options
    
    define("port", default=8888, help="run on the given port", type=int)
    
    def main():
        tornado.options.parse_command_line()
        http_server = HTTPServer(WSGIContainer(app), xheaders=True)
        http_server.listen(options.port)
        IOLoop.instance().start()
    
    if __name__ == '__main__': main()
    

    通过启动脚本启动应用程序:

    #!/bin/sh
    APP_ROOT=/srv/www/site
    cd $APP_ROOT
    python run.py --port=8000 --log_file_prefix=$APP_ROOT/logs/app.8000.log 2>&1 /dev/null
    python run.py --port=8001 --log_file_prefix=$APP_ROOT/logs/app.8001.log 2>&1 /dev/null
    

    这是nginx配置:

    user nginx;
    worker_processes 1;
    
    error_log /var/log/nginx/error.log;
    pid /var/run/nginx.pid;
    
    events {
      worker_connections 1024;
      use epoll;
    }
    
    http {
      upstream frontends {
        server 127.0.0.1:8000;
        server 127.0.0.1:8001;
      }
    
      include /usr/local/nginx/conf/mime.types;
      default_type application/octet-stream;
    
      # ..
    
      keepalive_timeout 65;
      proxy_read_timeout 200;
      sendfile on;
    
      tcp_nopush on;
      tcp_nodelay on;
    
      gzip on;
      gzip_min_length 1000;
      gzip_proxied any;
      gzip_types text/plain text/html text/css text/xml application/x-javascript
                 application/xml application/atom+xml text/javascript;
    
      proxy_next_upstream error;
    
      server {
        listen 80;
        root /srv/www/site;
    
        location ^~ /static/ {
          if ($query_string) {
            expires max;
          }
        }
    
        location / {
          proxy_pass_header Server;
          proxy_set_header Host $http_host;
          proxy_redirect off;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Scheme $scheme;
          proxy_pass http://frontends;
        }
    
      }
    }
    

    这是一个小型应用程序,服务于一个非常小的客户端,其中大部分是我继承的遗留代码,从来没有解决过。在添加需要更长时间才能完成的更复杂查询类型之后,我才注意到了这个问题。如果有任何事情发生,我将非常感谢您的反馈。感谢。

3 个答案:

答案 0 :(得分:2)

连接池不会使MySQLdb异步。 results = cursor.fetchall()阻止Tornado,直到查询完成。

使用Tornado的非异步库时会发生这种情况。 Tornado是一个IO循环;这是一个主题。如果您有20秒的查询,服务器将在等待MySQLdb返回时无响应。不幸的是,我不知道一个好的异步python MySQL库。有some Twisted ones但它们会在Tornado应用程序中引入额外的要求和复杂性。

龙卷风的人建议将慢速查询抽象为HTTP服务,然后您可以使用tornado.httpclient进行访问。您还可以查看调整查询(> 20秒!),或运行更多Tornado进程。或者您可以使用异步python库(MongoDB,Postgres等)切换到数据存储区。

答案 1 :(得分:1)

您正在运行什么样的“复杂数据库查询”?它们只是读取还是您正在更新表格。在某些情况下,MySQL必须锁定表 - 即使是看似可能的只读查询。这可以解释阻塞行为。

此外,我会说任何需要20秒或更长时间才能运行且经常运行的查询才是优化的候选者。

答案 2 :(得分:1)

因此,正如我们所知 - 标准的mysql驱动程序正在阻塞,因此服务器将在查询执行时阻塞。这里是good article,关于如何在龙卷风中实现非阻塞的mysql queires。

顺便说一下,正如迈克约翰斯顿所提到的 - 如果你的查询执行> 20s - 它很长。我的建议是找到在后台移动此查询的方法。 Tornado在它的软件包中没有异步mysql驱动程序 - 因为FriendFeed的工作人员做得最好,让他们的查询执行得非常快。

而不是使用20个同步数据库连接池 - 您可以启动20个服务器实例,每个实例有1个连接,并使用nginx作为它们的反向代理。它们比游泳池更加防弹。