1

MySQLデータベースにクエリを実行し、さまざまなリクエストパラメータに基づいて各リクエストの結果を表示するシンプルなシングルページFlask(v0.8)アプリケーションがあります。アプリケーションは、Nginx上のTornadoを使用して提供されます。

最近、DBクエリがまだ実行されているときに、アプリケーションが異なるクライアントからの同時リクエストをブロックしているように見えることに気付きました。例-

  1. クライアントは、完了するまでに時間がかかる(> 20秒)複雑なDBクエリを使用して要求を行います。
  2. 別のクライアントがサーバーにリクエストを送信し、最初のクエリが返されるまでブロックされます。

したがって、基本的に、アプリケーションはすべての人にサービスを提供する単一のプロセスのように動作します。サーバー上の共有DB接続に問題があると思っていたので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;
    }

  }
}

これは非常に小さなクライアントベースにサービスを提供する小さなアプリケーションであり、そのほとんどは私が継承したレガシーコードであり、修正や書き換えを行うことはありませんでした。問題に気付いたのは、完了するのに時間がかかるより複雑なクエリタイプを追加した後です。何かが飛び出した場合は、フィードバックをいただければ幸いです。ありがとう。

4

3 に答える 3

2

接続プールは MySQLdb を非同期にしません。はresults = cursor.fetchall()、クエリが完了するまで Tornado をブロックします。

これは、Tornado で非同期ライブラリを使用する場合に発生することです。Tornado は IO ループです。それは1つのスレッドです。20 秒のクエリがある場合、MySQLdb が戻るのを待っている間、サーバーは応答しなくなります。残念ながら、私は良い async python MySQL ライブラリを知りません。Twisted のものもいくつかありますが、それらは追加の要件と複雑さを Tornado アプリにもたらします。

Tornado の連中は、スロー クエリを HTTP サービスに抽象化することを推奨していますtornado.httpclient。また、クエリのチューニング (>20 秒!) や、より多くの Tornado プロセスの実行を検討することもできます。または、非同期 Python ライブラリ (MongoDB、Postgres など) を使用してデータストアに切り替えることもできます。

于 2012-08-08T17:56:32.593 に答える
1

どのような「複雑なデータベースクエリ」を実行していますか?それらは単に読み取るだけですか、それともテーブルを更新していますか。特定の状況下では、MySQLはテーブルをロックする必要があります-読み取り専用のクエリのように見える場合でも。これは、ブロッキング動作を説明する可能性があります。

さらに、実行に20秒以上かかり、頻繁に実行されるクエリは、最適化の候補になると思います。

于 2012-08-08T13:09:29.630 に答える
1

したがって、ご存知のように、標準の mysql ドライバーはブロックしているため、サーバーはクエリの実行中にブロックされます。tornado で非ブロッキング mysql クエリを実現する方法については、こちらの記事を参照してください。

ところで、Mike Johnston が述べたように、クエリが 20 秒以上実行される場合、非常に長くなります。私の提案は、このクエリをバックグラウンドで移動する方法を見つけることです。Tornado のパッケージには非同期の mysql ドライバーが含まれていません。

また、20 の同期データベース接続のプールを使用する代わりに、それぞれ 1 つの接続で 20 のサーバー インスタンスを起動し、それらのリバース プロキシとして nginx を使用できます。彼らはプールよりも防弾です。

于 2012-08-08T17:43:56.633 に答える