Redis 进阶知识
2026/1/14大约 6 分钟
Redis 进阶知识
一、持久化详解
RDB 持久化
RDB 通过快照方式将内存数据保存到磁盘。
配置方式:
# redis.conf
save 900 1 # 900秒内至少1次修改
save 300 10 # 300秒内至少10次修改
save 60 10000 # 60秒内至少10000次修改
dbfilename dump.rdb
dir /var/lib/redis手动触发:
SAVE # 同步保存,阻塞主线程
BGSAVE # 后台异步保存(推荐)优缺点:
- ✅ 文件紧凑,恢复速度快
- ✅ 适合备份和灾难恢复
- ❌ 可能丢失最后一次快照后的数据
- ❌ fork 子进程时可能造成短暂阻塞
AOF 持久化
AOF 记录每个写操作命令,重启时重放恢复数据。
配置方式:
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
# 同步策略
appendfsync always # 每次写入都同步(最安全,最慢)
appendfsync everysec # 每秒同步一次(推荐)
appendfsync no # 由操作系统决定(最快,可能丢数据)AOF 重写:
BGREWRITEAOF # 手动触发重写
# 自动重写配置
auto-aof-rewrite-percentage 100 # 文件增长100%时重写
auto-aof-rewrite-min-size 64mb # 最小64MB才重写混合持久化(Redis 4.0+)
结合 RDB 和 AOF 的优点。
aof-use-rdb-preamble yesAOF 文件前半部分是 RDB 格式,后半部分是 AOF 增量命令。
二、主从复制
配置主从
从节点配置:
# redis.conf
replicaof 192.168.1.100 6379
# 或运行时设置
REPLICAOF 192.168.1.100 6379查看复制状态:
INFO replication复制原理
- 全量复制:从节点首次连接,主节点执行 BGSAVE 生成 RDB 发送
- 增量复制:主节点将写命令发送到复制缓冲区,从节点读取执行
- 断线重连:通过复制偏移量(offset)进行增量同步
主节点 从节点
| |
|<--- PSYNC ? -1 ---------| (首次连接)
|---- +FULLRESYNC ------->|
|---- RDB 文件 ---------->|
|---- 增量命令 ---------->|
| |读写分离
// 主节点写
masterJedis.set("key", "value");
// 从节点读
String value = slaveJedis.get("key");三、哨兵模式(Sentinel)
哨兵用于监控主从节点,实现自动故障转移。
配置哨兵
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000参数说明:
monitor:监控的主节点,2 表示需要 2 个哨兵同意才能故障转移down-after-milliseconds:主节点无响应多久判定为下线parallel-syncs:故障转移时同时同步的从节点数failover-timeout:故障转移超时时间
启动哨兵
redis-sentinel sentinel.conf
# 或
redis-server sentinel.conf --sentinelJava 连接哨兵
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.1.101:26379");
sentinels.add("192.168.1.102:26379");
sentinels.add("192.168.1.103:26379");
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
try (Jedis jedis = pool.getResource()) {
jedis.set("key", "value");
}四、Redis Cluster 集群
集群架构
- 数据分片:16384 个槽位(slot),每个节点负责一部分
- 去中心化:节点间通过 Gossip 协议通信
- 高可用:每个主节点配备从节点
创建集群
# 创建6个节点(3主3从)
redis-cli --cluster create \
192.168.1.101:6379 \
192.168.1.102:6379 \
192.168.1.103:6379 \
192.168.1.104:6379 \
192.168.1.105:6379 \
192.168.1.106:6379 \
--cluster-replicas 1集群命令
# 查看集群信息
CLUSTER INFO
# 查看节点
CLUSTER NODES
# 查看槽位分配
CLUSTER SLOTS
# 计算 Key 属于哪个槽
CLUSTER KEYSLOT mykeyJava 连接集群
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.101", 6379));
nodes.add(new HostAndPort("192.168.1.102", 6379));
nodes.add(new HostAndPort("192.168.1.103", 6379));
JedisCluster cluster = new JedisCluster(nodes);
cluster.set("key", "value");
String value = cluster.get("key");集群限制
- 不支持多 Key 操作(除非在同一槽位)
- 不支持多数据库(只有 db0)
- 事务只能在单节点执行
Hash Tag:强制 Key 分配到同一槽位
SET {user:1000}.name "张三"
SET {user:1000}.age "25"
# 这两个 Key 会分配到同一槽位五、Lua 脚本
为什么用 Lua?
- 原子性:脚本中的命令作为整体执行
- 减少网络开销:多个命令一次发送
- 复用:脚本可缓存重复使用
基本语法
-- 获取参数
local key = KEYS[1]
local value = ARGV[1]
-- 调用 Redis 命令
redis.call('SET', key, value)
local result = redis.call('GET', key)
return result执行脚本
# EVAL 执行
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
# EVALSHA 执行(使用脚本 SHA1)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
EVALSHA <sha1> 1 mykeyJava 执行 Lua
String script =
"local current = redis.call('GET', KEYS[1]) " +
"if current then " +
" return redis.call('INCR', KEYS[1]) " +
"else " +
" redis.call('SET', KEYS[1], ARGV[1]) " +
" return ARGV[1] " +
"end";
Object result = jedis.eval(script,
Collections.singletonList("counter"),
Collections.singletonList("1"));实战:分布式锁
-- 加锁
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
if redis.call('SETNX', key, value) == 1 then
redis.call('EXPIRE', key, ttl)
return 1
end
return 0-- 解锁(验证持有者)
local key = KEYS[1]
local value = ARGV[1]
if redis.call('GET', key) == value then
return redis.call('DEL', key)
end
return 0六、内存管理
内存淘汰策略
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru| 策略 | 说明 |
|---|---|
| noeviction | 不淘汰,内存满时报错 |
| allkeys-lru | 所有 Key 中淘汰 LRU |
| volatile-lru | 有过期时间的 Key 中淘汰 LRU |
| allkeys-lfu | 所有 Key 中淘汰 LFU(4.0+) |
| volatile-lfu | 有过期时间的 Key 中淘汰 LFU |
| allkeys-random | 随机淘汰 |
| volatile-random | 有过期时间的 Key 中随机淘汰 |
| volatile-ttl | 淘汰即将过期的 Key |
内存分析
# 查看内存使用
INFO memory
# 分析大 Key
redis-cli --bigkeys
# 内存采样分析(4.0+)
MEMORY USAGE key
MEMORY DOCTOR过期键删除策略
- 惰性删除:访问时检查是否过期
- 定期删除:每 100ms 随机检查一批 Key
七、事务与管道
事务
MULTI # 开启事务
SET key1 value1
SET key2 value2
INCR counter
EXEC # 执行事务
# 或 DISCARD # 取消事务注意:Redis 事务不支持回滚!
WATCH 乐观锁
WATCH balance
val = GET balance
val = val - 100
MULTI
SET balance $val
EXEC # 如果 balance 被其他客户端修改,EXEC 返回 nilPipeline 管道
批量发送命令,减少网络往返。
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}
List<Object> results = pipeline.syncAndReturnAll();八、发布订阅
基本使用
# 订阅频道
SUBSCRIBE channel1 channel2
# 发布消息
PUBLISH channel1 "Hello"
# 模式订阅
PSUBSCRIBE news.*Java 实现
// 订阅者
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
System.out.println(channel + ": " + message);
}
}, "channel1");
// 发布者
jedis.publish("channel1", "Hello World");九、Stream(Redis 5.0+)
Stream 是 Redis 5.0 引入的消息队列数据结构。
基本操作
# 添加消息
XADD mystream * field1 value1 field2 value2
# 读取消息
XREAD COUNT 10 STREAMS mystream 0
# 创建消费者组
XGROUP CREATE mystream mygroup 0
# 消费者读取
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
# 确认消息
XACK mystream mygroup 1526569495631-0与 List 对比
| 特性 | List | Stream |
|---|---|---|
| 消息持久化 | ✅ | ✅ |
| 消费者组 | ❌ | ✅ |
| 消息确认 | ❌ | ✅ |
| 消息回溯 | ❌ | ✅ |
| 阻塞读取 | ✅ | ✅ |
十、性能优化
1. 合理使用数据结构
# 小数据量用 ziplist
hash-max-ziplist-entries 512
hash-max-ziplist-value 642. 避免大 Key
- String 类型 < 10KB
- Hash/List/Set/ZSet 元素数 < 5000
3. 批量操作
# 使用 MSET/MGET
MSET key1 val1 key2 val2 key3 val3
MGET key1 key2 key3
# 使用 Pipeline4. 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100); // 最大连接数
config.setMaxIdle(20); // 最大空闲连接
config.setMinIdle(5); // 最小空闲连接
config.setMaxWaitMillis(3000); // 最大等待时间
config.setTestOnBorrow(true); // 借用时测试连接5. 慢查询日志
# 配置
slowlog-log-slower-than 10000 # 超过10ms记录
slowlog-max-len 128 # 最多保存128条
# 查看
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET