覆盖 String/Hash/List/Set/ZSet/Bitmap/HyperLogLog/Geo/Stream 全数据结构,秒杀、限流、延迟队列、分布式会话、分布式ID、购物车等11个真实场景代码逐行解析。
一、入门:会用 Redis
1. Redis 是什么
Redis 是一个基于内存的高性能 Key-Value 数据库。它不仅能存字符串,还支持多种数据结构,常用于缓存、计数器、排行榜、分布式锁、消息队列、会话存储和实时统计。
Redis 的主要特点:
- 基于内存,读写性能高。
- 支持 String、Hash、List、Set、ZSet、Stream 等数据结构。
- 支持过期时间,适合做缓存。
- 支持 RDB 和 AOF 持久化。
- 支持主从复制、Sentinel 和 Cluster。
- 命令执行模型简单,单条命令具备原子性。
典型使用场景:
| 场景 | 常用数据结构 | 示例 |
|---|---|---|
| 缓存用户信息 | String / Hash | user:1001 |
| 验证码 | String | verify:phone:138xxxx |
| 文章阅读量 | String | article:1001:views |
| 抽奖 | Set | lottery:users |
| 排行榜 | ZSet | game:rank |
| 消息队列 | List / Stream | order:stream |
| 分布式锁 | String | lock:order:1001 |
2. Key 基础命令
常用命令
bash
SET name redis
GET name
EXISTS name
DEL name
EXPIRE name 60
TTL name
TYPE name
SCAN 0
示例:设置验证码
bash
SET verify:1001 9527 EX 60
GET verify:1001
TTL verify:1001
说明:
EX 60表示 60 秒后过期。TTL查看剩余过期时间。
生产环境注意:
bash
KEYS *
KEYS * 会遍历所有 key,可能阻塞 Redis。生产环境建议使用:
bash
SCAN 0 MATCH user:* COUNT 100
二、Redis 全数据结构详解
1. String:字符串
String 是 Redis 最基础的数据结构,可以存普通字符串、数字、JSON 字符串、二进制内容。
基础命令
bash
SET user:1:name Tom
GET user:1:name
MSET user:1:name Tom user:1:age 20
MGET user:1:name user:1:age
INCR article:1001:views
INCRBY article:1001:views 10
DECR stock:1001
示例:缓存用户 JSON
bash
SET user:1 '{"id":1,"name":"Tom","age":20}' EX 300
GET user:1
适合场景:验证码 / Token / Session / 页面缓存 / 对象 JSON 缓存 / 计数器
2. Hash:哈希
Hash 适合存对象,可以单独读写对象中的某个字段。
基础命令
bash
HSET user:1 name Tom age 20 city Shanghai
HGET user:1 name
HMGET user:1 name age
HGETALL user:1
HINCRBY user:1 age 1
HDEL user:1 city
示例:购物车
bash
HSET cart:1001 product:2001 2
HSET cart:1001 product:2002 1
HINCRBY cart:1001 product:2001 1
HGETALL cart:1001
String JSON 与 Hash 对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| String 存 JSON | 读取整体对象方便 | 修改单个字段需要反序列化再写回 |
| Hash | 可单独修改字段 | 对复杂嵌套对象不如 JSON 直观 |
3. List:列表
List 是有序、可重复的链表结构,适合做队列、栈、最近记录。
基础命令
bash
LPUSH queue task1
RPOP queue
BRPOP order:queue 10
LRANGE logs 0 9
适合场景:简单消息队列 / 最近浏览记录 / 操作日志 / 栈结构
局限:消息确认机制弱,更可靠的队列建议使用 Stream 或专业 MQ。
4. Set:集合
Set 是无序、不可重复的集合,适合去重和集合运算。
基础命令
bash
SADD user:1:tags java redis mysql
SMEMBERS user:1:tags
SISMEMBER user:1:tags redis
SCARD user:1:tags
集合运算
bash
SINTER user:1:follows user:2:follows # 共同好友
SUNION user:1:follows user:2:follows # 所有好友
SDIFF user:1:follows user:2:follows # 独有好友
示例:抽奖
bash
SADD lottery:users 1001 1002 1003 1004
SRANDMEMBER lottery:users 2 # 不移除
SPOP lottery:users 1 # 移除
5. ZSet:有序集合
ZSet 是有序集合,每个元素都有一个分数,Redis 按分数排序。
示例:游戏排行榜
bash
ZADD game:rank 1200 user:1001
ZADD game:rank 1800 user:1002
ZADD game:rank 1500 user:1003
ZREVRANGE game:rank 0 9 WITHSCORES # TOP 10
ZREVRANK game:rank user:1001 # 查名次
ZINCRBY game:rank 50 user:1001 # 加分
适合场景:排行榜 / 热门文章 / 延迟队列 / 时间线
6. Bitmap:位图
Bitmap 本质上是 String 的位操作,极节省内存。1亿用户签到状态只需约 12MB。
基础命令
bash
SETBIT key offset value
GETBIT key offset
BITCOUNT key [start end]
BITOP AND|OR|XOR|NOT destkey key [key ...]
示例:用户签到
bash
# 用户 1001 在 2026年4月 第 24 天签到
SETBIT sign:1001:202604 23 1
# 查询是否签到
GETBIT sign:1001:202604 23
# 统计当月签到天数
BITCOUNT sign:1001:202604
集合运算
bash
# 求两天都签到的用户(AND)
BITOP AND result:and sign:20260423 sign:20260424
适合场景:签到打卡 / 在线状态 / 功能开关 / 布隆过滤器底层
7. HyperLogLog:基数估算
HyperLogLog 用于统计不重复元素数量,误差约 0.81%,每个 key 固定占用约 12KB 内存。
示例:UV 统计
bash
PFADD uv:20260424 user:1001 user:1002 user:1003 user:1001
PFCOUNT uv:20260424
# 合并多天数据,统计周 UV
PFMERGE uv:week uv:20260418 uv:20260419 uv:20260420 uv:20260421 uv:20260422 uv:20260423 uv:20260424
PFCOUNT uv:week
| 方案 | 精确性 | 内存占用 | 适合场景 |
|---|---|---|---|
| Set | 精确 | 随数据增长 | 需要知道具体是哪些用户 |
| HyperLogLog | 约 0.81% 误差 | 约 12KB 固定 | 只关心数量,不关心是谁 |
8. Geo:地理位置
Geo 基于 ZSet 实现,支持附近搜索和距离计算。
示例:附近门店
bash
GEOADD stores 121.4737 31.2304 "shop:1001"
GEOADD stores 121.4800 31.2350 "shop:1002"
# 查询 5km 内的门店,按距离排序
GEOSEARCH stores FROMLONLAT 121.4737 31.2304 BYRADIUS 5 km ASC COUNT 10
# 计算两店距离
GEODIST stores shop:1001 shop:1002 km
适合场景:附近的人 / 附近门店 / 配送范围 / 地理围栏
9. Stream:消息队列
Stream 是 Redis 提供的日志型消息结构,更适合做消息队列。
bash
# 生产消息
XADD order:stream * orderId 1001 userId 1
# 创建消费者组
XGROUP CREATE order:stream group1 0 MKSTREAM
# 消费
XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS order:stream >
# 确认
XACK order:stream group1 message-id
| 方案 | 是否持久化 | 是否支持消费者组 | 是否适合可靠队列 |
|---|---|---|---|
| Pub/Sub | 否 | 否 | 不适合 |
| List | 部分支持 | 否 | 一般 |
| Stream | 是 | 是 | 较适合 |
三、提高:缓存设计与工程实践
1. Cache Aside 缓存模式
读流程:
text
查询缓存
-> 命中:直接返回
-> 未命中:查询数据库
-> 写入缓存
-> 返回结果
写流程(推荐):
java
mysql.update(user);
redis.del("user:" + user.getId());
不推荐"更新数据库后更新缓存",因为多个并发写操作可能导致缓存中出现旧值。
2. 缓存穿透
问题: 请求的数据既不在 Redis,也不在数据库。
解决方案一:缓存空值
bash
SET user:999999 null EX 60
解决方案二:布隆过滤器
text
请求 user:999999999
-> 布隆过滤器判断一定不存在
-> 直接拒绝
3. 缓存击穿
问题: 某个热点 Key 过期,大量请求同时打到数据库。
解决方案一:互斥锁
bash
SET lock:rebuild:product:1001 1 NX EX 10
解决方案二:逻辑过期
缓存中保存逻辑过期时间,过期后先返回旧数据,后台异步刷新。
4. 缓存雪崩
问题: 大量 Key 同时过期,或 Redis 整体不可用。
解决: TTL 加随机值 + 热点数据预热 + 多级缓存 + 限流降级
java
int ttl = 300 + random.nextInt(60);
redis.set(key, value, ttl);
5. 缓存预热
应用启动前主动加载热点数据:
java
@Component
public class CacheWarmup implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
List<Product> hotProducts = productMapper.selectHotProducts();
for (Product p : hotProducts) {
redis.set("product:detail:" + p.getId(), toJson(p), 300 + random.nextInt(60));
}
}
}
| 风险 | 应对 |
|---|---|
| 启动雷暴 | 分批预热、主从只由主实例预热 |
| 内存超限 | 只预热访问频率最高的 Top N |
| 数据过期 | 合理 TTL + 随机偏移 |
6. 分布式锁
bash
SET lock:order:1001 requestId NX EX 10
安全释放(Lua 脚本):
lua
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
工程实践中常用 Redisson,提供锁续期(看门狗)、可重入锁、公平锁、读写锁等能力。
7. Lua 脚本
Lua 脚本可以让多条 Redis 命令原子执行。
示例:限流脚本
lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
local count = redis.call("INCR", key)
if count == 1 then
redis.call("EXPIRE", key, expire)
end
if count > limit then
return 0
else
return 1
end
8. Pipeline
Pipeline 用于批量发送命令,减少网络往返次数。
java
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key:" + i, "value:" + i);
}
pipeline.sync();
四、典型工程实例
1. 秒杀库存扣减(Lua 防超卖)
lua
local stockKey = KEYS[1]
local orderKey = KEYS[2]
local userId = ARGV[1]
if redis.call("SISMEMBER", orderKey, userId) == 1 then
return 2 -- 重复下单
end
local stock = tonumber(redis.call("GET", stockKey))
if stock <= 0 then
return 0 -- 库存不足
end
redis.call("DECR", stockKey)
redis.call("SADD", orderKey, userId)
return 1 -- 抢购成功
完整流程:
text
用户请求
-> Redis Lua 判断库存和重复下单
-> 扣减 Redis 库存
-> 写入消息队列
-> 异步创建订单
-> 数据库最终落库
2. 接口限流
固定窗口限流:
bash
INCR rate:user:1001
EXPIRE rate:user:1001 60
滑动窗口限流(ZSet):
bash
ZADD rate:user:1001 1713931200000 request-1
ZREMRANGEBYSCORE rate:user:1001 0 1713931140000
ZCARD rate:user:1001
3. 延迟队列
使用 ZSet,score 为执行时间戳:
bash
# 添加任务
ZADD delay:queue 1713931200000 order:1001
# 拉取到期任务
ZRANGEBYSCORE delay:queue 0 1713931200000 LIMIT 0 10
# 删除已处理任务
ZREM delay:queue order:1001
适合:订单超时取消 / 延迟通知 / 失败重试
4. 商品详情缓存
java
String key = "product:detail:" + productId;
String cache = redis.get(key);
if (cache != null) {
return parse(cache);
}
Product product = productMapper.selectById(productId);
if (product == null) {
redis.set(key, "null", 60); // 防穿透:缓存空值
return null;
}
int ttl = 300 + random.nextInt(60); // 防雪崩:随机 TTL
redis.set(key, toJson(product), ttl);
return product;
5. 分布式会话
bash
HSET session:abc123 userId 1001 username Tom role admin loginTime 1713931200
EXPIRE session:abc123 1800
HGETALL session:abc123
Token 方案对比:
| 方案 | 存储位置 | 特点 |
|---|---|---|
| Session + Redis | 服务端 Redis | 服务端可控,支持主动注销 |
| JWT | 客户端 Token | 无需服务端存储,但无法主动失效 |
| JWT + Redis 黑名单 | 客户端 + Redis | 兼顾无状态和主动注销 |
Spring Boot 集成:
yaml
spring:
session:
store-type: redis
timeout: 30m
6. 分布式 ID 生成
bash
INCR order:id
INCRBY order:id:segment 1000 # 号段模式,一次取 1000 个
| 方案 | 有序性 | 性能 | 风险 |
|---|---|---|---|
| Redis INCR | 严格递增 | 高 | Redis 宕机影响 |
| Redis 号段 | 段内递增 | 极高 | 段用完前 Redis 宕机可暂用 |
| Snowflake | 趋势递增 | 极高 | 时钟回拨 |
本文是《Redis 知识体系》系列第一篇,覆盖数据结构与高频工程实战。
第二篇:《Redis 原理深度解析》→ 持久化/主从/Sentinel/Cluster/性能排查
第三篇:《Redis 专家实战》→ 生产架构/容量规划/安全/37道面试题