Redis Cluster高可用集群
概述
redis cluster集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特
性。
Redis cluster集群不需要sentinel哨兵节点也能完成节点移除和故障转移的功能。需要将每个节点
设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到
1000节点。
redis cluster集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单,其架构如下图所示:
集群的搭建
redis集群的搭建分两种方法,一种是原生的方法搭建,一种是使用redis提供的rb脚本搭建。
原生搭建
这里搭建四个节点的集群,两个主节点,两个从节点,首先还是配置每个redis节点,同样也是通过redis.conf文件配置。
配置节点
各个节点大部分配置还是和之前主从模式的节点配置差不多。
这里绑定的ip是我使用的虚拟机的ip,这样我的本机也是可以访问的
还是尽量减少RDB持久化的发生
同样如果我们配置了redis的密码还是要设置密码,不过不需要配置主机节点
接着开启集群模式
配置它的集群信息文件
打开这个配置
这个属性配置为no,这个配置的意思是在某一个节点出现问题挂掉的时候,如果配置no这个节点不可用,但其他节点可用,如果配置yes,代表整个集群都不可用,直到故障转移结束。
启动节点
- 启动节点
启动方式还是和普通节点的启动一样
- 节点互通
这时集群其实还没有搭建成功,此时这些几点之间还没有相互通信,先连接其中一个客户端
我们发现集群节点中只有自己
通过下面的命令连通其他的节点
cluster meet ip port
这里的连接时双向的,互通的,不需要各自和各自的节点手动连接,只要节点之间有其他节点相连都可以自动相连。现在各个节点之间都可以自由通信了,不过,现在的集群还没有搭建好,还没有分配槽点。
- 指派槽点
通过下面的节点指派槽点,redis总共有16384个槽点,这些槽点决定了那些数据存在哪些节点中,我们总共有两个主机节点,所以平分就是每个主机节点8192个槽点。这里如果手动分配那会很慢,可以通过脚本分配。
脚本如下
这里将槽点平分给7000和7001这两个节点
- 分配主从
上面我们看到只存在主机节点,没有从节点,所以我们需要分配主从,我们需要通过从机的客户端指定它的主机节点,这里指定的是节点的id,就是nodes信息里最前面的一串字符。
cluster replicate [nodeid]
现在我们发现主从已经分配好了
- 打开客户端
现在我们可以通过集群的方式打开客户端,只需要在最后加上-c
就可以了
现在redis集群就搭建好了
使用redis提供的脚本
这里我们会搭建一个6个节点的集群,3主3从,而且在5.0之后redis提供了另一种搭建的办法,就不需要借助rb了,这里也是主要介绍的也是这种方式。
首先配置还是和之前一摸一样,就不再过多介绍了,我们直接从集群的搭建开始介绍。
这里有两种方式:
- 5.0之前redis提供的rb脚本,不过这种方式需要首先拥有一个rb环境,安装rb,然后通过rb脚本搭建集群
(1)cd /usr/local/redis3/src
(2)./redis-trib.rb create --replicas 1 127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003 127.0.0.1:9004 127.0.0.1:9005
- 5.0之后redis提供了另外的方式搭建集群,使用
/usr/local/bin/redis-cli --cluster create 192.168.0.104:7000 192.168.0.104:7001 192.168.0.104:7002 192.168.0.104:7003 192.168.0.104:7004 192.168.0.104:7005 --cluster-replicas 1
搭建,下面也会使用此方式搭建集群。
首先我们已经配置好了6个节点的配置文件,然后启动每一个节点。
只需要执行下面的命令就可以直接搭建集群(5.0以后),我们可以在帮助中查看这个命令的各种参数的说明。
/usr/local/bin/redis-cli -h 192.168.123.19 -p 8000 -a 123456 --cluster help
我们搭建集群需要使用的命令就是create命令,这里需要解释的是最后--cluster-replicas 1
这个参数代表的是集群中主从节点的比率,1代表有多少主机就有多少从机,这里就是3主3从。
/usr/local/bin/redis-cli -h 192.168.123.19 -p 8000 -a 123456 --cluster create 192.168.123.19:8000 192.168.123.19:8001 192.168.123.19:8002 192.168.123.19:8003 192.168.123.19:8004 192.168.123.19:8005 --cluster-replicas 1
这时集群就已经搭建好了
集群伸缩
集群扩容
对于已经搭建好的集群我们可能还需要添加节点,这时我们就需要手动对这个集群扩容,下面我会演示对一个集群添加一个主节点和它的一个从节点。
同样有两种方式实现扩容,一种是原生的,一种是通过redis-cli的命令扩容。
- 原生的语法:
cluster meet ip port
- redis-cli的命令:
add-node 新节点ip 端口 已存在节点ip 端口
下面是演示的通过redis-cli的命令实现集群扩容
首先还是需要其他需要添加的节点
把节点加入集群
现在我们需要将从节点加入集群并且分配主从,这里可以采用先将从节点加入集群再分配集群,也可以通过redis-cli命令直接将从节点加入集群并一起分配主从,分配主从前面已经介绍了原生的方法,这里介绍redis-cli命令直接分配主从节点。
add-node 新节点ip 端口 已存在节点ip 端口 --cluster-slave --cluster-master-id masterID
迁移槽和数据
我们可以动态迁移槽和数据
- 首先开始槽迁移
/usr/local/bin/redis-cli --cluster reshard 192.168.123.19:8000
- 指定迁移的计划
首先是需要分配多少槽
选择接收的节点
接着就是输入槽点的源节点,我们分配的槽点就是由这些节点得来的,这里我们可以输入一个或多个源槽位的节点ID,输入完了就输入done表示结束,如果我们需要从所有其他的节点拿到槽位可以直接输入all,这里都是平均拿到槽位。
集群缩容
下线迁移槽
可以通过下面的命令将某个节点的一些槽位迁移给其他的节点
redis-cli --cluster reshard --cluster-from 要迁出节点ID --cluster-to 接收槽节点ID --cluster-slots 迁出槽数量 已存在节点ip 端口
移除节点
可以通过下面的命令将节点移出集群,注意这里不仅会将节点移出集群而且还会关闭这个redis节点
redis-cli --cluster del-node 已存在节点ip:端口 要删除的节点ID
故障转移
redis集群的故障转移和哨兵模式下的故障转移类似,下面是故障转移的过程:
- 故障发现: 通过ping/pong消息实现故障发现(不依赖sentinel)
- 故障恢复:
- 检查从节点的资格,每个从节点检查与主节点的断开时间,超过
cluster-node-timeout
*cluster-replica-validity-factor
时间取消资格 - 选择偏移量最大的,替换主节点
- 撤销以前主节点的槽位,给新的主节点
- 向集群广播消息,表明已经替换了故障节点
- 检查从节点的资格,每个从节点检查与主节点的断开时间,超过
集群客户端
smart客户端
-
从集群中选取一个可运行节点,使用cluster slots初始化槽和节点映射。
-
将cluster slots的结果映射到本地,为每个节点创建jedispool
-
准备执行命令
public class TestRedisCluster {
public static void main(String[] args) {
Logger logger= LoggerFactory.getLogger(TestRedisCluster.class);
Set<HostAndPort> nodesList=new HashSet<>();
nodesList.add(new HostAndPort("192.168.0.104",7000));
nodesList.add(new HostAndPort("192.168.0.104",7001));
nodesList.add(new HostAndPort("192.168.0.104",7002));
nodesList.add(new HostAndPort("192.168.0.104",7003));
nodesList.add(new HostAndPort("192.168.0.104",7004));
nodesList.add(new HostAndPort("192.168.0.104",7005));
// Jedis连接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空闲连接数, 默认8个
jedisPoolConfig.setMaxIdle(200);
// 最大连接数, 默认8个
jedisPoolConfig.setMaxTotal(1000);
//最小空闲连接数, 默认0
jedisPoolConfig.setMinIdle(100);
// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
jedisPoolConfig.setMaxWaitMillis(3000); // 设置2秒
//对拿到的connection进行validateObject校验
jedisPoolConfig.setTestOnBorrow(false);
JedisCluster jedisCluster=new JedisCluster(nodesList,jedisPoolConfig);
System.out.println(jedisCluster.mset("k1", "v1", "k2", "v2", "k3", "v3"));
System.out.println(jedisCluster.mget("k1","k2", "k3" ));
// while (true) {
// try {
// String s = UUID.randomUUID().toString();
// jedisCluster.set("k" + s, "v" + s);
// System.out.println(jedisCluster.get("k" + s));
// Thread.sleep(1000);
// }catch (Exception e){
// logger.error(e.getMessage());
// }finally {
//// if(jedisCluster!=null){
//// jedisCluster.close();
//// }
// }
// }
}
}
下面是jedis集群客户端可能出现的问题:
- moved重定向:指我们发送命令时,会对发送的key进行crc16算法,得到一个数字,然而我们连接的客户端并不是管理这个数字的范围,所以会返回错误并告诉你此key应该对应的槽位,然后客户端需要捕获此异常,重新发起请求到对应的槽位。
- asx重定向:指在我们送发命令时,对应的客户端正在迁移槽位中,所以此时我们不能确定这个key是还在旧的节点中还是新的节点中。
- 如果我们使用mset之类的API实现批量操作的时候可能出现问题,因为如果我们操作的数据处于不同的节点中,就会报错。
下面是jedis集群客户端的scan操作的API
public class TestRedisClusterScan {
public static void main(String[] args) {
Logger logger= LoggerFactory.getLogger(TestRedisCluster.class);
Set<HostAndPort> nodesList=new HashSet<>();
nodesList.add(new HostAndPort("192.168.0.104",7000));
nodesList.add(new HostAndPort("192.168.0.104",7001));
nodesList.add(new HostAndPort("192.168.0.104",7002));
nodesList.add(new HostAndPort("192.168.0.104",7003));
nodesList.add(new HostAndPort("192.168.0.104",7004));
nodesList.add(new HostAndPort("192.168.0.104",7005));
// Jedis连接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空闲连接数, 默认8个
jedisPoolConfig.setMaxIdle(200);
// 最大连接数, 默认8个
jedisPoolConfig.setMaxTotal(1000);
//最小空闲连接数, 默认0
jedisPoolConfig.setMinIdle(100);
// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
jedisPoolConfig.setMaxWaitMillis(3000); // 设置2秒
//对拿到的connection进行validateObject校验
jedisPoolConfig.setTestOnBorrow(false);
JedisCluster jedisCluster=new JedisCluster(nodesList,jedisPoolConfig);
int hello = JedisClusterCRC16.getCRC16("hello");
System.out.println(hello);
System.out.println(hello%16384);
Jedis connectionFromSlot = jedisCluster.getConnectionFromSlot(hello);
System.out.println(connectionFromSlot);
Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
Set<Map.Entry<String, JedisPool>> entries = clusterNodes.entrySet();
Iterator<Map.Entry<String, JedisPool>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, JedisPool> next = iterator.next();
String key = next.getKey();
JedisPool jedisPool = clusterNodes.get(key);
System.out.println(key+"--"+jedisPool);
}
}
}
来源:CSDN
作者:ww0peo
链接:https://blog.csdn.net/qq_35262405/article/details/103207005