Developer Tools

Redis 不止于缓存:数据结构、Pub/Sub 与实时应用

学习如何将 Redis 用于缓存之外的场景——探索其数据结构、pub/sub 消息传递、streams 以及构建快速、可扩展应用的设计模式。

10分钟阅读

服务器机房与发光的连接

大多数开发者认识 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 可以替代你技术栈中的多个基础设施组件——并以亚毫秒级速度完成所有操作。