【Redis】Redis 集群搭建与管理: 原理、实现与操作

目录

    • 集群 (Cluster)
      • 基本概念
      • 数据分片算法
        • 哈希求余
        • ⼀致性哈希算法
        • 哈希槽分区算法 (Redis 使⽤)
      • 集群搭建 (基于 docker)
        • 第⼀步: 创建⽬录和配置
        • 第⼆步: 编写 docker-compose.yml
        • 第三步: 启动容器
        • 第四步: 构建集群
      • 主节点宕机
        • 演⽰效果
        • 处理流程
        • 1)故障判定
        • 2)故障迁移
      • 集群扩容
        • 第⼀步: 把新的主节点加⼊到集群
        • 第⼆步: 重新分配 slots
        • 第三步: 给新的主节点添加从节点
      • 集群缩容 (选学)
        • 第⼀步: 删除从节点
        • 第⼆步: 重新分配 slots
        • 第三步: 删除主节点
      • 代码连接集群 (选学)
        • Java (Jedis)
        • Java (Spring)


集群 (Cluster)

本章节相关操作不需要记忆!!! 后续⼯作中如果⽤到了能查到即可.

重点理解流程和原理.

广义的集群:只要是多个机器,构成了分布式系统,都是一个集群

狭义的集群:redis 提供的集群模式。这个集群模式主要是解决存储空间不足的问题(拓展存储空间)

基本概念

上述的 哨兵 模式,提⾼了系统的可⽤性。但是真正⽤来存储数据的还是 master 和 slave 节点。所有的数据都需要存储在单个 master 和 slave 节点中。

如果数据量很⼤,接近超出了 master / slave 所在机器的物理内存,就可能出现严重问题了。

虽然硬件价格在不断降低,⼀些中⼤⼚的服务器内存已经可以达到 TB 级别了,但是 1TB 在当前这个 “⼤数据” 时代,俨然不算什么,有的时候我们确实需要更⼤的内存空间来保存更多的数据。

如何获取更⼤的空间? 加机器即可! 所谓 “大数据” 的核⼼,其实就是⼀台机器搞不定了,⽤多台机器来搞.

Redis 的集群就是在上述的思路之下,引⼊多组 Master / Slave ,每⼀组 Master / Slave 存储数据全集的⼀部分,从⽽构成⼀个更⼤的整体,称为 Redis 集群 (Cluster)。

假定整个数据全集是 1 TB,引⼊三组 Master / Slave 来存储。那么每⼀组机器只需要存储整个数据全集的 1/3 即可。

在上述图中,

  • Master1 和 Slave11 和 Slave12 保存的是同样的数据。占总数据的 1/3
  • Master2 和 Slave21 和 Slave22 保存的是同样的数据。占总数据的 1/3
  • Master3 和 Slave31 和 Slave32 保存的是同样的数据。占总数据的 1/3

这三组机器存储的数据都是不同的

每个 Slave 都是对应 Master 的备份(当 Master 挂了,对应的 Slave 会补位成 Master)。

每个红框部分都可以称为是⼀个 分片 (Sharding)

如果全量数据进⼀步增加,只要再增加更多的分⽚,即可解决。

可能有的同学认为,数据量多了,使⽤硬盘来保存不就⾏了?不要忘了硬盘只是存储多了,但是访问速度是⽐内存慢很多的。但是事实上,还是存在很多的应⽤场景,既希望存储较多的数据,⼜希望有⾮常⾼的读写速度。

⽐如搜索引擎

数据分片算法

Redis cluster 的核⼼思路是⽤多组机器来存数据的每个部分。那么接下来的核⼼问题就是,给定⼀个数据 (⼀个具体的 key),那么这个数据应该存储在哪个分⽚上?读取的时候⼜应该去哪个分⽚读取?围绕这个问题,业界有三种⽐较主流的实现⽅式。

哈希求余

借鉴了哈希表的基本思想:借助 hash 函数,把一个 key 映射到整数,再针对数组的长度求余,就可以得到一个数组下标

设有 N 个分⽚,使⽤ [0,N-1] 这样序号进⾏编号。

针对某个给定的 key,先计算 hash 值,再把得到的结果 % N,得到的结果即为分⽚编号。

例如, N 为 3。给定 key 为 hello,对 hello 计算 hash 值(⽐如使⽤ md5 算法,这本质是一个计算 hash 值的算法),得到的结果为 bc4b2a76b9719d91,再把这个结果 % 3,结果为 0,那么就把 hello 这个 key 放到 0 号分⽚上。当然,实际⼯作中涉及到的系统,计算 hash 的⽅式不⼀定是 md5,但是思想是⼀致的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

后续如果要取某个 key 的记录,也是针对 key 计算 hash,再对 N 求余,就可以找到对应的分⽚编号了。

优点:简单⾼效,数据分配均匀。

缺点:⼀旦需要进⾏扩容,N 改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列,以满⾜新的映射规则。此时需要搬运的数据量是⽐较多的,开销较⼤

N 为 3 的时候,[100,120] 这 21 个 hash 值的分布 (此处假定计算出的 hash 值是⼀个简单的整数,⽅便⾁眼观察)

当引⼊⼀个新的分⽚,N 从 3 => 4 时,⼤量的 key 都需要重新映射。(某个key % 3 和 % 4 的结果不⼀样,就映射到不同机器上了)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图可以看到,整个扩容⼀共 21 个 key,只有 3 个 key 没有经过搬运,其他的 key 都是搬运过的.

⼀致性哈希算法

为了降低上述的搬运开销,能够更⾼效扩容,业界提出了 “⼀致性哈希算法”

key 映射到分⽚序号的过程不再是简单求余了,⽽是改成以下过程:

第⼀步, 把 0 -> 2^32-1 这个数据空间,映射到⼀个圆环上。数据按照顺时针⽅向增⻓。

第⼆步,假设当前存在三个分⽚,就把分⽚放到圆环的某个位置上。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三步,假定有⼀个 key,计算得到 hash 值 H,那么这个 key 映射到哪个分⽚呢?规则很简单,就是从 H 所在位置,顺时针往下找,找到的第⼀个分⽚,即为该 key 所从属的分⽚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这就相当于,N 个分⽚的位置,把整个圆环分成了 N 个管辖区间。Key 的 hash 值落在某个区间内, 就归对应区间管理。

上述⼀致性哈希算法的过程,类似于去⾼铁站取票。

现在的⾼铁站都可以直接刷⾝份证了。但是以前的时候需要⽹上先购票, 然后再去⾼铁站的取票机上把票取出来。

