【Redis】集群模式(三):高可用集群(Redis-Cluster)原理分析及搭建、扩容、缩容(基于Docker)

1.Redis-Cluster 介绍

Redis cluster 是 Redis 的分布式解决方案,在 3.0 版本推出后有效地解决了 redis 分布式方面的需求,在 3.0 之前为了解决容量高可用用方面的需求基本上只能通过客户端分片+redis sentinel或者代理(twemproxy、codis)方案解决、redis cluster非常优雅地解决了redis集群方面的问题。

Redis 集群是一个由多个主从节点群组成的分布式服务器群,需要将每个节点设置成集群模式,如下图:
在这里插入图片描述
Redis-Cluster 的特点:

  • 它具有复制、高可用和分片特性
  • Redis集群不需要 sentinel 哨兵也能完成节点移除和故障转移的功能
  • 这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)
  • redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单

2.Redis-Cluster原理

Redis-Cluster 原理概括的说就以下四点:

1)Redis Cluster 将所有数据根据 key 划分到 16384 个槽位(slots)中,每个节点(小集主从群)负责其中一部分槽位。槽位的信息存储于每个节点中。

2)当 Redis Cluster 的客户端来连接集群时(原则上只要连接到一个节点即可),它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点,避免了到服务器再进行计算与寻找的资源消耗。

3)若发生扩容缩容等,槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。

4)当主节点挂掉时,会发起选举让从节点成为主节点。

下面就逐一来看这四点…

2.1 槽定位算法

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

HASH_SLOT = CRC16(key) mod 16384

2.2 跳转重定向

当客户端向一个错误的节点发出了指令(常发生于扩容缩容后),该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个携带目标操作节点地址的特殊跳转指令:

  1. 告诉客户端去连这个节点去获取数据,客户端收到指令后除了跳转到正确的节点上去操作
  2. 同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表

2.3 选举策略

当 slave 发现自己的 master 变为 FAIL 状态时,便尝试进行 Failover,以期成为新的 master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

  1. slave 发现自己的 master 变为 FAIL,然后将自己记录的集群 currentEpoch 加一,并广播FAILOVER_AUTH_REQUEST 信息,表示请求成为新 Master。

    两点注意:

    1. 为了避免网络抖动造成的假死,master 无响应后会有一定时间的等待。
    2. 每个slave广播REQUEST并不是同时的,而是有一个延迟时间。DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
      其中,SLAVE_RANK 表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)
  2. 其他节点收到该信息,只有 master 响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

  3. 尝试 Failover 的 slave 收集FAILOVER_AUTH_ACK

  4. 超过半数的 Slave 变成新 Master,然后广播通知其他集群节点

    因此每个集群至少要3个slave,因为只有2个slave时,投票率=50%

另外,选举也是需要时间的,所以选举过程中可能造成数据丢失,但极少数据丢失redis是允许的。

2.4 网络抖动问题

真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。

为解决这种问题,RedisCluster 提供了一种选项cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换(数据的重新复制)。

3.搭建(docker)& 运维

3.1 搭建

接下来借助 docker 搭建一个三主三从的集群

1.下载redis镜像

docker pull redis:5

2.写redis.conf(关键)

# 端口 注意各个redis不同
port 7000

# 如果是yes,表示启用集群,否则以单例模式启动
cluster-enabled yes

# 保存集群节点自动持久化每次配置的改变,用户不可编辑,redis在启动的时候新读取它。
cluster-config-file nodes.conf

# 超时时间,集群节点不可用的最大时间。如果一个master节点不可到达超过了指定时间,则认为它失败了,要开始选举
# 注意,超时时间尽量设置一致,避免slave选举失败
cluster-node-timeout 15000

# 开启aof持久化
appendonly yes

# 关闭保护模式
protected-mode no

# 设置当前redis密码(可选),用于登录。为了方便暂时不设置密码
#requirepass 123456
# 设置集群间节点访问密码,用于创建集群,各个节点需相同,在启动集群时要带上
#masterauth 123456

新建文件夹,并将写好的redis.conf分别放入,注意修改每个配置文件的port

在这里插入图片描述

3.启动所有redis容器(docker run)

依次启动这6个redis,注意修改对应端口号

docker run  -d --net=host --name redis-7000 
-v /usr/local/redis/redis-7000/data:/data 
-v /usr/local/redis/redis-7000/redis.conf:/usr/local/etc/redis/redis.conf   
redis:5 redis-server /usr/local/etc/redis/redis.conf

/*
  	-net=host:为了方便我们直接将这些容器的网络设置为host,即共用主机网络
	-v:挂载,挂载配置文件与存放数据的目录
	另外,启动redis时要指定配置文件位置
*/

4.创建集群(–cluster create)

docker exec -it redis-7000 bash  

redis-cli  --cluster -replicas 1  
--cluster create 
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
127.0.0.1:7004
127.0.0.1:7005

