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 课程学习文档。