想象下列场景:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设, ⼀个⼈每次来⾼铁站, 都会停⻋在同⼀个位置。(不同的⼈停⻋位置不同)。

每个⼈下⻋之后, 都往右⼿⽅向⾛, 遇到第⼀个取票机就进⾏取票。

在这个情况下, 如果扩容⼀个分⽚, 如何处理呢?

原有分⽚在环上的位置不动, 只要在环上新安排⼀个分⽚位置即可

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时,只需要把 0 号分⽚上的部分数据,搬运给 3 号分⽚即可。1 号分⽚和 2 号分⽚管理的区间都是不变的。

优点:⼤大降低了扩容时数据搬运的规模,提⾼了扩容操作的效率。

缺点:数据分配不均匀 (有的多有的少,数据倾斜)。

哈希槽分区算法 (Redis 使⽤)

为了解决上述问题 (搬运成本⾼ 和 数据分配不均匀),Redis cluster 引⼊了哈希槽 (hash slots) 算法

这种算法本质上的把一致性哈希和哈希求余结合了一下

 hash_slot = crc16(key) % 16384

其中 crc16 也是⼀种 hash 算法

16384 其实是 16 * 1024, 也就是 2^14

相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0,16383]

然后再把这些槽位⽐较均匀的分配给每个分⽚。每个分⽚的节点都需要记录⾃⼰持有哪些分⽚

假设当前有三个分⽚, ⼀种可能的分配⽅式:

  • 0 号分⽚: [0, 5461], 共 5462 个槽位
  • 1 号分⽚: [5462, 10923], 共 5462 个槽位
  • 2 号分⽚: [10924, 16383], 共 5460 个槽位

这⾥的分⽚规则是很灵活的。每个分⽚持有的槽位也不⼀定连续

每个分⽚的节点使⽤ 位图 来表⽰⾃⼰持有哪些槽位。对于 16384 个槽位来说, 需要 2048 个字节(2KB) ⼤⼩的内存空间表⽰

如果需要进⾏扩容, ⽐如新增⼀个 3 号分⽚, 就可以针对原有的槽位进⾏重新分配

⽐如可以把之前每个分⽚持有的槽位, 各拿出⼀点, 分给新分⽚

⼀种可能的分配⽅式:

  • 0 号分⽚: [0,4095], 共 4096 个槽位
  • 1 号分⽚: [5462,9557], 共 4096 个槽位
  • 2 号分⽚: [10924,15019], 共 4096 个槽位
  • 3 号分⽚: [4096,5461] + [9558,10923] + [15020,16383], 共 4096 个槽位

上述过程中,只有被移动的槽位,对应的数据才需要搬运

我们在实际使⽤ Redis 集群分⽚的时候, 不需要⼿动指定哪些槽位分配给某个分⽚, 只需要告诉某个分⽚应该持有多少个槽位即可, Redis 会⾃动完成后续的槽位分配, 以及对应的 key 搬运的⼯作

此处还有两个问题:

问题⼀: Redis 集群是最多有 16384 个分⽚吗?

并⾮如此。如果⼀个分⽚只有⼀个槽位, 这对于集群的数据均匀其实是难以保证的

实际上 Redis 的作者建议集群分⽚数不应该超过 1000

⽽且,16000 这么⼤规模的集群, 本⾝的可⽤性也是⼀个⼤问题。⼀个系统越复杂, 出现故障的概率是越⾼的

每个分片上只有一个槽位(此时很难保证数据在各个分片上的均衡性),key 是先映射到槽位,再映射到分片的。如果每个分片包含的槽位比较多,如果槽位个数相当,就可以认为是包含的 key 数量相当的。如果每个分配包含的槽位非常少,槽位个数不一定能直观的反应到 key 的数目。有的槽位可能是有多个 key,有的槽位可能是没有 key。

问题⼆: 为什么是 16384 个槽位?

Redis 作者的答案: https://github.com/antirez/redis/issues/2576

  • 节点之间通过⼼跳包通信。⼼跳包中包含了该节点持有哪些 slots(槽位)。这个是使⽤位图这样的数据结构表⽰的。表⽰ 16384 (16k) 个 slots, 需要的位图⼤⼩是 2KB。如果给定的 slots 数更多了, ⽐如 65536个了, 此时就需要消耗更多的空间, 8 KB 位图表⽰了。8 KB, 对于内存来说不算什么, 但是在频繁的⽹络⼼跳包中, 还是⼀个不⼩的开销的(周期性很强,特别频繁,很吃网络带宽)。
  • 另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚。 所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤的, 同时也会使对应的槽位配置位图体积不⾄于很⼤

集群搭建 (基于 docker)

接下来基于 docker, 搭建⼀个集群。每个节点都是⼀个容器.

当前阶段因为只有一个云服务器,搞分布式系统比较麻烦。实际工作中,一般是通过多个主机的方式来搭建集群的。

拓扑结构如下:

注意!

此处我们先创建出 11 个 redis 节点。其中前 9 个⽤来演⽰集群的搭建.

后两个⽤来演⽰集群扩容

第⼀步: 创建⽬录和配置

创建 redis-cluster ⽬录。内部创建两个⽂件

记得把之前启动的 redis 容器都停止掉

redis-cluster/
├── docker-compose.yml
└── generate.sh

Linux 中 .sh 后缀结尾的文件称为“shell 脚本”

使用 Linux 的时候,都是通过一些命令来操作的。使用命令操作,就非常适合把命令写到一个文件中,批量化执行。同时还能加入 条件、循环、函数 等机制。因此就可以基于这些完成更复杂的工作了。

我们这里要创建 11 个 redis 节点,这些配置文件内容大同小异,因此可以使用脚本来批量生成

generate.sh 内容如下

for port in $(seq 1 9); domkdir -p redis${port}/touch redis${port}/redis.confcat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done# 注意 cluster-announce-ip 的值有变化. 
for port in $(seq 10 11); domkdir -p redis${port}/touch redis${port}/redis.confcat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done

shell 中 {} 用来表示变量了,不是表示代码块。

对于 for 就是使用 dodone 来表示代码块的开始和结束的。

\ 是续行符,把下一行的内容和当前行合并成一行。

带有注释的版本

for port in $(seq 1 9); domkdir -p redis${port}/  # 创建一个文件夹,用于存放每个Redis实例的配置文件和数据touch redis${port}/redis.conf  # 创建配置文件cat << EOF > redis${port}/redis.conf
port 6379  # Redis服务监听端口
bind 0.0.0.0  # 允许所有地址连接
protected-mode no  # 关闭保护模式,允许非本地机连接
appendonly yes  # 启用AOF持久化
cluster-enabled yes  # 启用集群模式
cluster-config-file nodes.conf  # 集群的配置信息将存储在nodes.conf文件,这个文件不需要手动写,redis自动生成的
cluster-node-timeout 5000  # 集群节点超时时间
cluster-announce-ip 172.30.0.10${port}  # 集群通告IP地址,这里通过脚本生成不同的IP
cluster-announce-port 6379  # 集群通告端口
cluster-announce-bus-port 16379  # 集群总线端口,用于节点间的内部通信
EOF
done# 处理10号和11号端口的配置
for port in $(seq 10 11); domkdir -p redis${port}/  # 创建目录touch redis${port}/redis.conf  # 创建配置文件cat << EOF > redis${port}/redis.conf
port 6379  # Redis服务监听端口
bind 0.0.0.0  # 允许任何IP的连接
protected-mode no  # 关闭保护模式,允许非本地机连接
appendonly yes  # 启用AOF持久化
cluster-enabled yes  # 启用集群模式
cluster-config-file nodes.conf  # 集群配置文件,这个文件不需要手动写,redis自动生成的
cluster-node-timeout 5000  # 节点超时设置,单位毫秒
cluster-announce-ip 172.30.0.1${port}  # 为每个节点设置一个独特的IP地址
cluster-announce-port 6379  # 通告使用的端口
cluster-announce-bus-port 16379  # 集群内部通信使用的总线端口
EOF
done

port 就是 redis 节点自身绑定的端口(容器内的端口),不同的容器内部有相同的端口。后续进行端口映射,再把这些容器内的端口映射到容器外的不同端口即可

一个服务器就可以绑定多个端口号

业务端口:用来进行业务数据通信的(响应 redis 客户端的请求的)

管理端口:为了完成一些管理上的任务来进行通信的(如果某个分片中的 redis 主节点挂了,就需要让从节点变为主节点,就需要通过管理端口完成)

执⾏命令

bash generate.sh

⽣成⽬录如下:

redis-cluster/
├── docker-compose.yml
├── generate.sh
├── redis1
│   └── redis.conf
├── redis2
│   └── redis.conf
├── redis3
│   └── redis.conf
├── redis4
│   └── redis.conf
├── redis5
│   └── redis.conf
├── redis6
│   └── redis.conf
├── redis7
│   └── redis.conf
├── redis8
│   └── redis.conf
├── redis9
│   └── redis.conf
├── redis10
│   └── redis.conf
└── redis11└── redis.conf

其中 redis.conf 每个都不同,以 redis1 为例:

区别在于每个配置中配置的 cluster-announce-ip 是不同的, 其他部分都相同.

port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.101
cluster-announce-port 6379
cluster-announce-bus-port 16379

后续会给每个节点分配不同的 ip 地址

配置说明:

  • cluster-enabled yes 开启集群.
  • cluster-config-file nodes.conf 集群节点⽣成的配置.
  • cluster-node-timeout 5000 节点失联的超时时间.
  • cluster-announce-ip 172.30.0.101 节点⾃⾝ ip.
  • cluster-announce-port 6379 节点⾃⾝的业务端⼝.
  • cluster-announce-bus-port 16379 节点⾃⾝的总线端⼝. 集群管理的信息交互是通过这个端⼝进⾏的.
第⼆步: 编写 docker-compose.yml

基于 docker 创建 redis 容器,创建出 docker-compose.yml 文件,然后把文件里的内容填充上

  • 先创建 networks,并分配⽹段为 172.30.0.0/24
  • 配置每个节点。注意配置⽂件映射, 端⼝映射, 以及容器的 ip 地址. 设定成固定 ip ⽅便后续的观察和操作

此处的端⼝映射不配置也可以, 配置的⽬的是为了可以通过宿主机 ip + 映射的端⼝进⾏访问。通过 容器⾃⾝ ip:6379 的⽅式也可以访问

注释版本,以 redis1 为例

version: '3.7'  # 指定使用的Docker Compose文件格式版本
networks:mynet:  # 定义一个名为“mynet”的网络ipam:config:- subnet: 172.30.0.0/24  # 配置网络的子网services:redis1:image: 'redis:5.0.9'  # 使用的Docker镜像版本container_name: redis1  # 容器的名称restart: always  # 容器重启策略,设置为始终重启volumes:- ./redis1/:/etc/redis/  # 将宿主机的./redis1/目录映射到容器的/etc/redis/目录ports:- 6371:6379  # 映射容器内的6379端口到宿主机的6371端口- 16371:16379  # 映射容器内的16379端口到宿主机的16371端口,用于集群间通信command: redis-server /etc/redis/redis.conf  # 容器启动时执行的命令networks:mynet:ipv4_address: 172.30.0.101  # 分配给容器的静态IP地址
- subnet: 172.30.0.0/24  # 配置网络的子网

IP 地址=网络号+主机号

使用子网掩码的方式了区分网络号和主机号

子网掩码就是左边 24 位 是 1,右边 8 位是 0。

255.255.255.0

172.30.0 是网络号

- ./redis1/:/etc/redis/  # 将宿主机的./redis1/目录映射到容器的/etc/redis/目录

shell 脚本创建的目录,这里就包含配置文件

ports:- 6371:6379  # 映射容器内的6379端口到宿主机的6371端口- 16371:16379  # 映射容器内的16379端口到宿主机的16371端口,用于集群间通信

此处不进行端口映射也是可以的,映射一下目的是在容器外面可以通过客户端直接进行访问

networks:mynet:ipv4_address: 172.30.0.101  # 分配给容器的静态IP地址

此处配置静态ip,网络号部分要和前面的网段一样,主机号不分可以随便配置(1-255,保证不重复),就是按照之前的配置文件中写的 101-111,即下面这里的,这是 redis1 的 redis.conf

port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.101
cluster-announce-port 6379
cluster-announce-bus-port 16379

进行上述配置的时候,要保证前后一致

version: '3.7'
networks:mynet:ipam:config:- subnet: 172.30.0.0/24services:redis1:image: 'redis:5.0.9'container_name: redis1restart: alwaysvolumes:- ./redis1/:/etc/redis/ports:- 6371:6379- 16371:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.101redis2:image: 'redis:5.0.9'container_name: redis2restart: alwaysvolumes:- ./redis2/:/etc/redis/ports:- 6372:6379- 16372:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.102redis3:image: 'redis:5.0.9'container_name: redis3restart: alwaysvolumes:- ./redis3/:/etc/redis/ports:- 6373:6379- 16373:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.103redis4:image: 'redis:5.0.9'container_name: redis4restart: alwaysvolumes:- ./redis4/:/etc/redis/ports:- 6374:6379- 16374:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.104redis5:image: 'redis:5.0.9'container_name: redis5restart: alwaysvolumes:- ./redis5/:/etc/redis/ports:- 6375:6379- 16375:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.105redis6:image: 'redis:5.0.9'container_name: redis6restart: alwaysvolumes:- ./redis6/:/etc/redis/ports:- 6376:6379- 16376:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.106redis7:image: 'redis:5.0.9'container_name: redis7restart: alwaysvolumes:- ./redis7/:/etc/redis/ports:- 6377:6379- 16377:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.107redis8:image: 'redis:5.0.9'container_name: redis8restart: alwaysvolumes:- ./redis8/:/etc/redis/ports:- 6378:6379- 16378:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.108redis9:image: 'redis:5.0.9'container_name: redis9restart: alwaysvolumes:- ./redis9/:/etc/redis/ports:- 6379:6379- 16379:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.109redis10:image: 'redis:5.0.9'container_name: redis10restart: alwaysvolumes:- ./redis10/:/etc/redis/ports:- 6380:6379- 16380:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.110redis11:image: 'redis:5.0.9'container_name: redis11restart: alwaysvolumes:- ./redis11/:/etc/redis/ports:- 6381:6379- 16381:16379command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.111

严格按照这里的操作,在自己的机器上执行大概率是可以的,但不保证,因为环境可能不一样

第三步: 启动容器

启动之前一定要把之前已经运行的 redis 啥的都干掉,不然可能因为端口冲突等原因导致现在启动失败

通过 ps aux | grep redis 还有 docker ps -a 查询一下

一定要把之前启动过的都干掉

然后才可以启动容器

docker-compose up -d
第四步: 构建集群

启动⼀个 docker 客⼾端.

此处是把前 9 个主机构建成集群,3 主 6 从,后 2 个主机暂时不⽤.

redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 
172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 
172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2
  • --cluster create 表⽰建⽴集群,后⾯填写每个节点的 ip 和地址
  • --cluster-replicas 2 表⽰每个主节点需要两个从节点备份

这个配置设置好之后,redis 就知道了,3 个节点是一个分片的,一共 9 个节点,一共是 3 个分片了

redis 在构建集群的时候,谁是主节点谁是从节点,谁和谁是一个分片,这些都不是固定的。

本身从集群的角度来看,提供的这些节点直接都应该是等价的。

执⾏之后,容器之间会进⾏加⼊集群操作

⽇志中会描述哪些是主节点,哪些从节点跟随哪个主节点

>>> Performing hash slots allocation on 9 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.30.0.105:6379 to 172.30.0.101:6379
Adding replica 172.30.0.106:6379 to 172.30.0.101:6379
Adding replica 172.30.0.107:6379 to 172.30.0.102:6379
Adding replica 172.30.0.108:6379 to 172.30.0.102:6379
Adding replica 172.30.0.109:6379 to 172.30.0.103:6379
Adding replica 172.30.0.104:6379 to 172.30.0.103:6379
M: e4f37f8f0ea0dafc584349999795716613910e51 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
M: 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
M: b3c0a96f6a206088ecea639147b6fcf903afe872 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
S: 85025819223f12615046c54d89f510e9cd0444a1 172.30.0.104:6379
replicates b3c0a96f6a206088ecea639147b6fcf903afe872
S: 2e5dc211288784ba55d554a377b87bfe2b5398db 172.30.0.105:6379
replicates e4f37f8f0ea0dafc584349999795716613910e51
S: 29f05d98982bd3df05d0222091e4b8ef9569f424 172.30.0.106:6379
replicates e4f37f8f0ea0dafc584349999795716613910e51
S: 3584840ac704c3ee016f3bdcca3f7ebe6f6e8e80 172.30.0.107:6379
replicates 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c
S: 0a889103b35db2a6e82e8c09904bbef310cff3b1 172.30.0.108:6379
replicates 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c
S: 00ba82bed6abeb015116d51d1af7fcb1609d03ad 172.30.0.109:6379
replicates b3c0a96f6a206088ecea639147b6fcf903afe872
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 172.30.0.101:6379)
M: e4f37f8f0ea0dafc584349999795716613910e51 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
2 additional replica(s)
M: 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
2 additional replica(s)
S: 2e5dc211288784ba55d554a377b87bfe2b5398db 172.30.0.105:6379
slots: (0 slots) slave
replicates e4f37f8f0ea0dafc584349999795716613910e51
S: 0a889103b35db2a6e82e8c09904bbef310cff3b1 172.30.0.108:6379
slots: (0 slots) slave
replicates 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c
S: 3584840ac704c3ee016f3bdcca3f7ebe6f6e8e80 172.30.0.107:6379
slots: (0 slots) slave
replicates 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c
S: 85025819223f12615046c54d89f510e9cd0444a1 172.30.0.104:6379
slots: (0 slots) slave
replicates b3c0a96f6a206088ecea639147b6fcf903afe872
S: 00ba82bed6abeb015116d51d1af7fcb1609d03ad 172.30.0.109:6379
slots: (0 slots) slave
replicates b3c0a96f6a206088ecea639147b6fcf903afe872
S: 29f05d98982bd3df05d0222091e4b8ef9569f424 172.30.0.106:6379
slots: (0 slots) slave
replicates e4f37f8f0ea0dafc584349999795716613910e51
M: b3c0a96f6a206088ecea639147b6fcf903afe872 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
2 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

这里的每个 Adding replica 命令的含义是将一个 Redis 节点设置为另一个 Redis 节点的从节点。

比如 Adding replica 172.30.0.105:6379 to 172.30.0.101:6379 就是将位于IP地址 172.30.0.105、端口 6379 的Redis节点设置为位于IP地址 172.30.0.101、端口6379的Redis节点的从节点。这意味着前者将复制后者的数据。

⻅到下⽅的 [OK] 说明集群建⽴完成

此时,使⽤客⼾端连上集群中的任何⼀个节点,都相当于连上了整个集群(101-109)

  • 客⼾端后⾯要加上 -c 选项,否则如果 key 没有落到当前节点上,是不能操作的。-c 会⾃动把请求重定向到对应节点
  • 使⽤ cluster nodes 可以查看到整个集群的情况
# redis-cli -h 172.30.0.101 -p 6379 -c
172.30.0.101:6379> CLUSTER nodes
5f71983ad52cc7077ce8874ae1c4f9c23d9f502c 172.30.0.102:6379@16379 master - 0 1682
2e5dc211288784ba55d554a377b87bfe2b5398db 172.30.0.105:6379@16379 slave e4f37f8f0
0a889103b35db2a6e82e8c09904bbef310cff3b1 172.30.0.108:6379@16379 slave 5f71983ad
3584840ac704c3ee016f3bdcca3f7ebe6f6e8e80 172.30.0.107:6379@16379 slave 5f71983ad
85025819223f12615046c54d89f510e9cd0444a1 172.30.0.104:6379@16379 slave b3c0a96f6
e4f37f8f0ea0dafc584349999795716613910e51 172.30.0.101:6379@16379 myself,master -
00ba82bed6abeb015116d51d1af7fcb1609d03ad 172.30.0.109:6379@16379 slave b3c0a96f6
29f05d98982bd3df05d0222091e4b8ef9569f424 172.30.0.106:6379@16379 slave e4f37f8f0
b3c0a96f6a206088ecea639147b6fcf903afe872 172.30.0.103:6379@16379 master - 0 1682172.30.0.101:6379> set k1 1
-> Redirected to slot [12706] located at 172.30.0.103:6379
OK
172.30.0.103:6379> get k1
"1"

此处前面学过的 redis 相关的命令都是适用的(这句话不太严谨,有些指令能操作多个 key,如果 key 是分散在不同的分片,就可能出现问题了)

主节点宕机

从节点宕机问题不大

演⽰效果

⼿动停⽌⼀个 master 节点,观察效果

⽐如上述拓扑结构中,可以看到 redis1 redis2 redis3 是主节点,随便挑⼀个停掉

docker stop redis1

连上 redis2,观察结果

172.30.0.102:6379> CLUSTER NODES
2 e4f37f8f0ea0dafc584349999795716613910e51 172.30.0.101:6379@16379 master,fail - 1
3 0a889103b35db2a6e82e8c09904bbef310cff3b1 172.30.0.108:6379@16379 slave 5f71983ad
4 29f05d98982bd3df05d0222091e4b8ef9569f424 172.30.0.106:6379@16379 slave 2e5dc2112
5 00ba82bed6abeb015116d51d1af7fcb1609d03ad 172.30.0.109:6379@16379 slave b3c0a96f6
6 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c 172.30.0.102:6379@16379 myself,master -
7 b3c0a96f6a206088ecea639147b6fcf903afe872 172.30.0.103:6379@16379 master - 0 1682
8 2e5dc211288784ba55d554a377b87bfe2b5398db 172.30.0.105:6379@16379 master - 0 1682
9 3584840ac704c3ee016f3bdcca3f7ebe6f6e8e80 172.30.0.107:6379@16379 slave 5f71983ad
10 85025819223f12615046c54d89f510e9cd0444a1 172.30.0.104:6379@16379 slave b3c0a96f6

可以看到,101 已经提⽰ fail,然后 原本是 slave 的 105 成了新的 master

此处,集群做的工作和之前哨兵做的类似了,就会自动把该主节点旗下的从节点,挑选出来一个提拔成主节点

如果重新启动 redis1

docker start redis1

再次观察结果,可以看到 101 启动了,但已经是 slave了

172.30.0.102:6379> CLUSTER NODES
2 e4f37f8f0ea0dafc584349999795716613910e51 172.30.0.101:6379@16379 slave 2e5dc2112
3 0a889103b35db2a6e82e8c09904bbef310cff3b1 172.30.0.108:6379@16379 slave 5f71983ad
4 29f05d98982bd3df05d0222091e4b8ef9569f424 172.30.0.106:6379@16379 slave 2e5dc2112
5 00ba82bed6abeb015116d51d1af7fcb1609d03ad 172.30.0.109:6379@16379 slave b3c0a96f6
6 5f71983ad52cc7077ce8874ae1c4f9c23d9f502c 172.30.0.102:6379@16379 myself,master -
7 b3c0a96f6a206088ecea639147b6fcf903afe872 172.30.0.103:6379@16379 master - 0 1682
8 2e5dc211288784ba55d554a377b87bfe2b5398db 172.30.0.105:6379@16379 master - 0 1682
9 3584840ac704c3ee016f3bdcca3f7ebe6f6e8e80 172.30.0.107:6379@16379 slave 5f71983ad
10 85025819223f12615046c54d89f510e9cd0444a1 172.30.0.104:6379@16379 slave b3c0a96f6

可以使⽤ cluster failover 进⾏集群恢复. 也就是把 101 重新设定成 master. (登录到 101 上执⾏)

处理流程

集群机制的也能处理故障转移,但具体流程和哨兵的处理流程不太一样

1)故障判定

识别出某个节点是否挂了

集群中的所有节点,都会周期性的使⽤⼼跳包进⾏通信

  1. 节点 A 给 节点 B 发送 ping 包, B 就会给 A 返回⼀个 pong 包。ping 和 pong 除了 message type 属性之外, 其他部分都是⼀样的。这⾥包含了集群的配置信息(该节点的id,该节点从属于哪个分⽚,是主节点还是从节点, 从属于谁, 持有哪些 slots 的位图…)

  2. 每个节点, 每秒钟, 都会给⼀些随机的节点发起 ping 包, ⽽不是全发⼀遍。这样设定是为了避免在节点很多的时候, ⼼跳包也⾮常多(⽐如有 9 个节点, 如果全发, 就是 9 * 8 有 72 组⼼跳了, ⽽且这是按照 N^2 这样的级别增⻓的)

  3. 当节点 A 给节点 B 发起 ping 包, B 不能如期回应的时候, 此时 A 就会尝试重置和 B 的 tcp 连接, 看能否连接成功。如果仍然连接失败, A 就会把 B 设为 PFAIL 状态(相当于主观下线)

  4. A 判定 B 为 PFAIL 之后, 会通过 redis 内置的 Gossip 协议, 和其他节点进⾏沟通, 向其他节点确认 B 的状态。(每个节点都会维护⼀个⾃⼰的 “下线列表”, 由于视⻆不同, 每个节点的下线列表也不⼀定相同)

  5. 此时 A 发现其他很多节点, 也认为 B 为 PFAIL, 并且数⽬超过总集群个数的⼀半, 那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点(其他节点收到之后, 也会把 B 标记成FAIL)

⾄此,B 就彻底被判定为故障节点了

某个或者某些节点宕机,有的时候会引起整个集群都宕机 (称为 fail 状态)

以下三种情况会出现集群宕机:

  • 某个分⽚, 所有的主节点和从节点都挂了
  • 某个分⽚, 主节点挂了, 但是没有从节点
  • 超过半数的 master 节点都挂了(即使 slave 还有很多)

核⼼原则是保证每个 slots 都能正常⼯作(存取数据)

2)故障迁移

上述例⼦中,B 故障,并且 A 把 B FAIL 的消息告知集群中的其他节点

  • 如果 B 是从节点, 那么不需要进⾏故障迁移
  • 如果 B 是主节点, 那么就会由 B 的从节点 (⽐如 C 和 D) 触发故障迁移了

所谓故障迁移, 就是指把从节点提拔成主节点, 继续给整个 redis 集群提供⽀持.

具体流程如下:

  1. 从节点判定⾃⼰是否具有参选资格。如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太⼤了), 时间超过阈值, 就失去竞选资格

  2. 具有资格的节点, ⽐如 C 和 D, 就会先休眠⼀定时间。休眠时间 = 500ms 基础时间 + [0,500ms] 随机时间 + 排名 * 1000ms。offset 的值越⼤, 则排名越靠前(越⼩)

  3. ⽐如 C 的休眠时间到了, C 就会给其他所有集群中的节点, 进⾏拉票操作。但是只有主节点才有投票资格

  4. 主节点就会把⾃⼰的票投给 C (每个主节点只有 1 票)。当 C 收到的票数超过主节点数⽬的⼀半,C 就会晋升成主节点。(C ⾃⼰负责执⾏ slaveof no one, 并且让 D 执⾏ slaveof C)

  5. 同时,C 还会把⾃⼰成为主节点的消息,同步给其他集群的节点。⼤家也都会更新⾃⼰保存的集群结构信息

上述选举的过程, 称为 Raft 算法, 是⼀种在分布式系统中⼴泛使⽤的算法。目的是选一个出来,选谁不重要

在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功

哨兵,是先竞选出 leader,leader 负责找出一个从节点升级为主节点。而这里是直接投票选出新的主节点(master)

集群扩容

扩容是⼀个在开发中⽐较常遇到的场景

随着业务的发展,现有集群很可能⽆法容纳⽇益增⻓的数据。此时给集群中加⼊更多新的机器, 就可以使存储的空间更⼤了。

集群扩容操作,是一种风险较高,成本较大的操作。

所谓分布式的本质,就是使⽤更多的机器,引⼊更多的硬件资源

第⼀步: 把新的主节点加⼊到集群

上⾯已经把 redis1 - redis9 重新构成了集群。接下来把 redis10 和 redis11 也加⼊集群

此处我们把 redis10 作为主机, redis11 作为从机

此处相当于把数据分片从 3 -> 4

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

add-node 后的第⼀组地址是新节点的地址。第⼆组地址是集群中的任意节点地址。

执⾏结果

>>> Adding node 172.30.0.110:6379 to cluster 172.30.0.101:6379
>>> Performing Cluster Check (using node 172.30.0.101:6379)
M: 00d319e23ef76a4d51e74600c42ee2a371ae81f6 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
2 additional replica(s)
S: e34911c57d7605903de84ec05b3deac611aaef7e 172.30.0.105:6379
slots: (0 slots) slave
replicates 00d319e23ef76a4d51e74600c42ee2a371ae81f6
S: 6cf48cc11d0171b6ab1b418808473167acd7986e 172.30.0.106:6379
slots: (0 slots) slave
replicates 00d319e23ef76a4d51e74600c42ee2a371ae81f6
S: fd18c7f164b09ec563f4573ec9d6466e6769221e 172.30.0.108:6379
slots: (0 slots) slave
replicates b3f2ba758318f4bd54031c98c01d7a6155ff43d3
M: 579282abe81b3f20ffd17d5a1956cdca3b0e71b0 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
2 additional replica(s)
S: e9ea79b1326ea5a75a1701d5c12a0f6081c1d043 172.30.0.109:6379
slots: (0 slots) slave
replicates 579282abe81b3f20ffd17d5a1956cdca3b0e71b0
S: 628d1ec9ceef6760b9038c4fbc83ee92430062ac 172.30.0.107:6379
slots: (0 slots) slave
replicates b3f2ba758318f4bd54031c98c01d7a6155ff43d3
M: b3f2ba758318f4bd54031c98c01d7a6155ff43d3 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
2 additional replica(s)
S: 2a248acb47f0036655397897f9800c70ea22514f 172.30.0.104:6379
slots: (0 slots) slave
replicates 579282abe81b3f20ffd17d5a1956cdca3b0e71b0
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 172.30.0.110:6379 to make it join the cluster.
[OK] New node added correctly.
第⼆步: 重新分配 slots

把之前三组 master 上面的 slots 分出一些分配给新的 master

redis-cli --cluster reshard 172.30.0.101:6379

reshard 后的地址是集群中的任意节点地址

另外, 注意单词拼写, 是 reshard (重新切分), 不是 reshared (重新分享) , 不要多写个 e。

执⾏之后, 会进⼊交互式操作, redis 会提⽰⽤⼾输⼊以下内容:

  • 多少个 slots 要进⾏ reshard ? (此处我们填写 4096,这是 16384/4 得出的)
  • 哪个节点来接收这些 slots ? (此处我们填写 172.30.0.110 这个节点的集群节点 id)
  • 这些 slots 从哪些节点搬运过来? (此处我们填写 all, 表⽰从其他所有的节点都进⾏搬运。如果你想手动指定,就是从某一个节点或者某几个节点来移动 slots,以 done 为结尾)

执⾏结果如下

How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 522a1bd88a1a9084e6919fa88f4bf1c3655ad837
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

确定之后,会初步打印出搬运⽅案,让⽤⼾确认

输入 yes 之后就会进⾏集群的 key 搬运⼯作。这个过程涉及到数据搬运,可能需要消耗⼀定的时间。

在搬运 slots/key 的过程中,此时客户端能否访问我们的 redis 集群呢?

在搬运 key 的过程中, 对于那些不需要搬运的 key, 访问的时候是没有任何问题的。但是对于需要搬运的 key, 进⾏访问可能会出现短暂的访问错误 (key 的位置出现了变化)

假设客户端访问 k1,集群通过分片算法,得到 k1 是第一个分片的数据,就会重定向到第一个分片的节点,就可能在重定向过去之后,正好 k1 被搬走了,自然就无法访问了。

因此想进行这样的扩容操作时最好在夜深人静的时候进行扩容,可以把损失降到最低;又或者想追求更高的可用性,让扩容对于用户影响最小,就需要搞一组新的机器,重新搭建集群,并且把数据导入过来,用新集群代替旧集群。但这样成本也是最高的

随着搬运完成, 这样的错误⾃然就恢复了

第三步: 给新的主节点添加从节点

光有主节点了, 此时扩容的⽬标已经初步达成。但是为了保证集群可⽤性, 还需要给这个新的主节点添加从节点, 保证该主节点宕机之后, 有从节点能够顶上。

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave

执⾏完毕后, 从节点就已经被添加完成了

集群缩容 (选学)

扩容是⽐较常⻅的, 但是缩容其实⾮常少⻅。此处我们简单了解缩容的操作步骤即可。

接下来演⽰把 110 和 111 这两个节点删除。

第⼀步: 删除从节点

此处删除的节点 nodeId 是 111 节点的 id.

# redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
redis-cli --cluster del-node 172.30.0.101:6379 03f4a97806a0d3de2299cc16e6a3559f0
>>> Removing node 03f4a97806a0d3de2299cc16e6a3559f0c832bc1 from cluster 172.30.0
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
第⼆步: 重新分配 slots
redis-cli --cluster reshard 172.30.0.101:6379

执⾏后仍然进⼊交互式操作

注意!! 此时要删除的主节点, 包含 4096 个 slots. 我们把 110 这个注解上的这 4096 个 slots 分成三份 (1365 + 1365 + 1366), 分别分给其他三个主节点

这样可以使 reshard 之后的集群各个分⽚ slots 数⽬仍然均匀

第⼀次重分配: 分配给 101 1365 个 slots

接收 slots 的 nodeId 填写 101 的 nodeId。Source Node 填写 110 的 nodeId

How many slots do you want to move (from 1 to 16384)? 1365
What is the receiving node ID? 3397c6364b43dd8a8d49057ad37be57760d3a81f
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: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
Source node #2: done

第⼆次重分配: 分配给 102 1365 个 slots

接收 slots 的 nodeId 填写 102 的 nodeId。Source Node 填写 110 的 nodeId

How many slots do you want to move (from 1 to 16384)? 1365
What is the receiving node ID? 98736357a53c85aaebb31fa5ad286ab36b862426
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: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
Source node #2: done

第三次重分配: 分配给 103 1366 个 slots

接收 slots 的 nodeId 填写 103 的 nodeId。Source Node 填写 110 的 nodeId

How many slots do you want to move (from 1 to 16384)? 1366
What is the receiving node ID? 26e98f947b99b3a2a5da5a7c3ed3875ae9cf366c
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: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
Source node #2: done

此时查看集群状态, 可以看到 110 节点已经不再持有 slots 了

127.0.0.1:6379> CLUSTER NODES
26e98f947b99b3a2a5da5a7c3ed3875ae9cf366c 172.30.0.103:6379@16379 master - 0 1683
ee283a923b77fda2e370ff4463cbb22047f261f7 172.30.0.108:6379@16379 slave 98736357a
98736357a53c85aaebb31fa5ad286ab36b862426 172.30.0.102:6379@16379 master - 0 1683
b951da5c3573eec5db2926e022302188a8b686a6 172.30.0.107:6379@16379 slave 98736357a
df193127f99c59999e14518ff7235faeb7b7e8e7 172.30.0.104:6379@16379 slave 26e98f947
7c343b7e3f82f2e601ac6b9eba9f846b3065c600 172.30.0.110:6379@16379 master - 0 1683
3d148089278f9022a62a5936022e9c086fb7a062 172.30.0.105:6379@16379 slave 3397c6364
6de544df01337d4bbdb2f4f69f4c408314761392 172.30.0.109:6379@16379 myself,slave 26
3397c6364b43dd8a8d49057ad37be57760d3a81f 172.30.0.101:6379@16379 master - 0 1683
3b47ebc9868bbda018d8a5b926256861bb800cdc 172.30.0.106:6379@16379 slave 26e98f947
第三步: 删除主节点

把 110 节点从集群中删除

# redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
redis-cli --cluster del-node 172.30.0.101:6379 7c343b7e3f82f2e601ac6b9eba9f846b3
>>> Removing node 7c343b7e3f82f2e601ac6b9eba9f846b3065c600 from cluster 172.30.0
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

再次查看集群节点信息, 110 节点已经不在集群中了

127.0.0.1:6379> CLUSTER nodes
26e98f947b99b3a2a5da5a7c3ed3875ae9cf366c 172.30.0.103:6379@16379 master - 0 1683
ee283a923b77fda2e370ff4463cbb22047f261f7 172.30.0.108:6379@16379 slave 98736357a
98736357a53c85aaebb31fa5ad286ab36b862426 172.30.0.102:6379@16379 master - 0 1683
b951da5c3573eec5db2926e022302188a8b686a6 172.30.0.107:6379@16379 slave 98736357a
df193127f99c59999e14518ff7235faeb7b7e8e7 172.30.0.104:6379@16379 slave 26e98f947
3d148089278f9022a62a5936022e9c086fb7a062 172.30.0.105:6379@16379 slave 3397c6364
6de544df01337d4bbdb2f4f69f4c408314761392 172.30.0.109:6379@16379 myself,slave 26
3397c6364b43dd8a8d49057ad37be57760d3a81f 172.30.0.101:6379@16379 master - 0 1683
3b47ebc9868bbda018d8a5b926256861bb800cdc 172.30.0.106:6379@16379 slave 26e98f947

⾄此,缩容操作完成。

代码连接集群 (选学)

搭建集群之后, 主要仍然是要通过代码来访问集群的。这⼀点 C++ 和 Java 的 redis 的库都对集群做了较好的⽀持

Java (Jedis)

跳转到上面:Redis Java 使⽤ 样例列表

Java (Spring)

跳转到上面:Redis Java 集成到 Spring Boot

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/53381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP/TCP/UDP协议的关键知识点

导语&#xff1a;网络协议是理解网络情况的基础&#xff0c;当遇到网络问题时&#xff0c;首先可以从网络协议入手&#xff0c;熟悉的网络协议可以有效帮助小伙伴们排查或者说定位大概的问题方面。本文整理了目前最常用的网络通信协议&#xff0c;相信对小伙伴们肯定都有帮助。…

el-table使用type=“expand”根据数据条件隐藏展开按钮

一&#xff1a;添加className <el-table :data"tableData" border :loading"loading" :row-class-name"getRowClass" expand-change"expandchange"><el-table-column type"expand"><template #default"…

python学习11-Pytorch张量与数据处理1

ndarray 首先&#xff0c;我们介绍n维数组&#xff0c;也称为张量&#xff08;tensor&#xff09;。 使用过Python中NumPy计算包的读者会对本部分很熟悉。 无论使用哪个深度学习框架&#xff0c;它的张量类&#xff08;在MXNet中为ndarray&#xff0c; 在PyTorch和TensorFlow中…

华为eNSP:NAT Server(端口映射)

