【问题标题】:Flask/MySQL app seems to be blocking concurrent requests when query is complicated当查询复杂时,Flask/MySQL 应用程序似乎阻塞了并发请求
【发布时间】:2012-08-05 07:39:31
【问题描述】:

我有一个简单的单页 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;
    }

  }
}

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

【问题讨论】:

  • 你的数据库驱动支持异步模式吗?
  • MySQLdb 不是异步的,据我所知。但是,我使用连接池,所以理论上不同的连接不应该相互阻塞。没有?
  • 没有。它们不会相互阻塞,而是自身同步连接 - 阻塞龙卷风服务器。对于postgresql,我们有驱动程序叫momoko,不知道mysql。
  • 我可以建议 gevent 代替 Tornado 吗?它对所有网络调用进行猴子修补,因此您的所有阻塞调用(包括对 MySQL 的调用)都成为 gevent 事件循环的一部分,并由 greenlets 调度。它使用起来非常简单,不需要手动异步回调。
  • 感谢您的意见。现在我用 gunicorn 替换了 tornado,看起来效果不错,但我肯定会尝试 gevent。

标签: mysql nginx flask blocking tornado


【解决方案1】:

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

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

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

【讨论】:

  • @ColeMaclean 你的英语更好:)
【解决方案2】:

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

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

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

【讨论】:

  • 好电话。由于 Tornado 对于实际的应用程序并不是必需的,因此我将其替换为 gunicorn,这似乎解决了问题。谢谢!
【解决方案3】:

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

此外,我会说任何需要 20 秒或更长时间才能运行且频繁运行的查询都可以进行优化。

【讨论】:

  • 这是关于为什么tornado 阻塞的问题。不是mysql 锁定的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-23
相关资源
最近更新 更多