2. Redis 基础回顾:Redis 核心数据结构及其适用场景
在深入企业级热点削峰与权限缓存设计之前,必须对 Redis 的五种核心数据结构建立精确的认知。每种数据结构都有其独特的底层实现与最佳适用场景,选错结构将直接导致性能劣化或资源浪费。
2.1 数据结构全景对比
下表从底层编码、时间复杂度、最大容量与典型业务场景四个维度进行横向对比:
| 数据结构 | 底层编码 | 关键操作时间复杂度 | 单 Key 最大容量 | 典型企业场景 |
|---|---|---|---|---|
| String | int / embstr / raw | GET/SET: O(1) | 512 MB | 计数器、分布式锁、Session 令牌 |
| Hash | ziplist / hashtable | HGET/HSET: O(1) | 2^32 - 1 个 field | 用户画像、商品详情、权限属性集 |
| 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 + dict | ZADD: O(log N) ZRANGE: O(log N + M) |
2^32 - 1 个元素 | 排行榜、延迟队列、限流滑动窗口 |
2.2 String — 最通用的基础单元
String 是 Redis 最基础的数据类型,value 可以是字符串、整数或浮点数。底层编码根据值的大小与类型自动切换:整数使用 int 编码,短字符串使用 embstr,长字符串使用 raw。
# 计数器原子操作
SET login_count:user:1001 0
INCR login_count:user:1001
GET login_count:user:1001
# 分布式锁(NX + EX)
SET lock:order:pay_001 "thread_abc" NX EX 30
# 缓存 JSON 序列化对象
SET user:profile:1001 '{"name":"张三","role":"admin"}' EX 3600
2.3 Hash — 对象级缓存的正确选择
Hash 适合存储结构化对象,每个 field 对应一个属性。相比 String 序列化整个对象,Hash 支持单独读写某个字段,节省带宽并支持部分更新。
# 存储用户权限属性
HSET user:perm:1001 role "admin" dept "tech" level "L5"
HGET user:perm:1001 role
HGETALL user:perm:1001
# 批量获取多个 field
HMGET user:perm:1001 role level
当 field 数量较少(默认 < 512)且每个 field 值较小时,Redis 使用 ziplist 编码以节省内存;超过阈值后自动切换为 hashtable。在权限模型中,一个用户的权限属性通常不超过 20 个 field,非常适合 ziplist 模式。
2.4 List — 有序队列与消息缓冲
List 基于 quicklist(双向链表 + ziplist 混合结构)实现,支持双端操作。在企业架构中,List 常用于异步削峰:请求先入 List,后端 worker 从另一端消费。
# 请求入队(左侧压入)
LPUSH request:queue:order "order_1001"
LPUSH request:queue:order "order_1002"
# 工作线程消费(右侧弹出,阻塞等待)
BRPOP request:queue:order 5
# 获取队列长度(用于监控积压)
LLEN request:queue:order
2.5 Set — 去重与集合运算利器
Set 基于 hashtable 或 intset 实现,元素唯一且无序。在权限系统中,Set 天然适合存储角色集合、资源 ID 集合,并支持高效的集合运算。
# 用户角色集合
SADD role:user:1001 "admin" "editor"
SISMEMBER role:user:1001 "admin" # 返回 1
# 资源权限集合
SADD perm:resource:report_001 "read" "export"
# 集合运算:找出同时拥有两个角色的用户
SINTER role:admin role:editor
# 随机弹出(用于抽奖或负载均衡)
SPOP candidate:pool 1
当所有元素都是整数且数量较少时,intset 编码可大幅节省内存。权限模型中常用 SISMEMBER 做 O(1) 的权限校验,比遍历 List 高效得多。
2.6 Sorted Set — 带权重的有序集合
Sorted Set 通过 skiplist + dict 实现,每个元素关联一个 double 类型的 score,按 score 排序。这是实现热点削峰中“优先级队列”和“滑动窗口限流”的核心结构。
# 延迟队列:score 为执行时间戳
ZADD delay:task:pay 1710000000 "order_1001"
ZADD delay:task:pay 1710003600 "order_1002"
# 取出到期的任务
ZRANGEBYSCORE delay:task:pay -inf 1710000000 WITHSCORES
# 热点排行榜
ZINCRBY hot:product:rank 1 "product_888"
# 滑动窗口限流:score 为请求时间戳
ZADD rate:limit:api:user_1001 1710000000 "req_001"
ZREMRANGEBYSCORE rate:limit:api:user_1001 -inf 1709913600
ZCARD rate:limit:api:user_1001
2.7 数据结构选型决策流程
下图总结了从业务需求到 Redis 数据结构的选型路径:
2.8 企业级选型避坑清单
- 不要用 String 存大对象:频繁全量读写浪费带宽,改用 Hash 按字段操作。
- 不要用 List 做随机访问:LINDEX 是 O(N),改用 Sorted Set 或单独索引。
- 不要用 Set 做排行榜:Set 无序,无法排序;改用 Sorted Set 利用 score 排序。
- 不要用 Hash 存大量 field:超过 512 个 field 后 ziplist 失效,内存膨胀;考虑拆分多个 Key。
- 不要忽略过期时间:权限缓存必须设置 TTL,避免脏数据长期驻留。