Skip to content

077. Redis 测试修复与请求链路结构

学习目标

这一节先修复自动化测试问题,再梳理后端请求链路。

学完后,你应该能理解:

  • Redis 客户端为什么会在 PyTest 里触发事件循环错误;
  • 测试环境下如何避免复用失效连接;
  • 一个请求从服务器到 FastAPI 会经过哪些层;
  • 中间件的洋葱模型是什么;
  • 通用模块拦截器和中间件有什么区别。

测试失败现象

通用模块测试里出现了 401。

表面看是认证失败。

但 Token 本身没问题。

真正原因出在认证中间件读取用户缓存时。

用户缓存依赖 Redis。

Redis 客户端复用了已经绑定旧事件循环的连接。

旧事件循环被 PyTest 关闭后,再访问 Redis 就会报错。

事件循环和 Redis 客户端

PyTest 跑异步测试时,可能为每个测试用例创建独立事件循环。

测试结束后,这个事件循环会关闭。

Redis 客户端创建时会绑定当前事件循环。

如果这个客户端被放回连接池,下一个测试再复用它,就可能遇到:

txt
Event loop is closed

这个错误不是业务认证问题。

是测试环境和连接池复用之间的冲突。

测试环境处理方式

解决方式是区分测试环境和正常运行环境。

正常运行时继续使用 Redis 连接池。

测试运行时每次创建新的 Redis 客户端。

使用完后直接关闭。

这样客户端不会跨事件循环复用。

测试就能稳定通过。

单独运行失败测试

测试失败很多时,不一定要每次重跑全部测试。

可以只跑失败测试。

也可以单独运行某一个测试用例。

在 PyCharm 里,如果直接运行某个测试,面板可能覆盖原来的失败列表。

一种更方便的做法是用 Debug 单独跑目标测试。

这样保留原来的 Run 面板,同时多出一个 Debug 面板。

请求进入服务器的路径

一个外部请求进入后端,大致经过这些层:

  1. 云服务器安全组;
  2. Linux 防火墙;
  3. Nginx;
  4. Uvicorn;
  5. FastAPI app
  6. FastAPI 中间件;
  7. 路由匹配;
  8. 接口函数;
  9. 服务函数;
  10. 数据库或缓存。

每一层负责的事情不同。

不要把它们混成一个概念。

云安全组

云安全组是云厂商提供的网络防火墙。

它可以控制:

  • 哪些端口开放;
  • 哪些 IP 可以访问;
  • 哪些协议可以进来。

它的规则通常比较简单。

适合做最外层访问控制。

Linux 防火墙

服务器操作系统本身也可能有防火墙。

如果某些端口访问不了,除了看云安全组,还要检查系统防火墙。

有些镜像默认关闭。

有些系统可能默认开启。

这会影响 Nginx 或后端端口能不能被访问。

Nginx

Nginx 监听常见入口端口。

例如:

  • HTTP 默认走 80;
  • HTTPS 默认走 443。

它可以把请求转发到:

  • 前端静态文件;
  • 某个后端端口;
  • 不同项目的子路径。

课程项目里使用类似 /704/705 这种子路径转发到不同后端端口。

这是教学环境的管理方式。

企业环境更常见的是用 devuatapi 这类命名区分环境。

Uvicorn 和 FastAPI

Uvicorn 负责监听后端端口。

收到请求后,把请求交给 FastAPI 的 app 对象处理。

可以把 Uvicorn 理解成 Python 后端的运行服务器。

FastAPI app 则负责:

  • 中间件;
  • 路由;
  • 参数解析;
  • 接口执行;
  • 响应返回。

中间件洋葱模型

中间件是洋葱模型。

请求进入时按顺序一层层进入。

响应返回时再反向一层层出来。

所以它是:

txt
先进后出

例如请求进入:

txt
认证中间件 -> 错误处理中间件 -> 耗时统计中间件 -> 接口

响应返回时顺序反过来。

中间件和拦截器区别

中间件包在接口外面。

它处理的是整个请求生命周期。

例如:

  • 跨域;
  • Token 认证;
  • 错误处理;
  • 请求耗时统计。

Battice 拦截器在服务函数内部。

它处理的是通用模块某个动作前后的扩展点。

例如:

  • 新增前;
  • 新增后;
  • 查询前;
  • 查询后;
  • 更新缓存。

两者名字都像“拦截”,但层级完全不同。

路由匹配

FastAPI 根据路由规则找到对应接口。

通用接口通常是:

txt
/generic/{module}/{action}

module 是模块标识。

action 是操作类型。

路由匹配后,接口再调用 Battice 服务函数。

这一节的重点

这一节把测试问题和请求结构串起来了。

你需要记住:

  • PyTest 异步测试里 Redis 客户端不能随便跨事件循环复用;
  • 测试环境可以每次创建独立 Redis 客户端;
  • 请求先经过服务器网络层,再进入 Nginx、Uvicorn、FastAPI;
  • 中间件是接口外层的洋葱模型;
  • Battice 拦截器是服务函数内部的前后置动作;
  • 通用接口由动态路由匹配模块和操作类型。

AI Agent 课程学习文档。