2. Redis 基础回顾:数据结构概览与适用场景

在深入后端鉴权拦截与性能优化之前,必须重新夯实 Redis 的数据结构基础。Redis 之所以能成为缓存与鉴权场景的首选,核心在于其丰富的数据类型与原子操作。本章将系统回顾五种核心数据结构:String、Hash、List、Set、Sorted Set,并重点分析它们在鉴权 Token 存储、会话管理、速率限制等场景中的选型依据。

实战视角: 鉴权拦截场景中,90% 的缓存操作集中在 String(Token 键值)、Hash(用户会话字段)、Sorted Set(滑动窗口限流)三种结构上。掌握它们的底层编码与时间复杂度,是写出高性能拦截代码的前提。

2.1 数据结构全景对比

下表从底层编码、时间复杂度、最大容量三个维度对比五种核心数据结构,帮助你在设计鉴权缓存时快速决策。

数据结构 底层编码(Redis 7+) 关键操作时间复杂度 单 Key 最大容量 典型鉴权场景
String int / embstr / raw GET/SET: O(1) 512 MB Token 直接缓存、API Key 校验
Hash ziplist / hashtable HGET/HSET: O(1)
HGETALL: O(N)
2^32 - 1 个 field 用户会话多字段存储(userId, role, expire)
List quicklist LPUSH/RPOP: O(1)
LINDEX: O(N)
2^32 - 1 个元素 操作日志队列、异步通知
Set intset / hashtable SADD/SISMEMBER: O(1)
SMEMBERS: O(N)
2^32 - 1 个元素 黑名单/白名单、权限集合
Sorted Set ziplist / skiplist ZADD: O(log N)
ZRANGEBYSCORE: O(log N + M)
2^32 - 1 个元素 滑动窗口限流、排行榜、过期时间戳排序
选型口诀: 单值用 String,多字段用 Hash,去重用 Set,排序用 Sorted Set,队列用 List。鉴权拦截中,Hash 是存储用户会话的最优解,Sorted Set 是时间窗口限流的唯一选择。

2.2 核心数据结构详解与鉴权命令

2.2.1 String — 最基础的鉴权键值

String 是 Redis 最基础的数据类型,在鉴权场景中通常用于缓存 Token → 用户ID 的映射,或存储 API Key 的明文/哈希值。其操作原子性(SETNX)可用于实现分布式锁,防止鉴权缓存击穿。

# 缓存 Token 与用户信息(JSON 序列化)
SET token:abc123 '{"userId":1001,"role":"admin","exp":1700000000}' EX 3600

# 原子性写入(不存在时才设置,用于分布式锁)
SETNX lock:auth:user:1001 "locked"

# 批量获取多个 Token 状态
MGET token:abc123 token:def456 token:ghi789

2.2.2 Hash — 用户会话的最佳载体

Hash 允许你在一个 Key 下存储多个字段,非常适合存储用户会话的 结构化属性(userId、role、permissions、expireTime)。相比 String 序列化整个对象,Hash 支持单独读写某个字段,减少网络传输量。

# 存储用户会话(每个字段独立)
HSET session:user:1001 userId 1001 role "admin" permissions "read,write" expireAt 1700000000

# 仅获取角色字段(避免传输整个对象)
HGET session:user:1001 role

# 判断会话是否存在(字段计数)
HEXISTS session:user:1001 userId

# 获取所有字段(慎用,O(N) 复杂度)
HGETALL session:user:1001
避坑: 不要将 Hash 的 field 数量设计得过多(超过 1000 个),否则 HGETALL 会阻塞 Redis 单线程。鉴权会话通常控制在 5-10 个字段以内。

2.2.3 List — 异步鉴权日志队列

List 基于双向链表实现,适合用作 消息队列。在鉴权拦截中,可以将每次鉴权请求的日志(IP、时间、结果)异步推入 List,由后台消费者批量写入数据库,避免同步 I/O 阻塞鉴权链路。

# 生产者:鉴权通过后推送日志
LPUSH auth:log:queue '{"ip":"192.168.1.1","time":1700000000,"result":"allow"}'

# 消费者:阻塞式获取日志(BRPOP 支持超时)
BRPOP auth:log:queue 5

# 查看队列长度(监控积压)
LLEN auth:log:queue

2.2.4 Set — 黑名单与权限集合

Set 提供 O(1) 复杂度的成员判断能力,是 黑名单/白名单 场景的天然选择。在鉴权拦截中,可以用 Set 存储被拉黑的 Token 或 IP,拦截时直接通过 SISMEMBER 判断。

# 添加黑名单 IP
SADD blacklist:ip "192.168.1.100" "10.0.0.5"

