1

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

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

  1. 客户端使用复杂的数据库查询发出请求,该查询需要一段时间才能完成(> 20 秒)。
  2. 另一个客户端向服务器发出请求并被阻塞,直到第一个查询返回。

所以基本上,应用程序的行为就像一个为每个人服务的单一进程。我在想问题出在服务器上的共享数据库连接上,所以我开始使用该dbutils模块进行连接池。那没有帮助。我想我可能在架构或服务器配置中遗漏了一些重要的东西,所以我很感激对此的任何反馈。

这是执行数据库查询(简化)的 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;
    }

  }
}

这是一个小型应用程序,服务于非常小的客户群,其中大部分是我继承的遗留代码,从未修复或重写。我只是在添加更复杂的查询类型后才注意到这个问题,这些查询类型需要更长的时间才能完成。如果有任何问题,我会很感激您的反馈。谢谢。

4

3 回答 3

2

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

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

The Tornado guys recommend abstracting slow queries into an HTTP service, which you can then access using tornado.httpclient. You could also look at tuning your query (>20 seconds!), or running more Tornado processes. Or you could switch to a datastore with an async python library (MongoDB, Postgres, etc).

于 2012-08-08T17:56:32.593 回答
1

您正在运行哪种“复杂的数据库查询”?他们只是读取还是您正在更新表格。在某些情况下,MySQL 必须锁定表——即使在看起来可能是只读的查询上也是如此。这可以解释阻塞行为。

此外,我想说任何需要 20 秒或更长时间才能运行并且频繁运行的查询都是优化的候选对象。

于 2012-08-08T13:09:29.630 回答
1

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

顺便说一句,正如 Mike Johnston 所提到的 - 如果您的查询执行时间超过 20 秒 - 它会很长。我的建议是找到在后台移动此查询的方法。Tornado 的软件包中没有异步 mysql 驱动程序——因为 FriendFeed 的人尽了最大的努力,使他们的查询执行得非常快。

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

于 2012-08-08T17:43:56.633 回答