fastapi 接口阻塞问题处理

发布时间 2023-08-15 22:13:58作者: bitterteaer

原文地址:https://blog.csdn.net/qq_42006301/article/details/124873383

背景

在使用fastapi框架开发时,遇到一个坑,在请求某个耗时的接口时,再请求其他接口,发现请求都被阻塞卡住了,经过排查问题,发现是async使用不当导致的

问题复现

这里写了一个小demo文件,里面有耗时函数work 和 两个接口 /test1、 /test2

def work(s):
    print("work start work")
    time.sleep(s)
    print("work end work")


@api.get("/test1")
async def test1():
    print("func: test1")
    return "test1"


@api.get("/test2")
async def test2():
    a = int(time.time())
    print(f"start: {a}")
    work(8)
    b = int(time.time())
    print(f"start: {b-a}")
    return f"test2耗时:{b-a}"

使用如下测试脚本请求接口测试

import time
import requests
from threading import Thread

def task1():
    a = int(time.time())
    requests.get(url="http://127.0.0.1:8000/test/test1")
    b = int(time.time())
    print(f"test1耗时{b - a}")


def task2():
    a = int(time.time())
    requests.get(url="http://127.0.0.1:8000/test/test2")
    b = int(time.time())
    print(f"test2耗时{b - a}")


Thread(target=task1).start()
Thread(target=task2).start()

运行测试脚本发现,/test1被阻塞了,等/test2处理完之后,/test1 才返回结果
执行顺序如下

test2--start: 1652973708
work start work
work end work
test2--end: 1652973716
test1--执行
INFO:     127.0.0.1:63965 - "GET /test/test2 HTTP/1.1" 200 OK
INFO:     127.0.0.1:63966 - "GET /test/test1 HTTP/1.1" 200 OK

测试脚本

test1耗时8
test2耗时8

Process finished with exit code 0

可以看到,/test1在/test2执行完之后才执行,/test1请求同样耗时8秒
解决方法1
直接删除掉 /test2 的 async(fastapi会按照多线程的方式进行处理)
改为如下:

@api.get("/test2")
def test2():
    a = int(time.time())
    print(f"test2--start: {a}")
    work(8)
    b = int(time.time())
    print(f"test2--end: {b}")
    return f"test2耗时:{b-a}"

此时运行测试脚本,执行顺序如下

test1--执行
test2--start: 1652973918
work start work
INFO:     127.0.0.1:64024 - "GET /test/test1 HTTP/1.1" 200 OK
work end work
test2--end: 1652973926
INFO:     127.0.0.1:64023 - "GET /test/test2 HTTP/1.1" 200 OK

耗时

test1耗时0
test2耗时8

Process finished with exit code 0

解决方法2

利用asyncio获取当前的event loop。
然后将耗时函数work放到一个event loop中去运行

@api.get("/test2")
async def test2():
    a = int(time.time())
    print(f"test2--start: {a}")
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, work, 8)
    b = int(time.time())
    print(f"test2--end: {b}")
    return f"test2耗时:{b-a}"

执行顺序如下

test2--start: 1652974163
work start work
test1--执行
INFO:     127.0.0.1:64094 - "GET /test/test1 HTTP/1.1" 200 OK
work end work
test2--end: 1652974171
INFO:     127.0.0.1:64093 - "GET /test/test2 HTTP/1.1" 200 OK

耗时

test1耗时0
test2耗时8

Process finished with exit code 0

总结

如果定义了async函数,函数体却是同步的调用,将导致函数执行过程变成串行,所以在 async函数里如果有io等阻塞的地方一定要使用 await 将程序挂起。