# 判断请求 IP 是否在黑名单中(O(1))
SISMEMBER blacklist:ip "192.168.1.100"

# 获取所有黑名单 IP(慎用,O(N))
SMEMBERS blacklist:ip

# 集合运算:获取同时存在于两个权限集合的用户
SINTER role:admin role:api-access

2.2.5 Sorted Set — 滑动窗口限流核心

Sorted Set 的每个元素都关联一个 score(通常使用时间戳),可以按 score 范围高效查询。这是实现 滑动窗口限流 的唯一 Redis 原生方案:将每次请求的时间戳作为 score 加入集合,通过 ZREMRANGEBYSCORE 移除窗口外的旧记录,再用 ZCARD 统计当前窗口请求数。

# 滑动窗口限流:记录用户 1001 在时间戳 1700000000 的请求
ZADD rate:limit:user:1001 1700000000 "req:1700000000:uuid"

# 移除 1 秒前的所有记录(窗口大小 1 秒)
ZREMRANGEBYSCORE rate:limit:user:1001 0 1699999999

# 统计当前窗口内的请求数
ZCARD rate:limit:user:1001

# 获取窗口内所有请求时间戳(调试用)
ZRANGE rate:limit:user:1001 0 -1 WITHSCORES

2.3 数据结构选型决策流程

下图展示了在鉴权拦截场景中,如何根据业务需求快速选择 Redis 数据结构。该流程覆盖了 Token 校验、会话管理、限流、黑名单四个核心子场景。

鉴权场景 Redis 数据结构选型决策树 鉴权请求到达 Token 校验 / 键值查询 会话管理 / 多字段读写 String → GET/SET Hash → HGET/HSET 需要限流? 需要黑名单? 是:Sorted Set 否:String 即可 是:Set 否:Hash 继续 说明:Sorted Set 使用时间戳作为 score 实现滑动窗口;Set 提供 O(1) 成员判断;Hash 适合存储结构化会话

2.4 常用命令速查表

以下表格整理了鉴权拦截场景中最常用的 Redis 命令,按数据结构分类,并标注了时间复杂度与典型用途。

数据结构 命令 时间复杂度 鉴权场景用途
String SET key value [EX seconds] O(1) 缓存 Token 并设置过期时间
GET key O(1) 获取 Token 对应的用户信息
SETNX key value O(1) 分布式锁,防止缓存击穿
MGET key1 key2 ... O(N) 批量校验多个 Token 状态
Hash HSET key field value O(1) 设置用户会话的单个字段
HGET key field O(1) 获取会话中的角色或权限
HEXISTS key field O(1) 判断会话字段是否存在
HDEL key field O(1) 删除会话中的某个字段
Set SADD key member O(1) 添加黑名单 IP 或 Token
SISMEMBER key member O(1) 判断请求是否在黑名单中
SREM key member O(1) 从黑名单中移除
Sorted Set ZADD key score member O(log N) 记录请求时间戳用于限流
ZREMRANGEBYSCORE key min max O(log N + M) 移除窗口外的过期记录
ZCARD key O(1) 统计当前窗口请求数
ZRANGEBYSCORE key min max O(log N + M) 获取窗口内所有请求(调试)
List LPUSH key value O(1) 推送鉴权日志到队列
BRPOP key timeout O(1) 消费者阻塞获取日志
性能红线: 在鉴权拦截的高并发路径上,严禁使用 KEYS、SMEMBERS、HGETALL、LRANGE 等 O(N) 命令。必须用 SCAN、SSCAN、HSCAN 替代。Sorted Set 的 ZRANGEBYSCORE 在窗口内记录数超过 1000 时,建议增加 LIMIT 限制。

2.5 内存优化与编码转换

Redis 会根据数据量自动选择底层编码以节省内存。理解编码转换阈值,可以在设计鉴权缓存时提前规避性能陷阱。

数据结构 紧凑编码 转换条件(Redis 7 默认) 转换后编码 内存影响
Hash ziplist field 数 > 512 或单个 field value > 64 字节 hashtable 内存增长 3-5 倍
Sorted Set ziplist 元素数 > 128 或单个 member > 64 字节 skiplist 内存增长 2-3 倍
Set intset 元素数 > 512 或包含非整数元素 hashtable 内存增长 2-4 倍
优化建议: 对于 Hash 存储的用户会话,控制 field 数量在 512 以内,每个 field value 不超过 64 字节,可以保持 ziplist 紧凑编码,内存效率最高。对于 Sorted Set 限流场景,如果窗口内记录数超过 128,建议拆分 Key(如按分钟分片),避免 skiplist 带来的额外内存开销。