3、Redis基础回顾:Redis数据结构、String与Hash在Token存储中的应用、过期策略
3.1 Redis核心数据结构概览
Redis 提供了多种高性能数据结构,在 Token 鉴权场景中,最常使用的是 String 和 Hash。理解它们的特点与适用边界,是构建高效缓存链路的基础。
| 数据结构 | 底层编码 | 单 Key 容量 | 典型操作 | Token 场景适用性 |
|---|---|---|---|---|
| String | int / embstr / raw | 512 MB | SET / GET / SETEX / TTL | 单 Token 字符串存储,简单直接 |
| Hash | ziplist / hashtable | 2^32 - 1 个 field | HSET / HGET / HDEL / HGETALL | Token 元数据(uid + 过期 + 权限)结构化存储 |
| List | quicklist | 2^32 - 1 个元素 | LPUSH / RPOP / LRANGE | 不适用于 Token 鉴权 |
| Set | intset / hashtable | 2^32 - 1 个成员 | SADD / SISMEMBER / SMEMBERS | 黑名单 / 白名单集合 |
| Sorted Set | ziplist / skiplist | 2^32 - 1 个成员 | ZADD / ZRANK / ZREMRANGEBYSCORE | Token 按时间排序淘汰 |
选型原则: 如果只需要存储一个 Token 字符串(如 JWT),String 是最优解;如果需要同时存储 Token 对应的用户 ID、角色、过期时间等多个字段,Hash 能减少 Key 数量,提升内存利用率。
3.2 String 在 Token 存储中的应用
String 是 Redis 最基础的数据结构,在 Token 鉴权中通常以 token:{uuid} 为 Key,以 JWT 或 Session ID 为 Value。配合 SETEX 命令,可以在写入时直接设置过期时间,原子性完成存储与过期绑定。
# 存储 Token,有效期 3600 秒
SETEX token:a1b2c3d4 3600 "eyJhbGciOiJIUzI1NiIs..."
# 读取 Token
GET token:a1b2c3d4
# 检查剩余生存时间
TTL token:a1b2c3d4
# 删除 Token(登出时使用)
DEL token:a1b2c3d4
性能数据: String 的 SET/GET 操作在单节点 Redis 上可达 10 万+ QPS,内存开销仅为 Key + Value + 元数据(约 50 字节)。适合高并发、低延迟的 Token 校验场景。
3.3 Hash 在 Token 存储中的应用
当 Token 需要携带多个属性(如 uid、role、expireAt、refreshToken)时,使用 Hash 可以将这些字段聚合在一个 Key 下,避免生成多个 String Key,减少内存碎片和网络往返。
# 存储 Token 元数据
HSET token:a1b2c3d4 uid 10086 role admin expireAt 1718000000 refreshToken "rt_xxxx"
# 读取单个字段
HGET token:a1b2c3d4 uid
# 读取所有字段
HGETALL token:a1b2c3d4
# 删除某个字段
HDEL token:a1b2c3d4 refreshToken
# 设置整个 Hash 的过期时间(需借助 Key 过期)
EXPIRE token:a1b2c3d4 3600
注意: Redis Hash 本身不支持 field 级别的 TTL,只能对整个 Key 设置过期时间。如果需要对单个 field 设置不同过期时间,需要拆分为多个 String Key 或使用 Sorted Set 辅助实现。
3.4 String vs Hash:Token 存储对比
| 对比维度 | String | Hash |
|---|---|---|
| Key 数量 | 每个 Token 属性一个 Key | 一个 Key 包含多个 field |
| 内存效率 | 小 Value 时略高 | 多字段时节省 Key 元数据开销 |
| 读写粒度 | 整个 Value 读写 | 支持单个 field 读写 |
| 过期控制 | 每个 Key 独立 TTL | 整个 Key 统一 TTL |
| 序列化开销 | 无(直接存字符串) | 无(field/value 均为字符串) |
| 适用场景 | 纯 Token 字符串,无元数据 | Token + 用户信息 + 权限等结构化数据 |
3.5 过期策略:被动与主动
Redis 的过期策略是 Token 自动失效的基石。理解其机制,可以避免因过期 Key 堆积导致的内存泄漏或鉴权异常。
- 被动过期(惰性删除): 当客户端访问一个 Key 时,Redis 检查其是否过期,若过期则删除并返回 nil。这种策略对 CPU 友好,但过期 Key 可能长时间占用内存。
- 主动过期(定期删除): Redis 每隔 100ms 随机抽取一批设置了 TTL 的 Key,检查并删除其中过期的 Key。默认每次抽取 20 个,若过期比例超过 25%,则继续抽取。
生产建议: 不要依赖 Redis 过期策略做精确的定时清理。对于 Token 场景,建议在业务层配合 TTL 检查 和 定时任务补偿删除,确保过期 Token 不会在缓存中残留过久。
3.6 Token 过期策略选型
| 策略 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定 TTL | SETEX / EXPIRE | 简单,Redis 原生支持 | 所有 Token 同时过期,可能引发缓存雪崩 | 短期 Token(如 30 分钟) |
| 滑动 TTL | 每次访问时重新 SETEX | 活跃 Token 自动续期 | 写操作增加,需注意并发覆盖 | 用户在线状态保持 |
| 双 TTL + 刷新 | Access Token 短 TTL + Refresh Token 长 TTL | 安全与体验平衡 | 需要额外维护 Refresh Token 逻辑 | OAuth2 / JWT 标准方案 |
| 定时批量删除 | SCAN + DEL 扫描过期 Key | 精确控制内存释放 | 增加 Redis 负载,需错峰执行 | 高安全合规场景 |
3.7 数据结构与过期策略联动架构
下图展示了 Token 从生成到过期淘汰的完整链路,以及 String / Hash 在其中的位置。
3.8 生产实践:Token 存储选型决策树
在实际项目中,可以根据以下决策树快速选择合适的数据结构:
- 是否只需要存储 Token 字符串本身?
- 是 → 使用 String + SETEX
- 否 → 进入下一步
- 是否需要存储多个字段(uid、role、expireAt 等)?
- 是 → 使用 Hash + EXPIRE
- 否 → 使用 String
- 是否需要 field 级别独立过期?
- 是 → 拆分为多个 String Key,或使用 Sorted Set 辅助
- 否 → 保持 Hash 统一过期
- 是否需要频繁更新部分字段?
- 是 → Hash 的 HINCRBY / HSET 更高效
- 否 → String 全量覆盖即可
最佳实践: 对于大多数 Token 鉴权场景,推荐使用 Hash 存储 Token 元数据,配合 滑动 TTL 策略。这样既能减少 Key 数量,又能灵活管理 Token 生命周期。如果 Token 本身是自包含的 JWT(无需额外元数据),则 String 是更轻量的选择。