Skip to content

057. Redis 加锁缓存与认证中间件准备 ​

学习目标 ​

这一节把分布式锁接入实体缓存,并开始准备认证中间件。

学完后,你应该能理解:

  • RedisLock 为什么可以做成上下文管理器;
  • 加锁后为什么还要再查一次缓存;
  • 用户缓存如何接入登录认证;
  • 更新用户数据后为什么要刷新缓存;
  • 白名单文件负责什么;
  • 中间件适合解决什么问题。

RedisLock ​

分布式锁逻辑可以封装成 RedisLock 工具。

它负责:

  • 获取 Redis 锁;
  • 等待重试;
  • 超时抛错;
  • 在任务结束后释放锁;
  • 用 Lua 避免误删别人的锁。

封装后,业务代码不需要每次都手写 NX、循环等待和 Lua 释放逻辑。

上下文管理器写法 ​

锁适合做成异步上下文管理器。

调用方式类似:

py
async with get_redis_lock(lock_key, redis_client):
    await load_data()

进入 async with 时获取锁。

离开代码块时自动释放锁。

这种写法能降低忘记释放锁的风险。

加锁后的缓存查询 ​

缓存没命中时,不要马上查数据库。

更稳的流程是:

  1. 先查 Redis;
  2. 没命中;
  3. 获取分布式锁;
  4. 拿到锁后再查一次 Redis;
  5. 仍然没命中,才查 MySQL;
  6. 写入 Redis;
  7. 返回结果。

第 4 步很关键。

因为等待锁期间,前一个请求可能已经把数据写进 Redis 了。

如果拿到锁后不再检查缓存,就可能重复查数据库。

查询次数对比 ​

没有分布式锁时,多个并发请求同时缓存未命中,可能都去查 MySQL。

加上分布式锁后,只有第一个请求真正查库。

其他请求等缓存写入后,从 Redis 读取。

这能明显减少并发场景下的无意义查询。

用户缓存对象 ​

用户模型里可以内置一个用户缓存对象。

例如按用户名缓存用户信息。

后续认证逻辑查用户时,不再直接查 MySQL,而是调用用户缓存。

这样当前用户接口、登录认证、后续中间件都可以复用缓存能力。

缓存刷新 ​

只读缓存还不够。

当用户数据被更新后,要主动刷新缓存。

例如账号激活接口把用户状态从未激活改为已激活。

数据库提交后,就应该刷新对应用户缓存。

否则 Redis 里可能还保留旧状态,导致登录认证拿到过期数据。

缓存刷新由业务代码触发 ​

数据库什么时候更新,只有业务代码知道。

所以更新接口完成后,要在合适位置调用缓存刷新。

对于通用 CRUD 路由,可以在通用更新逻辑里统一处理。

对于特殊接口,例如激活账号,也要单独补上刷新动作。

为什么需要认证中间件 ​

前面已经能在某些接口里手动注入当前用户。

但这种方式容易漏。

有些接口不需要当前用户对象,但仍然必须要求登录。

如果完全靠每个接口手动加依赖,接口一多就很难管理。

认证中间件可以在请求进入路由前统一处理 token。

白名单 ​

认证中间件不能拦截所有接口。

这些接口通常要放入白名单:

  • 文档页;
  • 静态资源;
  • 登录接口;
  • 注册接口;
  • 激活账号接口;
  • 刷新 token 接口;
  • 支付或第三方回调;
  • 本地测试接口。

白名单可以放到独立文件里,由配置项指定路径。

精确匹配与通配匹配 ​

白名单里有两类路径。

一种是精确匹配。

例如:

txt
/auth/login

另一种是通配匹配。

例如:

txt
/test/*

通配路径可以让某一组测试接口都免认证。

中间件的基本形态 ​

FastAPI 中间件会接收 request 和 call_next。

它可以在请求进入路由前做处理,也可以在响应返回后补充信息。

例如计时中间件:

  • 请求开始时记录时间;
  • 执行业务路由;
  • 结束后计算耗时;
  • 把耗时写入响应头。

认证中间件也是同类思想,只是它处理的是 token 和白名单。

这一节的重点 ​

这一节把缓存从单点工具推进到认证体系。

你需要记住:

  • 分布式锁适合封装成上下文管理器;
  • 拿到锁后要再查一次缓存;
  • 更新数据库后要刷新缓存;
  • 当前用户查询可以走用户缓存;
  • 认证中间件需要白名单;
  • 中间件能统一处理大量接口的认证要求。

AI Agent 课程学习文档。