第 192 章:Redis 高级特性
学习目标
- 掌握发布订阅模式
- 学会 Stream 消息流
- 理解 Lua 脚本
- 学会 Redis 集群
一、发布订阅(Pub/Sub)
1.1 基础用法
bash
# 订阅
SUBSCRIBE news
# 发送
PUBLISH news "hello"
# 模式订阅
PSUBSCRIBE news.*
# 查看订阅数
PUBSUB CHANNELS
PUBSUB NUMSUB news
PUBSUB NUMPAT1.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-02.3 Stream vs Kafka
| 维度 | Stream | Kafka |
|---|---|---|
| 吞吐量 | 万级 | 百万级 |
| 持久化 | 内存 + 磁盘 | 磁盘 + 分区 |
| 生态 | 弱 | 强 |
| 适用 | 中小项目 | 大数据 |
三、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 current3.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:13.5 实战:分布式锁
lua
-- 加锁
if redis.call('EXISTS', KEYS[1]) == 1 then
return 0
end
redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2])
return 1lua
-- 解锁(只能解自己的锁)
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 --pipetypescript
// 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 | 水平扩展 |
动手练习
- 用 Stream 实现订单事件队列
- 写一个 Lua 脚本,实现"如果不存在则设置"
- 部署 3 主 3 从的 Redis Cluster
- 配置哨兵集群,模拟主库故障