redis · 2022-02-22 0

docker 搭建 redis 6.0 cluster 集群

概述

实现高可用,并且有分区功能

实现监控和自动故障恢复功能

一、创建redis容器

1.拉取镜像

docker pull redis:6.0.10

2.创建容器

创建 6 个 docker 容器

docker run -d --privileged=true -p 6381:6379 --name redis-6381 redis:6.0.10 redis-server /data/redis.conf
docker run -d --privileged=true -p 6382:6379 --name redis-6382 redis:6.0.10 redis-server /data/redis.conf
docker run -d --privileged=true -p 6383:6379 --name redis-6383 redis:6.0.10 redis-server /data/redis.conf
docker run -d --privileged=true -p 6384:6379 --name redis-6384 redis:6.0.10 redis-server /data/redis.conf
docker run -d --privileged=true -p 6385:6379 --name redis-6385 redis:6.0.10 redis-server /data/redis.conf
docker run -d --privileged=true -p 6386:6379 --name redis-6386 redis:6.0.10 redis-server /data/redis.conf

此时,是启动失败的,因为容器内没有 redis 配置文件

3.修改配置

修改 redis.conf 如下,支持集群

# bind 127.0.0.1
port 6379
cluster-enabled yes
cluster-config-file "node-6379.conf"
pidfile /var/run/redis_6379.pid
logfile ""
dbfilename dump.rdb
dir ./
protected-mode no

把 redis.conf 复制到每个节点

docker cp redis.conf redis-6381:/data/
docker cp redis.conf redis-6382:/data/
docker cp redis.conf redis-6383:/data/
docker cp redis.conf redis-6384:/data/
docker cp redis.conf redis-6385:/data/
docker cp redis.conf redis-6386:/data/

4.启动容器

docker start redis-6381 redis-6382 redis-6383 redis-6384 redis-6385 redis-6386

此时每个节点都是主节点,且没有关联

二、创建 redis 集群

进入其中一个节点,创建集群,本文redis版本为6.0

方式一

redis-cli --cluster create 172.17.0.2:6379 172.17.0.3:6379 172.17.0.4:6379 172.17.0.5:6379 172.17.0.6:6379 172.17.0.7:6379 --cluster-replicas 1

redis-cli后面跟着redis实例的地址列表,--cluster-replicas 1参数表示希望每个主服务器都有一个从服务器,redis集群会尽量把主从服务器分配在不同机器上

方式二

命令

集群

  • cluster info :打印集群的信息
  • cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息

节点

  • cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子
  • cluster forget <node_id> :从集群中移除 node_id 指定的节点
  • cluster replicate <master_node_id> :将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作
  • cluster saveconfig :将节点的配置文件保存到硬盘里面。

