Skip to content
第 192 / 250 章中间件⏱ 12 分钟阅读

第 192 章:Redis 高级特性

学习目标

  • 掌握发布订阅模式
  • 学会 Stream 消息流
  • 理解 Lua 脚本
  • 学会 Redis 集群

一、发布订阅(Pub/Sub)

1.1 基础用法

bash
# 订阅
SUBSCRIBE news

# 发送
PUBLISH news "hello"

# 模式订阅
PSUBSCRIBE news.*

# 查看订阅数
PUBSUB CHANNELS
PUBSUB NUMSUB news
PUBSUB NUMPAT

1.2 特点

  • ✅ 实时
  • ❌ 不持久化(消息不存)
  • ❌ 无 ACK 机制

适合实时通知、聊天;不适合消息队列(用 Stream 或 MQ)。

1.3 实战:聊天广播

typescript
// Node.js
const redis = require('ioredis');
const subscriber = new Redis();
const publisher = new Redis();

subscriber.subscribe('chat-room');
subscriber.on('message', (channel, message) => {
  console.log(`收到 ${channel}: ${message}`);
});

publisher.publish('chat-room', 'Hello!');

二、Stream(Redis 5.0+)

Redis 5.0 引入的消息流,持久化的发布订阅 + 消费组。

2.1 基本操作

bash
# 生产
XADD stream:orders * userId 1 amount 100
# * 自动生成 ID(时间戳-序号)

# 消费
XRANGE stream:orders - +         # 全部
XLEN stream:orders               # 长度
XRANGE stream:orders 1700000000000 1700999999999   # 区间

2.2 消费组(关键)

bash
# 创建组
XGROUP CREATE stream:orders group1 $ MKSTREAM

# 消费(每个消费者)
XREADGROUP GROUP group1 consumer1 COUNT 10 BLOCK 5000 STREAMS stream:orders >

# ACK
XACK stream:orders group1 1700000000000-0

# Pending
XPENDING stream:orders group1

# 重新投递
XCLAIM stream:orders group1 consumer2 0 1700000000000-0

2.3 Stream vs Kafka

维度StreamKafka
吞吐量万级百万级
持久化内存 + 磁盘磁盘 + 分区
生态
适用中小项目大数据

三、Lua 脚本

3.1 为什么用 Lua

  • 原子性:单线程执行,无并发问题
  • 高性能:减少网络往返
  • 复用:可缓存到 Redis

3.2 基本语法

lua
-- KEYS 表键
-- ARGV 表参数

local current = redis.call('GET', KEYS[1])
if current == false then
  redis.call('SET', KEYS[1], ARGV[1])
  return ARGV[1]
end
return current

3.3 EVAL 执行

bash
EVAL "return redis.call('GET', KEYS[1])" 1 user:1

EVAL "
  local v = redis.call('GET', KEYS[1])
  if not v then
    redis.call('SET', KEYS[1], ARGV[1], 'EX', 60)
    return ARGV[1]
  end
  return v
" 1 lock:order "123"

3.4 EVALSHA 缓存

bash
# 上传脚本
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# 返回 SHA1:abc123...

# 用 SHA1 执行(更快)
EVALSHA abc123... 1 user:1

3.5 实战:分布式锁

lua
-- 加锁
if redis.call('EXISTS', KEYS[1]) == 1 then
  return 0
end
redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2])
return 1
lua
-- 解锁(只能解自己的锁)
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0

四、Pipeline

减少网络往返,不是原子(Lua 才是)。

bash
# 不推荐:10 次往返
SET k1 v1
SET k2 v2
... (10 )

# 推荐:1 次往返
echo -e "SET k1 v1\nSET k2 v2\n..." | redis-cli --pipe
typescript
// Node.js
const pipeline = redis.pipeline();
pipeline.set('k1', 'v1');
pipeline.set('k2', 'v2');
pipeline.get('k1');
const results = await pipeline.exec();

五、事务(MULTI/EXEC)

bash
MULTI
SET k1 v1
INCR counter
EXEC

特点:

  • ✅ 原子性(EXEC 顺序执行)
  • ❌ 不支持回滚
  • ❌ 不支持条件分支(用 Lua 替代)

六、Redis 集群

6.1 三种集群方案

方案特点
主从复制一主多从,读写分离
哨兵(Sentinel)自动故障转移
Cluster数据分片,水平扩展

6.2 主从复制

bash
# 主库配置(默认)
# redis.conf: 无需特殊配置

# 从库配置
replicaof 192.168.1.100 6379
masterauth yourpassword
replica-read-only yes

特点:

  • 主写从读
  • 异步复制(可能丢数据)
  • 一个主,多个从

6.3 哨兵模式

bash
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 30000
sentinel parallel-syncs mymaster 1

启动:

bash
redis-sentinel /path/to/sentinel.conf

哨兵集群(至少 3 个):

主挂了 → 哨兵选举 → 从升主 → 通知客户端。

6.4 Cluster 集群

数据分片:16384 个 slot,分散到多节点。

bash
# 创建集群(3 主 3 从)
redis-cli --cluster create \
  192.168.1.1:6379 192.168.1.2:6379 192.168.1.3:6379 \
  192.168.1.4:6379 192.168.1.5:6379 192.168.1.6:6379 \
  --cluster-replicas 1

特点:

  • ✅ 水平扩展(16K slot)
  • ✅ 自动故障转移
  • ❌ 不支持多键操作(MGET 跨 slot 不行)
  • ❌ 至少 3 主节点

6.5 Hash Tag

强制同一 slot,支持多键:

bash
# 不同 slot
MSET user:1 name Tom user:2 age 20

# 相同 slot(用 {} 强制)
MSET {user}:1:name Tom {user}:2:age 20

七、客户端分片(无中心)

typescript
// ioredis Cluster 客户端
import Redis from 'ioredis';

const cluster = new Redis.Cluster([
  { host: '192.168.1.1', port: 6379 },
  { host: '192.168.1.2', port: 6379 },
  { host: '192.168.1.3', port: 6379 },
]);

await cluster.set('key', 'value');

八、内存优化

8.1 估算内存

bash
DEBUG OBJECT mykey                # 单个 key
MEMORY USAGE mykey                # 字节
INFO memory                        # 总览

8.2 优化技巧

技巧效果
用 Hash 存多个字段比 String JSON 省内存
短 key 名减少存储
合理过期防止 OOM
避免大 key阻塞主线程
用共享对象池小整数自动共享

8.3 大 key 风险

  • 慢查询(GET/SET 大 value 卡)
  • 删除阻塞(UNLINK 替代 DEL)
  • 集群迁移卡
bash
UNLINK bigkey    # 异步删除(Redis 4+)

九、监控

bash
# 实时命令
MONITOR

# 慢查询
SLOWLOG GET 10
SLOWLOG RESET

# INFO 信息
INFO clients
INFO memory
INFO stats
INFO replication

十、本章小结

特性用途
Pub/Sub实时通知(不持久化)
Stream轻量消息队列
Lua原子操作
Pipeline减少网络
Cluster水平扩展

动手练习

  1. 用 Stream 实现订单事件队列
  2. 写一个 Lua 脚本,实现"如果不存在则设置"
  3. 部署 3 主 3 从的 Redis Cluster
  4. 配置哨兵集群,模拟主库故障

推荐阅读


下一章:第 193 章:Spring Data Redis 实战

本站基于 VitePress 构建 · 由 Codebook 团队维护