找回密码
 立即注册
首页 业界区 业界 [python]requests VS httpx VS aiohttp

[python]requests VS httpx VS aiohttp

瘴锲如 2025-6-11 21:05:30
前言

前段时间想着把一个python服务的接口逐渐改成异步的,其中用到requests的地方就要改成httpx或者aiohttp,有点好奇异步请求相较于同步请求有哪些提升,遂做了点小实验。
首先有个服务A提供接口,这个接口会停顿1秒,模拟数据库操作。服务B去请求服务A的这个接口,并把响应返回给客户端C。服务B提供4个接口,这4个接口分别用requests、httpx同步、httpx异步和aiohttp去请求服务A。
客户端使用wrk做请求测试。
实现服务A

服务A使用Go编写,用标准库即可完成
  1. package main
  2. import (
  3.         "net/http"
  4.         "time"
  5. )
  6. func main() {
  7.         mux := http.NewServeMux()
  8.         mux.HandleFunc("GET /a", func(w http.ResponseWriter, r *http.Request) {
  9.                 time.Sleep(1 * time.Second)
  10.                 w.Write([]byte("ok"))
  11.         })
  12.         if err := http.ListenAndServe("127.0.0.1:8000", mux); err != nil {
  13.                 panic(err)
  14.         }
  15. }
复制代码
先用wrk直接请求试试,以此作为基准
  1. $ wrk -t8 -c1000 -d30s http://127.0.0.1:8000/a
  2. Running 30s test @ http://127.0.0.1:8000/a
  3.   8 threads and 1000 connections
  4.   Thread Stats   Avg      Stdev     Max   +/- Stdev
  5.     Latency     1.00s     7.62ms   1.17s    97.34%
  6.     Req/Sec   194.17    266.07     1.24k    86.82%
  7.   29491 requests in 30.10s, 3.32MB read
  8. Requests/sec:    979.85
  9. Transfer/sec:    112.91KB
复制代码
服务B: FastAPI

先用FastAPI做服务B试试
  1. from fastapi import FastAPI
  2. import httpx
  3. import aiohttp
  4. import uvicorn
  5. import requests
  6. app = FastAPI()
  7. url = "http://127.0.0.1:8000/a"
  8. @app.get("/sync1")
  9. def sync1():
  10.     try:
  11.         resp = requests.get(url, timeout=2)
  12.         resp.raise_for_status()
  13.     except Exception as e:
  14.         print(f"sync request failed, {e}")
  15.     else:
  16.         return resp.text
  17.    
  18. @app.get("/sync2")
  19. def sync2():
  20.     try:
  21.         with httpx.Client() as client:
  22.             resp = client.get(url, timeout=2)
  23.             resp.raise_for_status()
  24.     except Exception as e:
  25.         print(f"sync2 request failed, {e}")
  26.     else:
  27.         return resp.text
  28.    
  29. @app.get("/async1")
  30. async def async1():
  31.     try:
  32.         async with httpx.AsyncClient(timeout=2) as client:
  33.             resp = await client.get(url)
  34.             resp.raise_for_status()
  35.     except Exception as e:
  36.         print(f"async1 request failed, {e}")
  37.     else:
  38.         return resp.text
  39.    
  40. @app.get("/async2")
  41. async def async2():
  42.     try:
  43.         async with aiohttp.ClientSession() as session:
  44.             async with session.get(url, timeout=2) as resp:
  45.                 resp.raise_for_status()
  46.                 content = await resp.text()
  47.     except Exception as e:
  48.         print(f"async2 request failed, {e}")
  49.     else:
  50.         return content
  51.    
  52. if __name__ == "__main__":
  53.     uvicorn.run("server1:app", host="127.0.0.1", port=8001, workers=4, access_log=False)
复制代码
wrk请求结果。httpx不仅同步请求性能不如requests,没想到连异步请求性能也不如requests。而aiohttp以五倍多第二名的性能冠绝群雄。
APITotal requestQPStimeoutcomment/sync14640154.154480requests 同步请求/sync23631120.873570httpx 同步请求/async14313143.404254httpx 异步请求/async225379843.350aiohttp 异步请求异步比同步性能还差,着实有点费解,遂找大模型问了下,大模型回复说httpx默认配置参数不高,可以额外指定参数,还需要避免反复创建http client。似乎有点道理,但是同步性能不如开箱即用的requests,异步性能不如开箱即用的aiohttp,我为什么还要折腾httpx呢?
服务B: Flask

Flask 2.0 也支持异步接口,但是之前测试性能并不是很好,拉出来一并测试瞧瞧实力。
Flask 版本:3.1.1。因为gunicorn运行异步接口会报错,所以用的flask内置webserver。
  1. from flask import Flask
  2. import requests
  3. import httpx
  4. import logging
  5. import aiohttp
  6. app = Flask(__name__)
  7. url = "http://127.0.0.1:8000/a"
  8. @app.get("/sync1")
  9. def sync1():
  10.     try:
  11.         resp = requests.get(url, timeout=2)
  12.         resp.raise_for_status()
  13.     except Exception as e:
  14.         print("request failed")
  15.         return "request failed"
  16.     else:
  17.         return resp.text
  18.    
  19. @app.get("/sync2")
  20. def sync2():
  21.     try:
  22.         with httpx.Client() as client:
  23.             resp = client.get(url, timeout=2)
  24.             resp.raise_for_status()
  25.     except Exception as e:
  26.         print("request failed")
  27.         return "request failed"
  28.     else:
  29.         return resp.text
  30. @app.get("/async1")
  31. async def async1():
  32.     try:
  33.         async with httpx.AsyncClient(timeout=2) as client:
  34.             resp = await client.get(url, timeout=2)
  35.             resp.raise_for_status()
  36.     except Exception as e:
  37.         print("request failed")
  38.         return "request failed"
  39.     else:
  40.         return resp.text
  41.    
  42. @app.get("/async2")
  43. async def async2():
  44.     try:
  45.         async with aiohttp.ClientSession() as session:
  46.             async with session.get(url, timeout=2) as response:
  47.                 response.raise_for_status()
  48.                 resp = await response.text()
  49.     except Exception as e:
  50.         print("request failed")
  51.         return "request failed"
  52.     else:
  53.         return resp
  54.    
  55. if __name__ == "__main__":
  56.     werkzeug_logger = logging.getLogger("werkzeug")
  57.     werkzeug_logger.disabled = True
  58.     app.run(host="127.0.0.1", port=8001)
复制代码
测试结果。看来flask还是跟requests更搭,异步还不如同步。
APITotal requestQPStimeoutcomment/sync113279441.27248requests 同步请求/sync2232477.262323httpx 同步请求/async1233077.462330httpx 异步请求/async28277275.036887aiohttp 异步请求服务B: Sanic

再用Sanic测试一遍
  1. from sanic import Sanic
  2. from sanic.response import text
  3. import requests
  4. import httpx
  5. import aiohttp
  6. app = Sanic(__name__)
  7. url = "http://127.0.0.1:8000/a"
  8. @app.get("/sync1")
  9. def sync1(request):
  10.     try:
  11.         resp = requests.get(url, timeout=2)
  12.         resp.raise_for_status()
  13.     except Exception as e:
  14.         print(f"sync1 request failed, {e}")
  15.     else:
  16.         return text(resp.text)
  17. @app.get("/sync2")
  18. def sync2(request):
  19.     try:
  20.         with httpx.Client() as client:
  21.             response = client.get(url, timeout=2)
  22.             response.raise_for_status()
  23.     except Exception as e:
  24.         print(f"sync2 request failed, {e}")
  25.     else:
  26.         return text(response.text)
  27. @app.get("/async1")
  28. async def async1(request):
  29.     try:
  30.         async with httpx.AsyncClient() as client:
  31.             response = await client.get(url, timeout=2)
  32.             response.raise_for_status()
  33.     except Exception as e:
  34.         print(f"async1 request failed, {e}")
  35.     else:
  36.         return text(response.text)
  37. @app.get("/async2")
  38. async def async2(request):
  39.     try:
  40.         async with aiohttp.ClientSession() as session:
  41.             async with session.get(url, timeout=2) as response:
  42.                 response.raise_for_status()
  43.                 content = await response.text()
  44.     except Exception as e:
  45.         print(f"async2 request failed, {e}")
  46.     else:
  47.         return text(content)
  48. if __name__ == "__main__":
  49.     app.run(host="127.0.0.1", port=8001, debug=False, access_log=False, workers=4)
复制代码
可能是我对Sanic了解不多,单就这个测试结果来看,Sanic根本不适合编写同步API。而且使用httpx异步请求的时候有大量报错,wrk结果显示 Non-2xx or 3xx responses: 1244
APITotal requestQPStimeoutcomment/sync1371.2335requests 同步请求/sync2160.5316httpx 同步请求/async15481182.095339httpx 异步请求/async228116934.670aiohttp 异步请求服务B: Go

最后再用Go实现下请求
  1. func GetA(w http.ResponseWriter, r *http.Request) {
  2.         resp, err := http.Get("http://127.0.0.1:8000/a")
  3.         if err != nil {
  4.                 log.Println(err)
  5.         }
  6.         defer resp.Body.Close()
  7.         body, err := io.ReadAll(resp.Body)
  8.         if err != nil {
  9.                 log.Println(err)
  10.         }
  11.         w.Write(body)
  12. }
  13. func main() {
  14.         mux := http.NewServeMux()
  15.         mux.HandleFunc("GET /b", GetA)
  16.         if err := http.ListenAndServe("127.0.0.1:8001", mux); err != nil {
  17.                 panic(err)
  18.         }
  19. }
复制代码
测试结果,和直接请求服务A差别不大。
  1. $ wrk -t8 -c1000 -d30s http://127.0.0.1:8001/b
  2. Running 30s test @ http://127.0.0.1:8001/b
  3.   8 threads and 1000 connections
  4.   Thread Stats   Avg      Stdev     Max   +/- Stdev
  5.     Latency     1.02s    85.69ms   1.66s    96.55%
  6.     Req/Sec   210.49    175.05   740.00     63.65%
  7.   29000 requests in 30.08s, 3.26MB read
  8. Requests/sec:    963.97
  9. Transfer/sec:    111.08KB
复制代码
小结

用python编写同步请求还是老老实实用requests,异步接口应该用aiohttp,httpx的性能只能说能用。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册