槽(slot)

  • cluster addslots <slot> [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点
  • cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派。
  • cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
  • cluster setslot <slot> node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
    另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
  • cluster setslot <slot> migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。
  • cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。
  • cluster setslot <slot> stable :取消对槽 slot 的导入( import)或者迁移( migrate)。

  • cluster keyslot <key> :计算键 key 应该被放置在哪个槽上。
  • cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量。
  • cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键 。

1.建立集群

我们需要将各个独立的节点连接起来,构成一个包含多个节点的集群,使用 CLUSTER MEET 命令

root@c04509c0752e:/data# redis-cli -h 172.17.0.2 -c
172.17.0.2:6379> cluster meet 172.17.0.3 6379
OK
172.17.0.2:6379> cluster meet 172.17.0.4 6379
OK
172.17.0.2:6379> cluster meet 172.17.0.5 6379
OK
172.17.0.2:6379> cluster meet 172.17.0.6 6379
OK
172.17.0.2:6379> cluster meet 172.17.0.7 6379
OK

2.分配哈希槽

redis-cli -h 172.17.0.2  cluster addslots {0..5000}
redis-cli -h 172.17.0.3  cluster addslots {5001..10000}
redis-cli -h 172.17.0.4  cluster addslots {10001..16383}

3.主从复制

redis-cli -h 172.17.0.6 cluster replicate 09756ea21494c4ef967fd0e788371aa2d77a10cc
redis-cli -h 172.17.0.7 cluster replicate ce1ed80a4a0bc79eb6788c134d49470b2064ec07
redis-cli -h 172.17.0.5 cluster replicate 732aeb97724583ba807713ac9097bff18cd37a2a

三、查看集群

任意节点执行

redis-cli -h 172.17.0.2 cluster nodes

结果:

可以看出,有三对主从节点。主节点 172.17.0.2,从节点 172.17.0.6;主节点 172.17.0.3,从节点 172.17.0.7;主节点 172.17.0.4,从节点 172.17.0.5

一共 16383 个哈希槽,分配到三个主节点,172.17.0.2 是 0-5460,172.17.0.3 是 5461-10922,172.17.0.4 是 10923-16383。从节点是不分配哈希槽的

c6a2e9648c3ea70e9deb53411e97956335be7dad 172.17.0.8:6379@16379 master - 0 1645516603120 0 connected
6a9d139c3cc5a683a7b8586ab4d46246f587497a 172.17.0.5:6379@16379 slave 732aeb97724583ba807713ac9097bff18cd37a2a 0 1645516602000 3 connected
732aeb97724583ba807713ac9097bff18cd37a2a 172.17.0.4:6379@16379 master - 0 1645516602117 3 connected 10923-16383
2bade4248dcf270fc6abc4a952ebcf0933c2b9ac 172.17.0.7:6379@16379 slave ce1ed80a4a0bc79eb6788c134d49470b2064ec07 0 1645516601000 2 connected
d01c5c39ea96bf842ef3186792faa58508d620be 172.17.0.6:6379@16379 slave 09756ea21494c4ef967fd0e788371aa2d77a10cc 0 1645516601000 1 connected
09756ea21494c4ef967fd0e788371aa2d77a10cc 172.17.0.2:6379@16379 myself,master - 0 1645516602000 1 connected 0-5460
ce1ed80a4a0bc79eb6788c134d49470b2064ec07 172.17.0.3:6379@16379 master - 0 1645516601114 2 connected 5461-10922

四、操作集群

redis-cli -h 172.17.0.2 -c

-c 表示 连接RedisCluster的参数,可以防止moved和ask异常

五、添加节点

1.添加主节点

redis-cli --cluster add-node <新增节点ip:port> <现存节点ip:port>

<现存节点 ip:port>可以取当前集群中任一节点的 IP 和端口号,此时没有分配哈希槽,是不会存值到新节点的

例如:

redis-cli --cluster add-node 172.17.0.8:6379 172.17.0.2:6379

2.分配哈希槽

redis-cli --cluster reshard 172.17.0.2:6379

根据提示分配输入分配哈希槽个数

3.添加从节点

redis-cli --cluster add-node <新增从节点ip:port> <现存节点ip:port> --cluster-slave --cluster-master-id <主节点ID>

<现存节点ip:port> 可以取当前集群中任一节点的 IP 和端口号

例如:

redis-cli --cluster add-node 172.17.0.9:6379 172.17.0.2:6379 --cluster-slave --cluster-master-id c6a2e9648c3ea70e9deb53411e97956335be7dad 

六、删除节点

redis-cli --cluster del-node <任一节点ip:port> <被删除节点ID>

对于从节点,可以直接删除;对于主节点,哈希槽移动到其他主节点上,才可以删除它,否则会提示删除失败

七、节点间的内部通信机制

1.集中式、Gossip 协议

集群元数据的维护有两种方式:集中式、Gossip 协议。 redis cluster 节点间采用 gossip 协议进行通信。

gossip 协议包含多种消息,包含 ping,pong,meet,fail 等等。

  • meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
  • ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
  • pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
  • fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。

redis cluster 每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。

2.redis 节点间通信端口

在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

cluster bus 使用 gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

在 redis cluster 架构下,每个节点每隔一段时间都会往另外几个节点发送 ping 消息,同时其它几个节点接收到 ping 之后返回 pong。所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。

八、一致性 hash 算法

一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。

来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。

在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。

一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。

redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。

redis cluster 中每个 master 都会持有部分 slot,hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。

任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。

九、redis cluster 的高可用与主备切换

1.判断节点宕机

如果一个节点认为另外一个节点宕机,那么就是 pfail,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown。

在 cluster-node-timeout 内,某个节点一直没有返回 pong,那么就被认为 pfail。

如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中,ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail。

2.从节点过滤

对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。

检查每个 slave node 与 master node 断开连接的时间,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成 master。

3.从节点选举

每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。

所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。

  1. 每个slave会与自己的master通讯,当slave发现自己的master变为fail时。每个slave都会参与竞争,推举自己为master。
  2. 增加currentEpoch的值,并且每个slave向集群中的其他所有节点广播FAILOVER_AUTH_REQUEST。
  3. 其他master会受到多个slave的广播,但是只会给第一个slave回复FAILOVER_AUTH_ACK。
  4. slave接收到ack之后,会使用过半机制开始统计。即:当前有多少master给自己ack,如果超过一半的master发送ack,则成为master。
  5. 广播PONG,通知给集群中的其节点。

4.替换主节点

  1. 当前从节点取消复制变成主节点。(slaveof no one)
  2. 执行cluster del slot撤销故障主节点负责的槽,并执行cluster add slot把这些槽分配给自己
  3. 向集群广播自己的pong消息,表明已经替换了故障从节点