一、拓扑图 二、配置过程 此处省略设备地址以及路由配置过程 1、服务器开启ftp服务 2、路由器配置nat server [r4]int g0/0/2#进入流量出接口 [r4-GigabitEthernet0/0/2]nat server protocol tcp global 192.168.3.11 ftp inside 192.168.2.1 ftp# …

SnapGene 5.3.1下载安装教程百度网盘分享链接地址

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 SnapGene介绍 SnapGene 5.3.1下载安装教程百度网盘分享链接地址&#xff0c;SnapGene 是一款由美国公司开发&#xff08;后被收购&#xff09;的分子生物学软件&#xff0c;…

基于YOLO8的图片实例分割系统

文章目录 在线体验快速开始一、项目介绍篇1.1 YOLO81.2 ultralytics1.3 模块介绍1.3.1 scan_task1.3.2 scan_taskflow.py1.3.3 segment_app.py 二、核心代码介绍篇2.1 segment_app.py2.2 scan_taskflow.py 三、结语 代码资源&#xff1a;计算机视觉领域YOLO8技术的图片实例分割…

Java中Json、String、jsonObject、jsonArray格式之间的互相转换 (Fastjson、Gson、String字符串分隔)

1.org中jackson转换json,springboot中内置jackson ObjectMapper onew ObjectMapper();List<>listnew ArrayList();String jonso.writeAsValueString(list); 一、Fastion 使用阿里的fastjson <dependency><groupId>com.alibaba</groupId><artifactId…

使用 JAXB 将内嵌的JAVA对象转换为 xml文件

使用 JAXB 将内嵌的JAVA对象转换为 xml文件 1. 需求2. 实现&#xff08;1&#xff09;FileDesc类&#xff08;2&#xff09;MetaFileXml类&#xff08;3&#xff09;生成对应的xml文件 1. 需求 获取一个目录下所有文件的元数据信息&#xff08;文件名、大小、后缀等&#xff0…

1.2CubeMAX创建FREERTOS入门示例

1.CUBEMAX快速配置 V2改为V1否则编译会报错 2.Freertos各配置选项卡解释 Events &#xff1a;事件相关的创建 Task and Queues &#xff1a; 任务与队列的创建 Timers and Semaphores &#xff1a; 定时器和信号量的创建 Mutexes &#xff1a; 互斥量的创建 FreeRTOS Heap…

临床基础两手抓!这个12+神经网络模型太贪了,免疫治疗预测、通路重要性、基因重要性、通路交互作用性全部拿下!

生信碱移 IRnet介绍 用于预测病人免疫治疗反应类型的生物过程嵌入神经网络&#xff0c;提供通路、通路交互、基因重要性的多重可解释性评估。 临床实践中常常遇到许多复杂的问题&#xff0c;常见的两种是&#xff1a; 二分类或多分类&#xff1a;预测患者对治疗有无耐受(二分类…

如何在3DMAX中实现大规模项目的地形建模?

在房地产开发项目的环境建模过程中&#xff0c;我们对斜坡和不平坦地形进行建模是一项具有挑战性的任务。 我们已经制定了两种方法来纠正这一点。首先&#xff0c;让我告诉你&#xff0c;我们并没有想过如何使用NURBS来实现这一点&#xff0c;我们通常坚持使用多边形&#xff…

英语每日一段 195

Promising economic indicators won’t instantly reverse the lingering impact of hard times for millions of families, workplace culture expert Jessica Kriegel said. “Perception and reality are sometimes aligned and sometimes not,” Kriegel told Newsweek. “…

2024 数学建模高教社杯 国赛(A题)| “板凳龙”舞龙队 | 建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;运用等距螺线&#xff0c;多目标规划等强大工具&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量身打造创新解决方案。小秘籍团队&#xff0c;始终引领着建模问题求解的风潮。 抓紧小秘籍&am…

Java JVM 垃圾回收算法详解

Java 虚拟机&#xff08;JVM&#xff09;是运行 Java 应用程序的核心&#xff0c;它的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制是 JVM 中非常重要的一个部分。垃圾回收的主要任务是自动管理内存&#xff0c;回收那些不再被使用的对象&#xff0c;从而释放内…

【A题完整论文已出】2024数模国赛A题完整论文+可运行代码参考(无偿分享)

​​​​​​​ A 题 “板凳龙” 闹元宵 摘要&#xff1a; 随着城市节庆活动和传统文化展示的多样化发展&#xff0c;舞龙队的路径规划与速度控制问题成为传统活动表演中的重要研究课题。本文针对舞龙队在节庆活动中的路径优化、调头设计和行进速度控制问题&#xff0c;基…

2024年【金属非金属矿山(露天矿山)安全管理人员】考试题及金属非金属矿山(露天矿山)安全管理人员最新解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员考试题参考答案及金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员考试试题解析是安全生产模拟考试一点通题库老师及金属非金属矿山&#xf…

SQL 数据查询

文章目录 3.4.1 单表查询定义特点单表无条件查询单表带条件查询对查询结果进行排序限制查询结果数量 3.4.2 分组查询定义特点&#xff1a;聚集函数GROUP BY短语HAVING子句分组查询小结 3.4.3 连接查询定义特点&#xff1a;等值连接与非等值连接查询自然连接&#xff08;内连接&…

SQL的高级查询练习知识点(day24)

目录 1 学习目标 2 基础查询 2.1 语法 2.2 例子 3 条件查询 3.1 含义 3.2 语法 3.3 条件表达式 3.3.1 条件运算符 3.3.2 例子 3.4 逻辑表达式 3.4.1 逻辑运算符 3.4.2 例子 3.5 模糊查询 3.5.1 概述 3.5.2 例子 4 DISTINCT关键字 4.1 含义 4.2 例子 5 总结…

2024 年高教社杯全国大学生数学建模竞赛B题第二问详细解题思路(终版)

示例代码&#xff1a; import numpy as np import pandas as pd# 参数设定 params {p1: 0.10, p2: 0.10, c1: 4, c2: 2, d1: 2, d2: 3,pf: 0.10, a: 6, df: 3, s: 56, l: 6, r: 5 }# 决策变量 decisions [0, 1]# 利润计算函数 def calculate_profit(D1, D2, C, R, params):c…

Spring-@Bean的处理流程

Bean前置知识 1 需要再Configuration Class中才能被解析 2 静态Bean也就是标注在static方法上的 实例Bean标注在普通方法上的 所有的Bean在创建之前都会变成BeanDefinition,其中有这样两个属性&#xff1a; setFactoryMethodName&#xff1a;静态方法 setFactoryBeanName&…