一、Redis核心数据结构与热点场景

在秒杀、排行榜、Feed流等高并发场景中,Redis的数据结构选择直接决定了系统的吞吐上限与响应延迟。本章从五种核心数据结构出发,逐一剖析其底层实现、热点应用模式及性能调优要点。

1.1 String — 秒杀库存与分布式锁

String是Redis最基础的数据结构,在秒杀场景中承担库存扣减与分布式锁两大职责。其底层采用SDS(Simple Dynamic String),支持O(1)长度的原子增减操作。

秒杀库存扣减核心命令:
# 初始化库存
SET seckill:stock:1001 100

# 原子扣减(返回扣减后的值)
DECR seckill:stock:1001

# 带库存校验的扣减(Lua脚本保证原子性)
local stock = redis.call('GET', KEYS[1])
if stock and tonumber(stock) > 0 then
    return redis.call('DECR', KEYS[1])
else
    return -1
end
场景 数据结构 核心命令 时间复杂度 热点风险
库存扣减 String DECR / DECRBY O(1) 高(单Key竞争)
分布式锁 String SET NX EX O(1) 中(锁超时)
计数器 String INCR / INCRBY O(1) 低(分片后)
性能调优要点:秒杀场景下String热点Key会导致单实例CPU打满。建议采用Key分片策略,将单个库存Key拆分为N个段(如stock:1001:0~stock:1001:9),扣减时随机选取一段,降低单Key竞争。
String在秒杀场景中的热点Key分片架构 秒杀客户端 请求分发层 库存分片Key组 stock:1001:0 ~ stock:1001:9 实例0 实例1 实例2 实例3 实例4 调优说明: 1. 分片数量建议为10~20个,过多增加管理开销 2. 扣减时使用随机算法选择分片,避免请求倾斜 3. 库存耗尽时快速返回,避免无效网络开销

1.2 List — 秒杀异步队列与消息缓冲

List的LPUSH+BRPOP组合天然构成高性能生产者-消费者队列。在秒杀场景中,订单创建请求先入List缓冲,后端Worker异步消费,实现请求削峰。

秒杀异步队列实现:
// 生产者(秒杀接口)
LPUSH seckill:order:queue "{userId:1001, skuId:2001, time:1700000000}"

// 消费者(Worker进程)
BRPOP seckill:order:queue 5  // 阻塞5秒等待消息
操作 命令 时间复杂度 适用场景
入队 LPUSH / RPUSH O(1) 请求缓冲
出队 LPOP / RPOP O(1) 即时消费
阻塞出队 BLPOP / BRPOP O(1) 避免空轮询
范围查询 LRANGE O(N) 队列监控
避坑指南:List作为队列时,严禁使用LRANGE全量拉取再逐条删除,这会带来O(N)的复杂度。始终使用LPUSH+BRPOP或RPUSH+BLPOP的阻塞模式,单次操作复杂度O(1)。

1.3 Set — 秒杀去重与用户标签

Set的SADD/SISMEMBER操作在秒杀中用于一人一单校验,确保同一用户不能重复秒杀。其底层采用Hash表实现,SISMEMBER时间复杂度O(1)。

秒杀去重校验:
// 用户是否已秒杀
SISMEMBER seckill:users:1001 10001

// 记录秒杀用户
SADD seckill:users:1001 10001

// 获取秒杀用户总数
SCARD seckill:users:1001
场景 命令 数据量级 内存优化
用户去重 SADD / SISMEMBER 百万级 使用intset编码
标签系统 SADD / SMEMBERS 十万级 避免SMEMBERS全量
共同关注 SINTER / SUNION 万级 控制集合大小

1.4 ZSet — 排行榜与实时排序

ZSet(有序集合)是排行榜场景的最优解。每个元素关联一个score,Redis内部使用跳跃表(Skip List)+ Hash表实现,支持O(log N)的插入、更新和范围查询。

实时排行榜核心操作:
// 更新用户分数
ZINCRBY leaderboard:game:1001 10 "user_10001"

// 获取Top10
ZREVRANGE leaderboard:game:1001 0 9 WITHSCORES

// 获取用户排名
ZREVRANK leaderboard:game:1001 "user_10001"

// 获取用户分数
ZSCORE leaderboard:game:1001 "user_10001"
操作 命令 时间复杂度 热点场景
更新分数 ZINCRBY O(log N) 游戏/直播打赏
获取TopN ZREVRANGE O(log N + M) 榜单展示
获取排名 ZREVRANK O(log N) 用户个人排名
范围删除 ZREMRANGEBYRANK O(log N + M) 冷数据清理
性能调优要点:排行榜ZSet的最大风险是单个Key过大。当成员数超过1000万时,ZINCRBY延迟可能超过10ms。建议按时间分桶(如每日一个ZSet),或使用近似排行榜(只保留Top 10000,定期裁剪)。
ZSet排行榜分桶架构与查询流程 用户请求 时间桶路由 时间分桶ZSet leaderboard:20250101 | leaderboard:20250102 | ... 查询流程: 1. 用户请求 → 2. 根据时间戳定位分桶 → 3. 在对应ZSet执行ZREVRANGE 4. 如需跨桶聚合,使用ZUNIONSTORE合并 → 5. 返回TopN结果 分桶优势: 单桶数据量可控(建议<100万),ZINCRBY延迟稳定在1ms以内,支持按时间维度清理冷数据

1.5 Hash — Feed流与用户画像

Hash结构在Feed流场景中用于存储用户时间线或帖子元数据。每个用户维护一个Hash,field为帖子ID,value为时间戳或摘要,支持按需获取指定字段。

Feed流Hash存储模型:
// 存储用户Feed
HSET feed:user:10001 "post:20001" 1700000000
HSET feed:user:10001 "post:20002" 1700000001

// 批量获取Feed元数据
HMGET feed:user:10001 "post:20001" "post:20002"

// 获取所有Feed(慎用)
HGETALL feed:user:10001
场景 数据结构 优势 注意事项
用户时间线 Hash 按需获取字段 控制field数量
帖子元数据 Hash 内存紧凑 避免HGETALL
用户画像 Hash 灵活扩展属性 使用ziplist编码
避坑指南:Hash的HGETALL命令会返回所有field-value,当field数量超过1000时,网络传输和序列化开销显著增加。Feed流场景建议使用HMGET按需获取,或配合List存储时间线顺序、Hash存储元数据。

1.6 数据结构选型对比总结

场景 推荐结构 核心指标 热点风险 调优方向
秒杀库存 String 原子性、O(1) 高 Key分片、Lua脚本
秒杀队列 List 阻塞出队、O(1) 中 多消费者、确认机制
秒杀去重 Set SISMEMBER O(1) 低 intset编码、过期清理
排行榜 ZSet 排序、范围查询 中 时间分桶、近似排行
Feed流 Hash + List 按需获取、顺序 低 HMGET替代HGETALL
架构师建议:热点场景的本质矛盾是单Key高并发与Redis单线程模型之间的冲突。解决思路有三:
1. Key分片:将热点Key拆散到多个实例
2. 本地缓存:热点数据在应用层缓存,减少Redis压力
3. 异步化:同步操作转为异步队列,削峰填谷