Redis进阶
目录
Redis进阶
Redis7 —— 超详细操作演示!_redis linux-CSDN博客
主从配置
配置
解压后复制三份:Master\Slave1\Slave2
里面配置修改redis.windows.conf
# Master
port 6379
# Slave1
port 6380
slaveof 127.0.0.1 6379
# Slave2
port 6381
slaveof 127.0.0.1 6379

# 服务器开启:
cd Redis-x64-3.0.504 - Master
redis-server redis.windows.conf
# 客户端开启:
cd Redis-x64-3.0.504 - Master
redis-cli -p 6379
只配置主从结点:
-
数据备份:从节点有主节点的完整数据副本。
-
读写分离:读操作可以分发到从节点,减轻主节点压力。
- 一般情况下,主节点可以读写,从节点只能读
-
主节点宕机的情况下:
-
如果应用程序只配置了连接主节点,所有读写都不可用,配置了连接从节点,则可以在从节点读,但是都不能写了。
-
没有哨兵只能一直派人值守,发现主节点宕机,手动选择新的主结点,否则一直无法写。
-
这就是为什么需要哨兵:它自动完成所有这些决策和操作,实现秒级故障转移!
-
Jedis读写分离工具类 RedisReadWriteSplit.java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class RedisReadWriteSplit {
private JedisPool masterPool; // 主节点连接池 - 写操作
private List<JedisPool> slavePools; // 从节点连接池列表 - 读操作
private Random random = new Random();
public RedisReadWriteSplit() {
// 连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(50); // 最大连接数
poolConfig.setMaxIdle(10); // 最大空闲连接
poolConfig.setMinIdle(5); // 最小空闲连接
poolConfig.setMaxWaitMillis(3000); // 最大等待时间
// 主节点连接池(写操作)
masterPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 3000, "123456");
// 从节点连接池(读操作)
slavePools = new ArrayList<>();
slavePools.add(new JedisPool(poolConfig, "127.0.0.1", 6380, 3000, "123456"));
slavePools.add(new JedisPool(poolConfig, "127.0.0.1", 6381, 3000, "123456"));
}
/**
* 写操作 - 使用主节点
*/
public String set(String key, String value) {
try (Jedis jedis = masterPool.getResource()) {
return jedis.set(key, value);
}
}
public Long del(String key) {
try (Jedis jedis = masterPool.getResource()) {
return jedis.del(key);
}
}
public Long incr(String key) {
try (Jedis jedis = masterPool.getResource()) {
return jedis.incr(key);
}
}
/**
* 读操作 - 随机选择从节点
*/
public String get(String key) {
JedisPool slavePool = getRandomSlavePool();
try (Jedis jedis = slavePool.getResource()) {
return jedis.get(key);
}
}
public Boolean exists(String key) {
JedisPool slavePool = getRandomSlavePool();
try (Jedis jedis = slavePool.getResource()) {
return jedis.exists(key);
}
}
/**
* 随机获取一个从节点连接池
*/
private JedisPool getRandomSlavePool() {
int index = random.nextInt(slavePools.size());
return slavePools.get(index);
}
/**
* 关闭所有连接池
*/
public void close() {
if (masterPool != null) {
masterPool.close();
}
for (JedisPool pool : slavePools) {
if (pool != null) {
pool.close();
}
}
}
}
// 测试类
public class RedisDemo {
public static void main(String[] args) {
RedisReadWriteSplit redis = new RedisReadWriteSplit();
try {
// 1. 写操作演示 - 使用主节点
System.out.println("=== 写操作演示 ===");
redis.set("user:1001", "张三");
redis.set("user:1002", "李四");
redis.incr("visit_count");
System.out.println("数据写入成功");
// 2. 读操作演示 - 使用从节点
System.out.println("\n=== 读操作演示 ===");
for (int i = 0; i < 5; i++) {
String user1 = redis.get("user:1001");
String user2 = redis.get("user:1002");
String count = redis.get("visit_count");
System.out.println(String.format("读取 %d: user1001=%s, user1002=%s, count=%s",
i + 1, user1, user2, count));
}
// 3. 更多操作示例
System.out.println("\n=== 更多操作示例 ===");
// Hash操作
redis.set("user:1001:profile", "{\"name\":\"张三\",\"age\":25}");
// List操作
try (Jedis jedis = redis.getMasterResource()) {
jedis.lpush("news:latest", "新闻1", "新闻2", "新闻3");
}
// 从从节点读取List
String latestNews = redis.getSlaveResource().lpop("news:latest");
System.out.println("最新新闻: " + latestNews);
} finally {
redis.close();
}
}
}
与Redis Cluster对比
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.HashSet;
import java.util.Set;
public class RedisClusterClient {
private JedisCluster jedisCluster;
// 初始化连接
public void init() {
try {
// 1. 连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200); // 最大连接数
poolConfig.setMaxIdle(50); // 最大空闲连接
poolConfig.setMinIdle(10); // 最小空闲连接
poolConfig.setMaxWaitMillis(3000); // 获取连接最大等待时间
poolConfig.setTestOnBorrow(true); // 获取连接时测试
poolConfig.setTestOnReturn(true); // 归还连接时测试
poolConfig.setTestWhileIdle(true); // 空闲时测试
// 2. Redis Cluster 节点
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.43.212", 7001)); // 主节点
nodes.add(new HostAndPort("192.168.43.212", 7002)); // 从节点
nodes.add(new HostAndPort("192.168.43.213", 7001)); // 主节点
nodes.add(new HostAndPort("192.168.43.213", 7002)); // 从节点
nodes.add(new HostAndPort("192.168.43.214", 7001)); // 主节点
nodes.add(new HostAndPort("192.168.43.214", 7002)); // 从节点
// 3. 创建 JedisCluster
// 参数:节点集合, 连接超时, 读取超时, 最大重试次数, 密码, 连接池配置
jedisCluster = new JedisCluster(
nodes,
2000, // connectionTimeout
2000, // soTimeout
3, // maxAttempts 重试次数
"redis123", // password (没有密码传null)
poolConfig
);
// 测试连接
testConnection();
} catch (Exception e) {
throw new RuntimeException("Redis Cluster 连接失败", e);
}
}
private void testConnection() {
try {
String pong = jedisCluster.ping();
System.out.println("Redis Cluster 连接成功: " + pong);
} catch (Exception e) {
throw new RuntimeException("Redis Cluster 连接测试失败", e);
}
}
// 基本操作示例
public void basicOperations() {
try {
// 设置值
jedisCluster.set("user:1001:name", "张三");
jedisCluster.set("user:1001:age", "25");
// 获取值
String name = jedisCluster.get("user:1001:name");
System.out.println("用户名: " + name);
// 哈希操作
jedisCluster.hset("product:2001", "name", "iPhone");
jedisCluster.hset("product:2001", "price", "6999");
// 列表操作
jedisCluster.lpush("queue:orders", "order1", "order2", "order3");
// 设置过期时间
jedisCluster.expire("user:1001:name", 3600);
// 批量操作(注意:批量操作需要在同一个slot)
jedisCluster.mset(
"{user:1001}:email", "zhangsan@example.com",
"{user:1001}:phone", "13800138000"
);
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭连接
public void close() {
if (jedisCluster != null) {
try {
jedisCluster.close();
System.out.println("Redis Cluster 连接已关闭");
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 获取连接实例
public JedisCluster getJedisCluster() {
return jedisCluster;
}
}
// 主节点A(槽位0-5000)
// └─ 从节点A1(复制A的数据)
// 主节点B(槽位5001-10000)
// └─ 从节点B1(复制B的数据)
// 主节点C(槽位10001-16383)
// └─ 从节点C1(复制C的数据)
Redis Cluster 的数据分布原理
每个服务不是全量的数据,只是每个里面存一部分
public class RedisClusterDataDistribution {
public static void main(String[] args) {
JedisCluster cluster = new JedisCluster(nodes);
// 假设有3个主节点的Cluster:
// 节点A (7000): 只存储 slot 0-5460 的数据
cluster.set("key_in_slot_100", "value"); // slot 100 -> 节点A
cluster.set("key_in_slot_3000", "value"); // slot 3000 -> 节点A
// 节点B (7001): 只存储 slot 5461-10922 的数据
cluster.set("key_in_slot_6000", "value"); // slot 6000 -> 节点B
cluster.set("key_in_slot_8000", "value"); // slot 8000 -> 节点B
// 节点C (7002): 只存储 slot 10923-16383 的数据
cluster.set("key_in_slot_12000", "value"); // slot 12000 -> 节点C
cluster.set("key_in_slot_15000", "value"); // slot 15000 -> 节点C
// 每个节点只有 1/3 的数据!
}
}
数据分布可视化
Cluster 数据分布:
节点A (7000) 节点B (7001) 节点C (7002)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Slot: 0 │ │ Slot: 5461 │ │ Slot: 10923 │
│ ... │ │ ... │ │ ... │
│ Slot: 5460 │ │ Slot: 10922 │ │ Slot: 16383 │
│ │ │ │ │ │
│ 数据量: 33% │ │ 数据量: 33% │ │ 数据量: 33% │
└─────────────┘ └─────────────┘ └─────────────┘
对比主从复制的数据分布:
主节点 (6379) 从节点1 (6380) 从节点2 (6381)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 所有数据 │ │ 所有数据 │ │ 所有数据 │
│ 100% │ │ 100% │ │ 100% │
│ User:1001 │ │ User:1001 │ │ User:1001 │
│ Order:2001 │ │ Order:2001 │ │ Order:2001 │
│ Product:3001│ │ Product:3001│ │ Product:3001│
└─────────────┘ └─────────────┘ └─────────────┘
优缺点总结
Redis Cluster 的优点:
- ✅ 超大容量:数据分片,突破单机内存限制
- ✅ 写性能扩展:写入可以分散到多个节点
- ✅ 读性能扩展:读取也可以分散到多个节点
- ✅ 真正的水平扩展:可以轻松增加节点
Redis Cluster 的缺点:
- ❌ 架构复杂:配置和维护更复杂
- ❌ 跨slot操作限制:不支持跨节点的复杂操作
- ❌ 客户端要求:需要支持Cluster的客户端
- ❌ 数据迁移成本:扩容时需要数据重新分片
主从复制的优点:
- ✅ 架构简单:配置和维护容易
- ✅ 全量数据:每个节点都有完整数据
- ✅ 操作灵活:支持所有Redis命令
- ✅ 客户端兼容:任何Redis客户端都可以用
主从复制的缺点:
- ❌ 容量限制:总容量受单节点内存限制
- ❌ 写性能瓶颈:所有写操作都在主节点
- ❌ 存储浪费:数据全量复制,存储效率低
选择建议
用 Redis Cluster 当:
- 数据量 > 单机内存的70%
- 需要真正的读写水平扩展
- 业务可以接受跨slot操作限制
用 主从+哨兵 当:
- 数据量 < 单机内存的70%
- 主要是读多写少的场景
- 需要简单的架构和运维
- 需要使用所有Redis命令
哨兵配置
# 端口26379
port 26379
# 核心配置:监控名为mymaster的主节点,
# mymaster:给这个Redis集群起的名字,可以自定义
#192.168.43.212 6379:初始主节点的地址(哨兵启动时去这里找主节点)
#2:法定人数 - 至少需要2个哨兵同意才能判定主节点客观下线
sentinel monitor mymaster 192.168.43.212 6379 2
# 哨兵用这个密码去连接Redis主节点和从节点进行监控。
sentinel auth-pass mymaster 111111 # 主节点没密码的话,不用配置这个
# 主节点多少毫秒无响应后,哨兵将其标记为"主观下线"。
sentinel down-after-milliseconds mymaster 5000
# 故障转移的超时时间,如果在这个时间内没完成故障转移,会尝试新的故障转移。
sentinel failover-timeout mymaster 60000
# 故障转移后,同时向新主节点同步数据的从节点数量。
sentinel parallel-syncs mymaster 1
# 告诉其他哨兵"我自己的地址和端口",用于哨兵之间的自动发现
sentinel announce-ip 192.168.43.212
sentinel announce-port 26379
# 启动
cd Redis-x64-3.0.504 - Sentinel1
redis-server redis.windows.conf --sentinel
Redis 哨兵的自动恢复机制
// 初始状态
主节点A: slave-priority 50 (192.168.43.212)
从节点B: slave-priority 100 (192.168.43.213)
从节点C: slave-priority 200 (192.168.43.214)
// 步骤1:主节点A宕机
主节点A宕机 → 哨兵开始故障转移
↓
比较从节点优先级:
- 节点B: priority 100
- 节点C: priority 200
↓
选择优先级更高的节点B(数字越小优先级越高!)
↓
节点B成为新主节点
// 步骤2:原主节点A恢复
原主节点A恢复启动
↓
哨兵检测到节点A在线
↓
发现集群已有主节点B
↓
强制节点A成为节点B的从节点
↓
节点A开始同步节点B的数据
// 注意:原主节点恢复后只会成为从节点,不会自动恢复成主节点。
// 1. 避免频繁的主节点切换
// 2. 保持系统稳定性:节点A:刚刚从故障中恢复,稳定性未知
// 3. 防止数据一致性问题
Jedis 内置的哨兵支持
代码是硬编码的,无法感知Redis主从的故障切换。当哨兵完成主从切换后,应用程序仍然在尝试连接旧的、已经宕机的主节点,导致服务不可用。
// 主节点连接池(写操作)
masterPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 3000, "123456");
// 从节点连接池(读操作)
slavePools = new ArrayList<>();
slavePools.add(new JedisPool(poolConfig, "127.0.0.1", 6380, 3000, "123456"));
slavePools.add(new JedisPool(poolConfig, "127.0.0.1", 6381, 3000, "123456"));
// 我用这个来进行读写,假如6379宕机了,虽然哨兵选出了新的6380来使用,但是我这还是6379不能用
需要让应用程序能够动态地发现当前的主节点。
核心思想: 应用程序连接的是哨兵集群,而不是具体的Redis节点。通过哨兵来查询当前有效的主节点地址。
// 例子1
public class RedisPoolUtils {
private static Jedis jedis;
private static JedisSentinelPool jedisSentinelPool;
static{
try {
JedisPoolConfig config = new JedisPoolConfig();
//最大空闲连接数, 默认8个
config.setMaxIdle(8);
//最大连接数, 默认8个
config.setMaxTotal(8);
//最小空闲连接数, 默认0
config.setMinIdle(0);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(3000);
//在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false
config.setTestOnBorrow(true);
//redis服务器列表
Set<String> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("192.168.43.212", 26379).toString());
sentinels.add(new HostAndPort("192.168.43.213", 26379).toString());
sentinels.add(new HostAndPort("192.168.43.214", 26379).toString());
//初始化连接池
jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, config, "111111");
// 从池中获取一个Jedis对象
jedis = jedisSentinelPool.getResource();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 例子2
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;
public class RedisSentinelExample {
private static JedisSentinelPool sentinelPool;
static {
// 1. 配置哨兵集群地址
Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381"); // 添加所有哨兵节点
// 2. 主节点名称 (在sentinel.conf中配置的 `mymaster`)
String masterName = "mymaster";
// 3. 创建基于哨兵的连接池
sentinelPool = new JedisSentinelPool(masterName, sentinels, "123456");
}
// 写操作 - 自动获取当前主节点连接
public void set(String key, String value) {
try (Jedis jedis = sentinelPool.getResource()) {
jedis.set(key, value);
}
}
// 读操作 - 自动路由到从节点 (JedisSentinelPool内部处理)
public String get(String key) {
try (Jedis jedis = sentinelPool.getResource()) {
return jedis.get(key);
}
}
}
工作原理:
JedisSentinelPool在初始化时,会连接你提供的哨兵节点。- 通过哨兵查询当前
mymaster对应的真正主节点IP和端口。 - 内部维护一个到当前主节点的连接池。
- 当哨兵进行主从切换时,
JedisSentinelPool能够自动感知到,并更新内部连接池到新的主节点。 - 对于读操作,默认也是从主节点读(保证强一致性),如果你希望从从节点读,需要额外的配置(见下文)。
扩展
-
JedisSentinelPool + 配置实现读写分离,怎么个配置和使用问deepseek!
-
当然还可以自定义监听哨兵事件(更灵活,但复杂)。具体问deepseek!
redis 客户端查看信息
# 启动 连接主redis服务
cd Redis-x64-3.0.504 - Master
redis-cli -p 6379
# 查看主从信息
info replication
# 查看哨兵信息
# 连接哨兵
cd Redis-x64-3.0.504 - Sentinel1
redis-cli -p 26379
# 或者连接哨兵
redis-cli.exe -h 127.0.0.1 -p 26379
# 查看监控的主节点信息
127.0.0.1:26379> sentinel master mymaster
# 查看所有从节点信息
127.0.0.1:26379> sentinel slaves mymaster
# 查看其他哨兵节点信息
127.0.0.1:26379> sentinel sentinels mymaster
# 获取当前主节点地址
127.0.0.1:26379> sentinel get-master-addr-by-name mymaster
# 查看哨兵配置
127.0.0.1:26379> sentinel config mymaster
完整配置1主2从3哨兵流程
下载并解压
具体流程看:Redis - deyang - 博客园
下载并解压后得到:Redis-x64-3.0.504文件夹

文件夹内容:

复制1主2从3哨兵
复制6份Redis-x64-3.0.504,分别对应1主2从3哨兵

配置
redis.windows.conf配置
# 所有Redis-x64-3.0.504里面的redis.windows.conf配置 以下配置追加到redis.windows.conf
# 主节点的无需修改,就用port 6379
# Master
port 6379
# Slave1
port 6380
slaveof 127.0.0.1 6379
# Slave2
port 6381
slaveof 127.0.0.1 6379
# sentinel1
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass mymaster 111111
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel announce-ip 127.0.0.1
sentinel announce-port 26379
# sentinel2
port 26380
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass mymaster 111111
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel announce-ip 127.0.0.1
sentinel announce-port 26380
# sentinel3
port 26381
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass mymaster 111111
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel announce-ip 127.0.0.1
sentinel announce-port 26381
启动脚本
# master
cd Redis-x64-3.0.504 - Master
redis-cli -p 6379
# slave1
cd Redis-x64-3.0.504 - Slave1
redis-cli -p 6380
# sentinel
cd Redis-x64-3.0.504 - Sentinel1
redis-cli -p 26379
测试
# 启动对应客户端,查看对应信息
# 查看主从信息
info replication
# 查看主信息
sentinel master mymaster
# 查看所有从节点信息
sentinel slaves mymaster
# 查看其他哨兵节点信息
sentinel sentinels mymaster
# 获取当前主节点地址
sentinel get-master-addr-by-name mymaster
# 查看哨兵配置
sentinel config mymaster
# 校验:
# 1.主节点宕机,从节点自动变成主节点
# 2.原主节点恢复,自动变成从节点
# 主节点宕机测试,测试后会变成 6380变成主节点:
# 哨兵配置变化
port 26380
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel config-epoch mymaster 3
sentinel leader-epoch mymaster 3
sentinel known-slave mymaster 127.0.0.1 6381
# 6379的配置变化
slaveof 127.0.0.1 6380
# 6380的slaveof配置被移除
# 配置变化的原因:这是因为哨兵在故障转移后,会将新的主节点信息持久化到其配置文件中,重启后它会依据这个最新的配置来初始化并管理集群
浙公网安备 33010602011771号