この記事は全部俺 Advent Calendar 2018の4日目の記事です。
この記事 is 何?
Python + Flaskでバックグラウンド処理をしてレスポンスをすぐ返す方法について記載します。
※のっぴきならない理由がない限り、今から開発を始める場合はFlaskではなくresponderを使いましょう
この記事にresponderで同じことをする方法を記載しているのでよければご一読くださいm(_ _)m
tl;dr
- Flaskで非同期処理を実現する前に、Flask.run()は開発環境前提なので本番環境用にuWSGIを通す必要があります
- 処理を発火させた後、処理完了を待たずに終了することを
fire and forget
と呼びます - 新たにサーバを立てる場合で非同期処理する可能性がある場合はresponderを使いましょう(2度目)
筆者の環境
software | version |
---|---|
Ubuntu | 18.04.1 LTS |
Python | 3.6.5 |
Flask | 1.0.2 |
Flaskでawait/asyncを使えるようにするまで
まずはHello, World
まず、FlaskでHello, World
するコードを書いてみます。
import flask
app = flask.Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World\n'
if __name__ == '__main__':
app.run(host='0.0.0.0')
そしてpython3 app.py
でサーバ起動した後、curl http://0.0.0.0:5000/
すると、Hello, World
が表示されました。同様に、ブラウザからhttp://[サーバIP]:5000
にアクセスしてもHello, World
が表示されるはずです。
Flaskのprocessesを増やしてみる
app.run(host="0.0.0.0")
の部分を、app.run(host="0.0.0.0", processes=10)
にして再度python3 app.py
してみると、下記のエラーが出ます。
これは、Flask1.0.2のバグのようですが、そもそも3行目にWARNING: Do not use the development server in a production environment.
と出ているように、Flask.run()
は開発用なのでこれに対応して本番用の環境の作成から行っていきます。
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
Traceback (most recent call last):
File "app.py", line 14, in <module>
app.run(host="0.0.0.0", threaded=True, processes=10)
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 943, in run
run_simple(host, port, self, **options)
File "/home/ubuntu/.local/lib/python3.6/site-packages/werkzeug/serving.py", line 814, in run_simple
inner()
File "/home/ubuntu/.local/lib/python3.6/site-packages/werkzeug/serving.py", line 774, in inner
fd=fd)
File "/home/ubuntu/.local/lib/python3.6/site-packages/werkzeug/serving.py", line 656, in make_server
raise ValueError("cannot have a multithreaded and "
ValueError: cannot have a multithreaded and multi process server.
本番環境の作成
uWSGIの準備
sudo pip3 install uwsgi
でuwsgiをインストールした後、vim ~/uwsgi.ini
で設定ファイルを作成し、以下のように記述します。
[uwsgi]
socket = /tmp/uwsgi.sock
chmod-socket = 666
module = app
master = true
processes = 2
この設定では、HTTPではなくUnixドメインソケットを作るようにしているので、無駄なポートを消費しません。
processes = 2
が今回の肝で、これによってマルチプロセス処理を可能にしているので、後のasync/await処理にて非同期処理を実装することができます。
Nginxの準備
sudo apt install nginx
してnginxをインストールします。
sudo vim /etc/nginx/conf.d/fireforget.conf
で新しく設定ファイルを作成し、以下のように記述します。
server {
listen 80 default_server;
server_name _;
location / {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
}
ここでは、uWSGIの準備で設定したUnixドメインソケットをuwsgi_passで指定しているので、/
へのアクセスをuWSGIに流すことが可能になっています。
uWSGIとNginxの実行
sudo systemctl start nginx
sudo uwsgi --ini ~/uwsgi.ini
ここまで来たら、サーバ上でcurl http://127.0.0.1/
した場合にHello, World
が表示されるはずです。
並列処理化
vim ~/app.py
を実行し、以下のようにファイルを書き換えます。
import asyncio
import time
from datetime import datetime
import flask
app = flask.Flask(__name__)
@app.route('/')
def hello():
# asyncioを使用し、処理を発火させた後完了を待たずにreturnする
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.ensure_future(sleep(10)))
loop.close()
print(f'[hello ]{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}')
return "Hello, World\n"
# 長い時間がかかる処理
async def sleep(seconds):
with open
print(f'[sleep start]{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}')
time.sleep(seconds)
print('slept!!')
print(f'[sleep end]{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}')
if __name__ == '__main__':
app.run()
これを実行すると、uWSGIから以下のように結果が出力されるはずです。
[hello ]2018/12/04 14:45:43
ファイルの中身は以下のようになります。
[sleep start]2018/12/04 14:45:43
slept!!
[sleep end]2018/12/04 14:45:53
以上でFlaskを用いた非同期実行が確認できました!
補足
ちなみに、上記のuWSGIとNginxを通さず、無理やりpython3 app.py
を実行してアクセスすると、以下のようなエラーログが出力されます。
これは、Flaskが非同期処理に対応したサーバではないのに無理やりasyncio.get_event_loop()
を実行したために、新たなThreadを開始できずに発生するエラーです。
Traceback (most recent call last):
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/ubuntu/.local/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "app.py", line 11, in hello
loop = asyncio.get_event_loop()
File "/usr/lib/python3.6/asyncio/events.py", line 694, in get_event_loop
return get_event_loop_policy().get_event_loop()
File "/usr/lib/python3.6/asyncio/events.py", line 602, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
127.0.0.1 - - [04/Dec/2018 13:47:07] "GET / HTTP/1.1" 500 -
まとめ
どうでしたでしょうか?
Flask上で非同期処理をしたいだけなのに、かなり面倒な設定が多かったですね。。
繰り返しになりますが、今から開発を始める場合はresponderをおすすめします。
responderで非同期処理を行うノウハウについても近い内にまとめたいと思います。