/*
	-a 指定连接密码,若配置时未设置则不用
	--cluster-replicas 指定每个master下有几个slave,会自动进行分配
	--cluster create 指定每个redis的IP:port,这里因为是host网络,直接127.0.0.1:7000即可,
		             若使用的默认bridge则需要查出各容器ip:6379
*/

注:另外redis-cli --cluster还有这些命令

5.校验

进入一个容器,然后进入redis客户端(cluster nodes)

redis-cli -p 7000 -c // 若不指定端口则6379,-c表集群模式
  • cluster info:查看集群信息
    在这里插入图片描述

  • cluster nodes:查看节点列表,包括了每个节点id,ip,master/slave ,所分槽点
    在这里插入图片描述

  • 到挂载的data目录下,可以看到持久化文件rdb,aof,以及配置节点信息文件nodes.conf(如下)
    在这里插入图片描述在这里插入图片描述

6.关闭及重启

关闭时需要对每台redis逐一关闭

redis-cli -c -p 7000 shutdown

重启时,直接将各个节点重启就ok,不用再create

3.2 扩容

1.启动新redis节点(docker run)

新建两个文件夹,放入redis.conf;

启动命令如下,注意启动第二个时修改端口号

docker run  -d --net=host --name redis-7006
-v /usr/local/redis/redis-7006/data:/data 
-v /usr/local/redis/redis-7006/redis.conf:/usr/local/etc/redis/redis.conf   
redis:5 redis-server /usr/local/etc/redis/redis.conf

在这里插入图片描述在这里插入图片描述

2.将新节点加入集群(–cluster add-node)

进入任意redis容器中,将启动的两个节点加入集群(add-node),然后再查看所有结点进行校验

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000  // 127.0.0.1是为了通过该节点连接到集群
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000

在这里插入图片描述
在这里插入图片描述

3.为主节点分配槽位(–cluster reshard)

新加入的节点是没有槽位的,需要手动分配

// 对哪个集群要重分配节点,写任意一个集群中的节点
redis-cli --cluster reshard 127.0.0.1:7000 

然后再根据提示输入:

// 要分配的槽位数
How many slots do you want to move (from 1 to 16384)? 600
// 要给哪个节点分 id
What is the receiving node ID? eb57a5700ee6f9ff099b3ce0d03b1a50ff247c3c
// 选择分配类型,all:已有的均分 done:指定节点中分配
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node 1:all
// 确认执行策略
Do you want to proceed with the proposed reshard plan (yes/no)? yes

在这里插入图片描述

4.为主节点连接从节点(cluster replicate)

进入从节点的容器,并进入redis客户端

127.0.0.1:7007> cluster replicate 47032...065 // 主节点id

在这里插入图片描述

3.3 缩容

1.删除从节点(–cluster del-node)

redis-cli --cluster del-node 127.0.0.1:7007

2.主节点槽位卸载(–cluster reshard)

槽位卸载实际就是将已有的槽点分配给别的节点

redis-cli --cluster reshard 127.0.0.1:7000
// 7006共有600个槽位
How many slots do you want to move (from 1 to 16384)? 600
// 这里是需要把数据移动到哪?7001的主节点id
What is the receiving node ID? 752...f2a
// 这里是需要数据源,也就是我们的7006节点id
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node 1:470...065
Source node 2:done
// 这里直接输入done 开始生成迁移计划)
 ... ...
Do you want to proceed with the proposed reshard plan (yes/no)? Yes

3.删除主节点(–cluster del-node)

redis-cli --cluster del-node 127.0.0.1:7006

4.关闭相应容器(docker stop)

docker stop redis-7006
docker stop redis-7007

4.Jedis连接

<dependency>     
    <groupId>redis.clients</groupId>     
    <artifactId>jedis</artifactId>     
    <version>2.9.0</version>
</dependency>
  •  
public class RedisCluster 
{
    public static void main(String[] args) throws IOException
    {
        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7000));
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7001));
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7002));
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7003));
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7004));
        jedisClusterNode.add(new HostAndPort("39.105.136.~", 7005));
        
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(10);
        config.setTestOnBorrow(true);
        // connectionTimeout:指的是连接一个url的连接等待时间
        // soTimeout:指的是连接上一个url,获取response的返回等待时间
        JedisCluster jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, config);
        System.out.println(jedisCluster.set("student", "zhangsan"));
        System.out.println(jedisCluster.set("age", "19"));
        
        System.out.println(jedisCluster.get("student")); // zhangsan
        System.out.println(jedisCluster.get("age")); // 19
        
        jedisCluster.close();
    }
}

在连接集群的时候原则上只用配置一个节点信息就可以访问到所有结点,因为每个节点都存储了所有结点的信息,但防止该节点down机导致连接失败,最好还是将所有结点信息都配置上。

客户端也会存储一份结点槽位的分配信息,可以传到服务端时直接到正确的槽位。但若集群发生扩容缩容,服务端会进行跳转重定向,客户端也会重新备份槽点信息。

猜你喜欢

转载自blog.csdn.net/qq_33762302/article/details/114275010