2. Redis 核心数据结构回顾:String、List、Hash、Set、Sorted Set 在权限与削峰中的应用基础

在深入企业级权限模型与热点削峰方案之前,必须重新审视 Redis 的五种核心数据结构。本章不讨论基础 API 语法,而是聚焦于每种结构在“权限校验”与“流量削峰”两大场景下的设计模式、内存特征与选型依据。

2.1 数据结构全景对比

数据结构 底层编码 时间复杂度(关键操作) 权限场景典型用途 削峰场景典型用途
String int / embstr / raw O(1) 读写 Token 存储、单用户权限缓存 计数器(令牌桶)、分布式锁
List quicklist O(1) 头尾操作 操作审计日志队列 异步削峰缓冲队列
Hash ziplist / hashtable O(1) 单字段操作 用户权限属性集(角色+资源+有效期) 热点参数聚合、限流维度存储
Set intset / hashtable O(1) 增删查 角色集合、用户权限交集/并集运算 去重拦截、黑/白名单
Sorted Set ziplist / skiplist O(log N) 范围查询 权限优先级排序、资源分级 滑动窗口限流、热点 Key 排序
选型原则: 权限场景优先考虑 Hash 和 Set 的字段级操作与集合运算能力;削峰场景优先考虑 String 的原子计数与 List 的阻塞队列特性。Sorted Set 在两种场景中均承担“排序+窗口”的独特角色。

2.2 String:原子计数器与 Token 基石

String 是 Redis 最基础的结构,但在企业级架构中承担着两个关键角色:分布式计数器和Token 存储。

2.2.1 权限场景:Token 与 Session

用户登录后生成的 Token 直接以 String 类型存储,Key 为 Token 值,Value 为序列化的用户身份 JSON。配合 EXPIRE 命令实现自动过期,避免 Session 堆积。

// 用户登录成功后
SET token:abc123 "{\"uid\":1001,\"role\":\"admin\",\"expire\":1710000000}" EX 7200
// 每次请求时校验
GET token:abc123
// 若返回 nil,则 Token 已过期,需重新登录

2.2.2 削峰场景:令牌桶计数器

利用 INCR 与 EXPIRE 的原子组合,实现秒级计数器。这是最轻量的限流手段,适合单机或简单分布式场景。

// 伪代码:每秒最多允许 100 次请求
String key = "ratelimit:api:user_1001:" + currentSecond;
Long count = redis.incr(key);
if (count == 1) {
    redis.expire(key, 1);  // 设置 1 秒过期
}
if (count > 100) {
    return "请求过于频繁";
}
注意: 上述方案在 INCR 与 EXPIRE 之间若发生崩溃,会导致 Key 永不过期。企业级方案应使用 Lua 脚本或 Redis 6.2+ 的 INCR 带 EX 选项保证原子性。

2.3 List:异步削峰缓冲队列

List 的 LPUSH + BRPOP 模式是企业级削峰的标准组件。请求先入队,后端 Worker 按能力消费,天然实现“生产者-消费者”解耦。

2.3.1 权限场景:操作审计日志

每次权限变更操作(角色分配、权限撤销)都作为一条 JSON 记录 RPUSH 到审计队列。异步消费者批量写入数据库,避免同步写库对权限校验路径的延迟影响。

// 生产者
LPUSH audit:log '{"uid":1001,"action":"assign_role","target":"editor","time":1710000000}'
// 消费者(后台守护进程)
BRPOP audit:log 0

2.3.2 削峰场景:请求缓冲

当瞬时流量超过后端处理能力时,List 作为缓冲区吸收峰值。配合 LLEN 监控队列深度,触发背压或降级策略。

企业实践: 队列长度建议设置上限(如 10 万),超过上限直接丢弃或返回降级响应,防止内存溢出。使用 LTRIM 定期裁剪队列。

2.4 Hash:权限属性集与限流维度

Hash 是权限模型中最常用的结构。一个用户的所有权限属性可以存储在一个 Hash 中,字段级操作避免全量序列化开销。

2.4.1 权限场景:用户权限属性集

将用户 ID 作为 Key,字段为资源标识,值为权限掩码或角色名。查询单资源权限时只需 HGET,无需反序列化整个对象。

// 用户 1001 的权限 Hash
HSET user:1001:perms "report:view" "1"
HSET user:1001:perms "report:edit" "1"
HSET user:1001:perms "admin:panel" "0"
// 校验用户是否有查看报表权限
HGET user:1001:perms "report:view"  // 返回 "1"

2.4.2 削峰场景:多维度限流计数器

Hash 可以存储同一用户在不同接口上的限流计数,避免 Key 爆炸。例如:Key 为 ratelimit:user:1001,字段为接口路径,值为当前秒的请求计数。

