2. Redis 基础回顾:数据结构概览与适用场景
在深入后端鉴权拦截与性能优化之前,必须重新夯实 Redis 的数据结构基础。Redis 之所以能成为缓存与鉴权场景的首选,核心在于其丰富的数据类型与原子操作。本章将系统回顾五种核心数据结构:String、Hash、List、Set、Sorted Set,并重点分析它们在鉴权 Token 存储、会话管理、速率限制等场景中的选型依据。
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 个元素 | 滑动窗口限流、排行榜、过期时间戳排序 |
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
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 校验、会话管理、限流、黑名单四个核心子场景。
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 倍 |