2. Redis 基础回顾:Redis 核心数据结构及其在鉴权场景中的典型用途

在构建后端鉴权拦截系统时,Redis 不仅是高性能缓存层,更是承载会话状态、令牌黑名单、访问频率控制等核心逻辑的“数据中枢”。理解 Redis 五种核心数据结构及其在鉴权场景中的映射关系,是设计可靠拦截方案的前提。本节从数据结构特性出发,逐一拆解其在鉴权流程中的典型用途。

2.1 数据结构总览与鉴权映射

下表总结了五种数据结构的核心特性及其在鉴权拦截中最常见的应用模式:

数据结构 底层实现 核心操作 鉴权场景典型用途
String SDS(动态字符串) GET / SET / SETEX / INCR 单点登录 Token 存储、验证码、用户会话 ID 映射
Hash 压缩列表 / 哈希表 HGET / HSET / HGETALL / HDEL 用户会话详情(userId、角色、过期时间)、OAuth2 客户端信息
List 快速列表(quicklist) LPUSH / RPUSH / LPOP / BRPOP 异步鉴权日志队列、令牌刷新请求缓冲
Set 整数集合 / 哈希表 SADD / SISMEMBER / SMEMBERS / SPOP Token 黑名单、已吊销会话集合、API 白名单路径
Sorted Set 跳表 + 压缩列表 ZADD / ZRANGEBYSCORE / ZREMRANGEBYSCORE 滑动窗口限流、Token 自动过期清理、访问频次排行榜
架构选型提示: 在鉴权拦截层,String 和 Set 是最高频使用的结构,分别承担“单点查询”和“集合判重”职责;Sorted Set 则用于需要时间窗口排序或自动淘汰的场景,如滑动窗口限流。
Redis 核心数据结构与鉴权场景映射 Redis 核心数据结构 → 鉴权场景映射 Redis 数据引擎 String Hash List Set Sorted Set Token 存储 验证码 / 会话 ID 用户会话详情 OAuth2 客户端 鉴权日志队列 令牌刷新缓冲 Token 黑名单 API 白名单路径 滑动窗口限流 Token 自动过期清理 鉴权拦截层通过组合使用多种数据结构,实现高性能会话管理

2.2 String:会话令牌与验证码的基石

String 是 Redis 最基础的结构,在鉴权中承担“键值对”直存角色。典型用途包括:

  • Token 直存: 以 token:{uuid} 为 key,用户 ID 或会话 JSON 为 value,配合 SETEX 设置过期时间,实现无状态 Token 的快速校验。
  • 验证码存储: 手机/邮箱验证码使用 SETEX captcha:{phone} 300 123456,5 分钟自动失效。
  • 分布式计数器: 使用 INCR 实现登录失败次数计数,超过阈值则临时锁定账号。
// 示例:Token 存储与校验(伪代码)
// 登录成功时存储
SETEX token:a1b2c3d4 7200 "userId=1001,role=admin"
// 鉴权拦截时校验
GET token:a1b2c3d4
// 若返回 nil,则 Token 已过期或不存在

2.3 Hash:结构化会话详情

当需要存储用户会话的多个字段(如 userId、角色、权限列表、登录 IP、过期时间戳)时,Hash 比 String 拼接 JSON 更节省内存且支持部分更新。

  • 会话详情存储: HSET session:1001 userId 1001 role admin permissions "read,write"
  • OAuth2 客户端信息: 存储 client_id、client_secret、redirect_uri 等字段,支持按需读取。
  • 部分更新优势: 仅更新某个字段(如最后访问时间)时,无需整体序列化/反序列化。
性能对比: 在字段数 < 100 且单字段长度较小时,Hash 的内存效率比 String 高 30%-50%,且 HGET 操作时间复杂度为 O(1)。

2.4 List:异步鉴权日志与请求缓冲

List 在鉴权中主要用于“生产者-消费者”模式,处理非实时、可异步落盘的场景:

  • 鉴权日志队列: 每次鉴权拦截后,将请求路径、用户、结果、时间戳 LPUSH 到日志 List,后台消费者 BRPOP 批量写入数据库。
  • 令牌刷新缓冲: 当 Token 即将过期时,将刷新请求放入 List,由专门 worker 异步处理,避免同步刷新阻塞主请求。
// 生产者:记录鉴权日志
LPUSH auth:log:queue '{"path":"/api/order","userId":1001,"result":"deny","ts":1700000000}'
// 消费者:后台批量消费
BRPOP auth:log:queue 0

2.5 Set:黑名单与白名单的高效判重

Set 的无序、唯一特性使其成为鉴权中“集合归属判断”的首选:

  • Token 黑名单: 用户登出或管理员强制吊销后,将 Token 的 jti(JWT ID) SADD 到 blacklist:tokens,拦截时 SISMEMBER 判断是否已吊销。
  • API 白名单: 将无需鉴权的路径(如 /login、/register)存入 Set,拦截器先 SISMEMBER 判断是否跳过鉴权。
  • 已吊销会话集合: 存储已失效的 sessionId,支持批量清理。
// 黑名单判重示例
SISMEMBER blacklist:tokens "a1b2c3d4"
// 返回 1 表示已吊销,拦截请求

2.6 Sorted Set:滑动窗口限流与自动过期清理

Sorted Set 的 score 排序特性,使其在时间窗口类鉴权场景中不可替代:

  • 滑动窗口限流: 以用户 ID 或 IP 为 key,每次请求 ZADD 当前时间戳作为 score,通过 ZREMRANGEBYSCORE 移除窗口外记录,ZCARD 统计窗口内请求数。
  • Token 自动过期清理: 将 Token 的过期时间戳作为 score,后台定时任务 ZREMRANGEBYSCORE 移除已过期的 Token 记录,避免内存泄漏。
  • 访问频次排行榜: 统计各 API 或各用户的访问次数,用于异常检测。
// 滑动窗口限流:1 分钟内最多 10 次
ZREMRANGEBYSCORE rate:limit:user:1001 0 (now-60)
ZCARD rate:limit:user:1001
// 若结果 < 10,则允许请求并 ZADD 当前时间戳
Sorted Set 滑动窗口限流流程 Sorted Set 滑动窗口限流流程 请求到达 ZREMRANGEBYSCORE ZCARD 统计 < 阈值? 允许 + ZADD 拒绝请求 是 否

2.7 数据结构组合实战:完整鉴权拦截流程

在实际工程中,一次完整的鉴权拦截往往需要组合使用多种数据结构。以下是一个典型流程:

  1. 白名单过滤(Set): 判断请求路径是否在 whitelist:paths 中,是则直接放行。
  2. Token 提取与校验(String): 从 Header 提取 Token,GET token:{token} 检查是否存在且未过期。
  3. 黑名单检查(Set): SISMEMBER blacklist:tokens {token} 判断是否已被吊销。
  4. 会话详情加载(Hash): HGETALL session:{userId} 获取角色、权限等,用于后续鉴权。
  5. 限流检查(Sorted Set): 对用户或 IP 执行滑动窗口限流,超出阈值则拒绝。
  6. 日志记录(List): 将本次鉴权结果异步 LPUSH 到日志队列。
故障排查要点: 在组合使用多种数据结构时,需注意各操作的原子性与超时设置。例如,黑名单检查与 Token 校验之间若存在竞态条件,可能导致已吊销 Token 被短暂放行。建议使用 Lua 脚本或 Redis 事务保证多个操作的原子性。