// 对用户 1001 的 /api/order 接口限流
HINCRBY ratelimit:user:1001 "/api/order" 1
// 同时设置整个 Hash 的过期时间
EXPIRE ratelimit:user:1001 1

2.5 Set:角色集合与去重拦截

Set 的无序、唯一特性在权限系统中用于角色归属判断和权限交集运算;在削峰中用于幂等去重。

2.5.1 权限场景:角色与权限集合运算

使用 Set 存储用户拥有的角色集合,以及每个角色对应的权限集合。通过 SINTER 求交集判断用户是否同时拥有多个角色;通过 SUNION 合并角色权限。

// 用户 1001 的角色集合
SADD user:1001:roles "admin" "editor"
// 角色 admin 的权限集合
SADD role:admin:perms "report:view" "report:edit" "user:manage"
// 判断用户是否有 "user:manage" 权限
SISMEMBER role:admin:perms "user:manage"  // 返回 1

2.5.2 削峰场景:请求幂等去重

对于重复提交的请求(如支付回调),使用 Set 的 SADD 原子性判断是否已处理。返回 1 表示首次处理,返回 0 表示重复请求。

// 使用请求唯一 ID 去重
SADD processed:orders "order_20240301_1001"
// 若返回 0,说明已处理过,直接返回成功

2.6 Sorted Set:滑动窗口与优先级排序

Sorted Set 的 score 排序特性使其成为滑动窗口限流和权限优先级排序的唯一选择。

2.6.1 权限场景:资源分级与优先级队列

将权限资源按敏感度或优先级排序,score 代表等级。高优先级权限(如超级管理员)score 值高,低优先级(如只读)score 值低。通过 ZRANGEBYSCORE 快速获取某个等级范围内的资源列表。

// 资源分级:score 越高,权限等级越高
ZADD resource:levels 1 "report:view"
ZADD resource:levels 5 "report:edit"
ZADD resource:levels 10 "admin:panel"
// 获取等级 >= 5 的资源
ZRANGEBYSCORE resource:levels 5 10

2.6.2 削峰场景:滑动窗口限流

固定窗口计数器存在“临界突变”问题。滑动窗口使用 Sorted Set 记录每个请求的时间戳,通过 ZREMRANGEBYSCORE 移除窗口外的记录,ZCARD 统计窗口内请求数。

// 滑动窗口限流:窗口 1 秒,最大 10 次
long now = System.currentTimeMillis();
String key = "sliding:user:1001";
// 移除 1 秒前的记录
redis.zremrangeByScore(key, 0, now - 1000);
// 添加当前请求
redis.zadd(key, now, String.valueOf(now));
// 设置 Key 过期(防止内存泄漏)
redis.expire(key, 2);
// 统计窗口内请求数
long count = redis.zcard(key);
if (count > 10) {
    return "限流";
}
性能注意: 滑动窗口的 ZREMRANGEBYSCORE + ZADD 是 O(log N) 操作,高并发下建议使用 Lua 脚本合并为一次原子执行,减少网络往返。

2.7 数据结构选型决策树

Redis 数据结构选型决策树(权限与削峰) 业务场景入口 权限校验场景 流量削峰场景 Token / Session → String + EXPIRE 角色/权限集合 → Set / Hash 请求缓冲 → List (LPUSH+BRPOP) 滑动窗口限流 → Sorted Set 选型核心原则 权限场景:Hash 字段级操作 + Set 集合运算 | 削峰场景:String 原子计数 + List 缓冲 + Sorted Set 窗口 审计日志 → List 去重拦截 → Set

2.8 内存与性能基准参考

以下基准数据基于 Redis 7.0 单实例(Xeon 2.5GHz, 4GB 内存),使用 redis-benchmark 测试,供选型时参考:

数据结构 写入 QPS(万/秒) 读取 QPS(万/秒) 内存开销(100 万 Key) 适用并发等级
String(10 字节 Value) 12.5 14.2 ~85 MB 极高
List(队列模式) 10.8 11.5 ~110 MB 高
Hash(5 字段) 9.2 10.6 ~95 MB 高
Set(10 元素) 8.5 9.8 ~120 MB 中高
Sorted Set(10 元素) 7.1 8.3 ~150 MB 中
关键结论: String 在纯读写场景性能最优;Hash 在字段级操作中内存效率最高;Sorted Set 因 skiplist 结构在写入和内存上开销最大,仅在需要排序范围查询时使用。权限场景优先选择 Hash 和 Set,削峰场景优先选择 String 和 List。

2.9 本章小结

  • String:原子计数器与 Token 存储,削峰场景的基石。
  • List:异步缓冲队列,削峰与审计日志的标准模式。
  • Hash:权限属性集的最佳载体,字段级操作避免全量序列化。
  • Set:角色集合与去重拦截,集合运算能力不可替代。
  • Sorted Set:滑动窗口限流与权限优先级排序,唯一支持范围排序的结构。