HTTPリクエストの同期/非同期の動きについて確認する。より、多くのリクエストをより早く処理するためには非同期の存在は不可欠だと考える。
なぜ、非同期が必要なのか?
リクエストの応答待ちをしている間に他の処理を動かして、効率よく処理をしたいから。
リクエストの応答がなかなか返ってこない間、他の処理は止まってしまう。その間にリクエストを受信してしまうと
サーバー側は処理しきれなくなってしまう。
そのため、リクエストの応答を待っている間にリクエストの受信して他のリクエストを処理できるようにするために
非同期での実行が必要になる。
aiohttpとは
非同期で動作するHTTPクライアント/サーバー。
asyncioを使って動作する。
HTTPリクエストのモジュールでは、requests
がメジャーだと思う。
自分も仕事ではrequests
ばかり使っている。
aiohttpのインストール
aiohttpモジュールをインストールするだけ。
pip install aiohttp
ハローワールド
GETリクエストのハローワールド。aiohttpのページに書かれているのawait
を使うように
少し変更。
import aiohttp import asyncio async def main(): async with aiohttp.ClientSession() as session: resp = await session.get("http://httpbin.org/get") print(resp.status) print(await resp.text()) asyncio.run(main())
同期処理と非同期でのリクエスト受信時の動作確認
ここでは、同期で別のマイクロサービスにリクエストを送信する場合と非同期でリクエストを送信した場合の リクエストの処理の違いを確認する。 Webフレームワークは、FastAPIを使用する。
同期処理で別のマイクロサービスにリクエストを送信
リクエストを送信した際に、受信側が別のマイクロサービスに同期でリクエストを送信するケースを実験する。
送信元は、task1とtask2をほぼ同時にリクエストを送信しているが、リクエストはtask1の応答が返ってきて
その後にtask2の応答結果が返ってきている。
これは、リクエストを同期で処理しているため、task1から順番に処理したことがわかる。
# 送信元 2024-04-20 07:59:04,569 - --- task1 ----- 2024-04-20 07:59:04,569 - --- task1 Send!! 2024-04-20 07:59:04,570 - --- task2 ----- 2024-04-20 07:59:04,570 - --- task2 Send!! 2024-04-20 07:59:14,606 - --- task1 status = 200 2024-04-20 07:59:14,606 - --- task1{"res":{"message":"success"}} 2024-04-20 07:59:24,612 - --- task2 status = 200 2024-04-20 07:59:24,612 - --- task2{"res":{"message":"success"}} # 受信側 2024-04-20 07:59:04,572 - Received task1 Request!! 2024-04-20 07:59:14,604 - Send task1 response INFO: 127.0.0.1:52673 - "GET /task1 HTTP/1.1" 200 OK 2024-04-20 07:59:14,606 - Received task2 Request!! 2024-04-20 07:59:24,611 - Send task2 response INFO: 127.0.0.1:52674 - "GET /task2 HTTP/1.1" 200 OK
非同期で別のマイクロサービスにリクエストを送信
リクエストを非同期で処理したケースでは、さきほどと同様に送信元はtask1とtask2のリクエストを
ほぼ同時に送信している。
リクエストを受信した側も同様にほぼ同時にリクエストを受信しており、応答結果もほぼ同時に返却している
ことがわかる。
# 送信元 2024-04-20 08:01:19,726 - --- task1 ----- 2024-04-20 08:01:19,726 - --- task1 Send!! 2024-04-20 08:01:19,726 - --- task2 ----- 2024-04-20 08:01:19,726 - --- task2 Send!! 2024-04-20 08:01:29,740 - --- task1 status = 200 2024-04-20 08:01:29,740 - --- task1{"res":{"message":"success"}} 2024-04-20 08:01:29,740 - --- task2 status = 200 2024-04-20 08:01:29,740 - --- task2{"res":{"message":"success"}} # 受信側 2024-04-20 08:01:19,728 - Received task1 Request!! 2024-04-20 08:01:19,732 - Received task2 Request!! 2024-04-20 08:01:29,738 - Send task1 response INFO: 127.0.0.1:53064 - "GET /task1 HTTP/1.1" 200 OK 2024-04-20 08:01:29,739 - Send task2 response INFO: 127.0.0.1:53065 - "GET /task2 HTTP/1.1" 200 OK
まとめ
aiohttpとrequestsモジュールを使って、同期と非同期時にHTTPリクエストの処理の仕方を
確認した。実際にプログラムを動かしてみることで、送信元と受信側の動きを理解することができた。
また、aiohttpの存在は知らなかったので、知ることが出来てよかった。
Postリクエストはデータの登録や更新、削除で使用されるため、同期処理した方が良さそうだが、
Getリクエストは非同期でより多くのリクエストを処理できるようにした方がよいと思う。
実際にプロジェクトの仕様にもよるが、aiohttpが使えることは頭に入れておこうと思う。
参考
- 送信元
import aiohttp import asyncio import logging from logging import getLogger, StreamHandler, Formatter logger = getLogger("LogTest") logger.setLevel(logging.DEBUG) stream_handler = StreamHandler() stream_handler.setLevel(logging.DEBUG) handler_format = Formatter("%(asctime)s - %(message)s") stream_handler.setFormatter(handler_format) logger.addHandler(stream_handler) async def send(name): logger.info("--- " + name + " -----") async with aiohttp.ClientSession() as session: logger.info("--- " + name + " Send!!") url = "http://127.0.0.1:8001/" + str(name) resp = await session.get(url) logger.info("--- " + name + " status = " + str(resp.status)) logger.info("--- " + name + str(await resp.text())) async def main(): task1 = asyncio.create_task(send("task1")) task2 = asyncio.create_task(send("task2")) await task1 await task2 asyncio.run(main())
- app
uvicorn received_app:app --port 8001
で起動
from fastapi import FastAPI import requests import aiohttp import logging from logging import getLogger, StreamHandler, Formatter logger = getLogger("LogTest") logger.setLevel(logging.DEBUG) stream_handler = StreamHandler() stream_handler.setLevel(logging.DEBUG) handler_format = Formatter("%(asctime)s - %(message)s") stream_handler.setFormatter(handler_format) logger.addHandler(stream_handler) app = FastAPI() @app.get("/task2") async def get_task2(): logger.info("Received task2 Request!!") # response = requests.get("http://127.0.0.1:8000") # logger.info("Send task2 response") # return {"res": response.json()} async with aiohttp.ClientSession() as session: response = await session.get("http://127.0.0.1:8000") json_res = await response.json() logger.info("Send task2 response") return {"res": json_res} @app.get("/task1") async def get_task1(): logger.info("Received task1 Request!!") # response = requests.get("http://127.0.0.1:8000") # logger.info("Send task1 response") # return {"res": response.json()} async with aiohttp.ClientSession() as session: response = await session.get("http://127.0.0.1:8000") json_res = await response.json() logger.info("Send task1 response") return {"res": json_res}
- app2
uvicorn late_app:app --port 8000
で起動
from fastapi import FastAPI import time import logging from logging import getLogger, StreamHandler, Formatter logger = getLogger("LogTest") logger.setLevel(logging.DEBUG) stream_handler = StreamHandler() stream_handler.setLevel(logging.DEBUG) handler_format = Formatter("%(asctime)s - %(message)s") stream_handler.setFormatter(handler_format) logger.addHandler(stream_handler) app = FastAPI() @app.get("/") def call_api(): logger.info("Received request in late app.") time.sleep(10) response = {"message": "success"} return response