Redis 不止于缓存:数据结构、Pub/Sub 与实时应用
学习如何将 Redis 用于缓存之外的场景——探索其数据结构、pub/sub 消息传递、streams 以及构建快速、可扩展应用的设计模式。
大多数开发者认识 Redis,是因为它"缓存速度极快"。但 Redis 其实是一个功能完备的内存数据结构服务器,可以处理会话管理、实时排行榜、限流、消息队列等众多场景——所有操作都以亚毫秒级延迟完成。
Redis 为何如此之快
Redis 将所有数据存储在内存中,并使用单线程事件循环来处理命令。读取时无需磁盘 I/O,没有锁竞争,也没有上下文切换。最终结果:在普通硬件上即可实现 每秒 100,000+ 次操作。
典型延迟对比:
┌─────────────────┬──────────────┐
│ 操作 │ 延迟 │
├─────────────────┼──────────────┤
│ Redis GET │ 0.1 ms │
│ PostgreSQL SEL │ 1-5 ms │
│ REST API call │ 50-200 ms │
│ 磁盘读取 │ 5-10 ms │
└─────────────────┴──────────────┘
核心数据结构
Redis 不仅仅是键值存储,它支持丰富的数据结构,能够直接映射到常见的应用需求。
Strings——基础构件
Strings 可存储文本、数字或最大 512 MB 的二进制数据:
SET user:1001:name "Alice"
GET user:1001:name # "Alice"
# 原子计数器
INCR page:views # 1, 2, 3, ...
INCRBY cart:total 2500 # 增加 25.00(以分为单位存储)
# 带过期时间的键(TTL)
SET session:abc123 "{...}" EX 3600 # 1 小时后过期
TTL session:abc123 # 剩余秒数
Hashes——轻量级对象
Hashes 非常适合存储对象,无需序列化开销:
HSET user:1001 name "Alice" email "alice@example.com" plan "pro"
HGET user:1001 name # "Alice"
HGETALL user:1001 # 所有字段和值
HINCRBY user:1001 logins 1 # 原子字段递增
与将每个字段存储为独立键相比,Hashes 节省约 10 倍内存。
Lists——队列与动态信息流
支持从两端进行 push/pop 操作的有序集合:
LPUSH notifications:alice "New comment on your post"
LPUSH notifications:alice "You have a new follower"
LRANGE notifications:alice 0 9 # 最新 10 条通知
# 用作队列(生产者/消费者模式)
RPUSH queue:emails "send-welcome"
LPOP queue:emails # 处理下一个任务
Sets——唯一元素集合
由唯一字符串组成的无序集合:
SADD tags:post:42 "redis" "database" "nosql"
SMEMBERS tags:post:42 # 所有标签
SISMEMBER tags:post:42 "redis" # true
# 集合操作
SINTER tags:post:42 tags:post:99 # 共同标签
SUNION tags:post:42 tags:post:99 # 所有标签合并
Sorted Sets——排行榜与排名
每个成员带有分值的集合,自动按分值排序:
ZADD leaderboard 1500 "alice" 2300 "bob" 1800 "charlie"
ZREVRANGE leaderboard 0 2 WITHSCORES # 前 3 名玩家
ZRANK leaderboard "alice" # 排名(从 0 开始)
ZINCRBY leaderboard 100 "alice" # Alice 得 100 分
游戏排行榜、热门内容以及优先级队列在大规模场景下正是如此构建的。
实际应用模式
模式 1:会话存储
使用 Redis 实现即时查找,代替基于数据库的会话存储:
import redis
r = redis.Redis()
# 存储会话
r.setex(f"session:{token}", 3600, json.dumps(user_data))
# 获取会话
data = r.get(f"session:{token}")
为何选择 Redis? 数据库会话每次请求增加 1-5ms 延迟,Redis 会话仅增加 0.1ms。在每秒 1000 次请求的场景下,每秒可节省 5 秒的延迟开销。
模式 2:限流
用 3 条命令实现滑动窗口限流:
# 每用户每分钟允许 100 次请求
MULTI
INCR rate:user:1001
EXPIRE rate:user:1001 60
EXEC
# 判断:若 INCR 结果 > 100,则拒绝请求
模式 3:Cache-Aside 缓存模式
最常见的缓存模式:
def get_user(user_id):
# 1. 检查缓存
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# 2. 缓存未命中 → 查询数据库
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 3. 填充缓存(5 分钟后过期)
redis.setex(f"user:{user_id}", 300, json.dumps(user))
return user
模式 4:Pub/Sub 实现实时事件
Redis Pub/Sub 支持服务间实时消息传递:
# 订阅者(监听消息)
SUBSCRIBE chat:room:42
# 发布者(发送消息)
PUBLISH chat:room:42 "Hello everyone!"
在应用代码中:
# 发布者
redis.publish("notifications", json.dumps({
"type": "new_order",
"order_id": 12345
}))
# 订阅者
pubsub = redis.pubsub()
pubsub.subscribe("notifications")
for message in pubsub.listen():
handle_notification(message)
模式 5:分布式锁
防止多台服务器之间出现竞态条件:
# 获取锁(NX = 仅当不存在时设置,EX = 过期时间)
SET lock:invoice:gen "worker-1" NX EX 30
# 释放锁(仅当自己持有锁时释放——使用 Lua 脚本)
Redis Streams——事件溯源与消息队列
Streams 是 Redis 对标 Kafka 风格事件日志的解决方案:
# 添加事件
XADD orders * user_id 1001 product "Widget" qty 3
XADD orders * user_id 1002 product "Gadget" qty 1
# 读取最新事件
XRANGE orders - + COUNT 10
# 消费者组(多个 worker)
XGROUP CREATE orders processors $ MKSTREAM
XREADGROUP GROUP processors worker-1 COUNT 5 BLOCK 2000 STREAMS orders >
Streams 提供持久化、可回放的事件日志,并支持消费者组——非常适合微服务间通信。
性能优化技巧
1. 批量操作使用 pipelining
将 100 条命令一次性发送,而非进行 100 次往返:
pipe = redis.pipeline()
for i in range(100):
pipe.set(f"key:{i}", f"value:{i}")
pipe.execute() # 单次网络往返
2. 选择合适的数据结构
- 需要计数器?→
INCR(而非 GET + SET) - 需要存储对象?→ Hash(而非序列化的 JSON 字符串)
- 需要唯一元素?→ Set(而非 List + 去重逻辑)
- 需要排名?→ Sorted Set(而非查询后再排序)
3. 为所有键设置 TTL
内存是有限的,务必为缓存键设置过期时间:
SET cache:api:result "{...}" EX 300 # 5 分钟 TTL
4. 使用键命名规范
resource:id:field
user:1001:profile
cache:api:v2:products
session:abc123
queue:emails:pending
持久化:RDB 与 AOF
Redis 可以将数据持久化到磁盘以保证数据安全:
| 模式 | 工作原理 | 权衡取舍 |
|---|---|---|
| RDB | 定时快照 | 恢复速度快,可能丢失部分数据 |
| AOF | 记录每次写操作 | 较慢,数据丢失极少 |
| 两者结合 | RDB + AOF 同时启用 | 最佳持久性保障 |
# 在 redis.conf 中配置
save 900 1 # 900 秒内有 1 个键变更则创建快照
appendonly yes # 启用 AOF
appendfsync everysec # 每秒执行一次 fsync
快速命令参考
需要查找特定 Redis 命令?使用我们的 Redis Cheat Sheet——涵盖 200+ 条命令,按数据结构分类整理,附带语法说明、示例和一键复制功能。
总结
Redis 远不止是一个缓存工具。它的数据结构能够清晰地映射到实际应用需求:
- Strings → 会话、计数器、简单缓存
- Hashes → 用户资料、配置对象
- Lists → 队列、动态信息流、最近访问记录
- Sets → 标签、唯一访客、关系数据
- Sorted Sets → 排行榜、排名、优先级队列
- Streams → 事件溯源、消息队列
- Pub/Sub → 实时通知、聊天
从缓存开始,但不要止步于此。Redis 可以替代你技术栈中的多个基础设施组件——并以亚毫秒级速度完成所有操作。