高级应用
Redis 的数据结构组合出各种应用模式。面试主线:Stream 怎么当消息队列、限流怎么做、分布式 ID 和排行榜怎么用 zset。
Stream:轻量消息队列
Stream(5.0+)是 Redis 官方的消息队列结构:
| 能力 | 说明 |
|---|---|
| XADD | 追加消息,返回消息 ID(毫秒时间戳加序号) |
| XREADGROUP | 消费者组读取,组内消息分发给不同消费者 |
| 消费确认 | XACK 确认,未确认消息进 Pending 列表,可重试 |
| 消息留存 | 按 MAXLEN 限制长度,不自动删除 |
对比 Kafka:
| 维度 | Stream | Kafka |
|---|---|---|
| 定位 | 轻量,随 Redis 部署 | 重量级独立系统 |
| 吞吐 | 十万级 | 百万级 |
| 持久化 | Redis 持久化语义 | 磁盘日志,强持久 |
| 生态 | 无 | 分区、重平衡、流处理完整 |
选型结论:业务量小、不想引入 Kafka 时用 Stream;高吞吐、强持久、复杂消费语义用 Kafka。
限流
| 方案 | 实现 | 特点 |
|---|---|---|
| 固定窗口 | INCR + EXPIRE(计数到阈值拒绝) | 简单,窗口边界突刺(前后窗口交界可双倍流量) |
| 滑动窗口 | zset 记录时间戳,统计窗口内请求数 | 精确,zset 占用随请求量增长 |
| 令牌桶 | Lua 脚本维护令牌数和上次补充时间 | 允许突发,平滑限流 |
| 漏桶 | 恒定速率处理 | 不允许突发,实现复杂 |
生产常用令牌桶(Lua 原子实现)加多级限流(网关层 + 应用层 + Redis 层)。
分布式 ID
- INCR/INCRBY:Redis 自增,简单但依赖 Redis 可用性,批量取号(一次 INCRBY 100 本地分发)可减压力
- 雪花算法(Snowflake):时间戳 + 机器 ID + 序列号,趋势递增,无中心依赖,业界主流(美团 Leaf 等)
- 对比:UUID 无序且太长,不适合做索引;数据库自增受单点限制
排行榜
zset 天然适配:
- 分数即排名依据,
ZADD更新,ZREVRANGE取前 N 名,ZRANK查排名 - 实时榜单(直播间热度、游戏分数)都是 zset
- 同分处理:分数可编码(如分数加时间戳小数位),或按字典序兜底
其他常用模式
| 模式 | 实现 |
|---|---|
| 布隆过滤器 | RedisBloom 模块,防缓存穿透 |
| UV 统计 | HyperLogLog 12KB 统计上亿 |
| 签到/位图 | bitmap 按位标记 |
| 附近的人 | GEO(geohash 进 zset) |
| 延迟队列 | zset 按执行时间排序,轮询取到期任务 |
面试追问
- Stream 和 Kafka 怎么选? 量小轻量用 Stream,高吞吐强持久用 Kafka。Stream 是 Redis 的附属能力
- 固定窗口限流的问题? 窗口边界突刺:两个窗口交界处可放行双倍流量。滑动窗口精确但占内存
- 令牌桶怎么实现? Lua 原子维护令牌数和补充时间,按速率补令牌,有令牌才放行。允许突发
- 雪花算法的组成? 时间戳 + 机器 ID + 序列号,趋势递增。时钟回拨是主要风险
- 排行榜用什么结构? zset,ZADD 更新分数,ZREVRANGE 取榜单,O(log n) 查询