--- id: python.fastapi.interview title: FastAPI 面试题 type: interview domain: python stack: [fastapi] tags: [FastAPI, pydantic, 依赖注入, 面试] level: intermediate related: [python.fastapi.routing-validation, python.fastapi.deps-auth-errors, python.fastapi.streaming-testing] week: [4, 5, 16] --- # FastAPI 面试题 ## 基础 ### 【基础】FastAPI 有什么特点?为什么 AI 服务常用它? **参考答案**:基于 Starlette(ASGI)和 pydantic,原生支持 async,适合 IO 密集的模型调用;通过类型标注自动完成参数解析和校验、生成 OpenAPI 文档;依赖注入方便组织鉴权、数据库会话和测试替换;支持流式响应和 WebSocket。AI 生态(模型 SDK、LangChain、向量库客户端)以 Python 为主,用 FastAPI 封装服务很自然。 **追问**:FastAPI 和 Flask、Django 怎么选?→ AI 服务、异步 I/O、需要自动接口文档时选 FastAPI;需要完整后台管理和 ORM 的传统业务系统可以选 Django。 ### 【基础】FastAPI 怎么区分路径参数、查询参数和请求体? **参考答案**:在路径模板中出现的参数是路径参数;简单类型(int、str、bool 等)的其他参数是查询参数;pydantic 模型类型的参数是请求体;请求头、Cookie 用 `Header()`、`Cookie()` 显式声明。也可以用 `Path()`、`Query()`、`Body()` 显式指定并添加校验规则。 **追问**:请求体可以是多个 Pydantic 模型吗?→ 可以,多个模型参数会按参数名嵌套在 JSON 中;单个模型参数想嵌套时用 `Body(embed=True)`。 ### 【基础】路由函数用 `async def` 和 `def` 有什么区别? **参考答案**:`async def` 在事件循环中直接执行,里面只能调用非阻塞的异步代码;普通 `def` 会被放到线程池中执行,适合调用同步阻塞库。在 `async def` 中调用阻塞函数会卡住整个事件循环,是最常见的性能问题。 **追问**:`def` 路由的线程池大小有限制吗?→ 有,默认由 AnyIO 的线程池限制(默认 40 个线程,以官方文档为准),大量阻塞调用会排队。 ### 【基础】响应模型有什么作用? **参考答案**:声明接口返回的数据结构:自动过滤掉未声明的字段(防止泄露内部字段),校验和序列化返回数据,并体现在 OpenAPI 文档中,前端可以据此生成类型。 **追问**:怎么避免把密码等字段返回给前端?→ 定义单独的输出模型(不包含敏感字段)并设置为 `response_model`,而不是直接返回数据库对象。 ## 进阶 ### 【进阶】`Depends` 的原理和常见用途? **参考答案**:FastAPI 在处理请求时解析路由函数签名,递归解析依赖树,按顺序调用依赖函数并把返回值注入参数;同一请求内相同依赖默认只调用一次;带 `yield` 的依赖可以在请求结束后执行清理。用途:鉴权和获取当前用户 / 租户、数据库会话、分页参数、共享客户端;测试时通过 `dependency_overrides` 替换。 **追问**:依赖中的 `yield` 有什么用?→ `yield` 之前创建资源(数据库会话),之后清理资源;测试时可以用 `app.dependency_overrides` 替换依赖。 ### 【进阶】中间件和依赖都能做鉴权,怎么选? **参考答案**:中间件作用于所有请求,适合与业务无关的横切逻辑:请求 ID、日志、耗时、CORS。依赖按路由或路由器声明,能拿到类型化的结果(当前用户)并注入处理函数,能在测试中替换,适合鉴权、权限、租户。通常鉴权用依赖。 **追问**:中间件能访问路由的参数吗?→ 中间件在路由匹配之前执行,拿不到解析后的参数;需要按路由做权限控制的,用依赖。 ### 【进阶】怎么实现大模型的流式输出?要注意什么? **参考答案**:用 `StreamingResponse` 包装异步生成器,`media_type="text/event-stream"`,每个事件 `data: ...\n\n`。注意:响应头发出后不能改状态码,错误要以事件形式发送;检查客户端断开及时停止生成;内容用 JSON 编码避免换行破坏格式;经过 Nginx 时关闭缓冲;前端 POST 请求需要用 fetch 读取流。 **追问**:客户端中途断开,服务端还在调用大模型怎么办?→ 在生成器中检查 `await request.is_disconnected()`,或者依赖取消机制,及时停止上游调用以节省成本。 ## 场景题 ### 【场景题】压测发现 FastAPI 服务 QPS 很低,CPU 也不高,可能是什么原因? **参考答案**:最常见的是在 `async def` 路由里调用了阻塞操作(同步数据库驱动、`requests`、`time.sleep`、同步 SDK),事件循环被阻塞。其他原因:每个请求新建 HTTP 客户端或数据库连接、连接池太小、下游(模型、数据库)本身慢、只启动了一个 worker。排查:看慢请求的耗时分布,用 py-spy 看事件循环线程在做什么。 **追问**:怎么验证是不是事件循环被阻塞了?→ 开启 asyncio 的调试模式(会记录执行过慢的回调),或者在压测时观察一个简单的 `/health` 接口延迟是否也升高。 ### 【场景题】一个接口依赖大模型和向量数据库,怎么写单元测试? **参考答案**:把模型客户端和检索器抽象成接口(Protocol),通过依赖注入获取;测试中用 `app.dependency_overrides` 替换为假实现,返回固定内容或模拟异常;用 `TestClient` 调用接口,断言状态码、响应格式、流式事件顺序、错误处理。真实模型的效果评估另外做(评测集),不放在单元测试里。 **追问**:测试流式接口要注意什么?→ 用 `TestClient` 的 `stream` 方法或 httpx 异步客户端逐块读取,断言事件格式和结束标记,而不只是最终文本。 ### 【场景题】给多个业务系统提供 AI 能力,怎么防止某个系统把额度用光? **参考答案**:每个调用方一个 API Key,鉴权依赖解析出租户;在服务端按租户做限流(Redis 滑动窗口 / 令牌桶)和配额(按日 / 月的 token 数);记录每次调用的 token 用量写入数据库;超过配额返回 429 并给出提示;对异常增长告警。Nginx 层可以再加一层按 IP 的粗粒度限流。 **追问**:额度用完时返回什么?→ 返回 429 和明确的错误码,带上 `Retry-After` 头;同时通知对应业务方,提供额度查询接口。