redis-9-集群

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • [1. 集群](#1. 集群)
    • [1.1 基本介绍](#1.1 基本介绍)
    • [1.2 哈希求余](#1.2 哈希求余)
    • [1.3 ⼀致性哈希算法](#1.3 ⼀致性哈希算法)
    • [1.4 哈希槽分区算法 (Redis 使⽤)](#1.4 哈希槽分区算法 (Redis 使⽤))
    • [1.5 搭建集群环境](#1.5 搭建集群环境)
    • [1.6 使用集群](#1.6 使用集群)
    • [1.7 故障处理](#1.7 故障处理)
    • [1.8 集群扩容](#1.8 集群扩容)
      • [第⼀步: 把新的主节点加⼊到集群](#第⼀步: 把新的主节点加⼊到集群)
      • [第⼆步: 重新分配 slots](#第⼆步: 重新分配 slots)
      • [第三步: 给新的主节点添加从节点](#第三步: 给新的主节点添加从节点)
      • 集群缩容
      • [使⽤ Spring Boot 连接 Redis 集群](#使⽤ Spring Boot 连接 Redis 集群)
      • jedis访问集群
  • 总结

前言

1. 集群

1.1 基本介绍

上述的 哨兵 模式, 提⾼了系统的可⽤性. 但是真正⽤来存储数据的还是 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).

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

1.2 哈希求余

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

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

例如, N 为 3. 给定 key 为 hello, 对 hello 计算 hash 值(⽐如使⽤ md5 算法), 得到的结果为

bc4b2a76b9719d91 , 再把这个结果 % 3, 结果为 0, 那么就把 hello 这个 key 放到 0 号分⽚上.

当然, 实际⼯作中涉及到的系统, 计算 hash 的⽅式不⼀定是 md5, 但是思想是⼀致的.

md5就是一个计算哈希值的方案,计算出来的东西是一个数字,可能会有字母---》只是进制不同而已

特点:md5计算结果是定长的,md5计算结果是分散的,不同的字符串,甚至只有一两个字母不一样,生成出来的数字都是差别很大的,md5的计算结果是不可逆的,计算出数字很简单,但是给出md5值,不能得出源字符串,所以md5可以用来加密

md5的可逆----》只能可逆出简单的,因为把简单字符串的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 都是搬运过的.

1.3 ⼀致性哈希算法

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

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

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

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

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

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

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

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

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

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

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

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

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

bash 复制代码
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] + [15019, 16383], 共 4096 个槽位,只有这些被移动的槽位会进行搬运

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

此处还有两个问题:

问题⼀: Redis 集群是最多有 16384 个分⽚吗?如果这么多分片---》几万台机器了

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

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

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

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 个分⽚来说是⾜够⽤的, 同时也会使对应的槽位配置位图体积不⾄于很⼤.

1.5 搭建集群环境

生成配置文件

注意!

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

后两个⽤来演⽰集群扩容.

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

1 redis-cluster/

2 ├── docker-compose.yml

3 └── generate.sh

generate.sh 内容如下

这个是shell脚本,就是把命令写在一个文件中,用来创建redis的配置文件

bash 复制代码
for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << 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); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << 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

$(seq 1 9)的意思就是生成1到9的序列

${port}表示一个变量

do 和done表示代码块的开始和结束

\是续行符

shell默认情况下,要求代码都必须是一行,所以用续行符

redis${port}就是一个字符串的拼接

cluster-enabled yes意思就是开启集群

cluster-config-file nodes.conf这个是指定集群的配置文件,这个文件不需要我们手动来写,这个是redis自动生成的,配置集群的信息

cluster-node-timeout 5000:多个节点交互联络超时时间,心跳包的超时时间

cluster-announce-ip 172.30.0.1${port}:这个是设置的redis节点自己的ip,就是docker容器的ip,外部的端口

cluster-announce-port 6379这个就是节点的ip,就是容器自己内部的ip

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 节点⾃⾝的总线端⼝. 集群管理的信息交互是通过这个端⼝进⾏的

这些都可以在官网上面查,还可以自己去redis配置文件中查看配置

使用 Windows 自带的 WSL就可以执行.sh脚本了,因为wsl就是类似于linux的ubuntu

cmd直接输入wsl

bash 复制代码
wsl
./generate.sh

编写 docker-compose.yml

先创建 networks, 并分配⽹段为 172.30.0.0/24

• 配置每个节点. 注意配置⽂件映射, 端⼝映射, 以及容器的 ip 地址. 设定成固定 ip ⽅便后续的观察和操作.

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

bash 复制代码
version: '3.7'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24

services:
  redis1:
    image: 'redis:5.0.9'
    container_name: redis1
    restart: always
    volumes:
      - ./redis1/:/etc/redis/
    ports:
      - 6371:6379
      - 16371:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.101

  redis2:
    image: 'redis:5.0.9'
    container_name: redis2
    restart: always
    volumes:
      - ./redis2/:/etc/redis/
    ports:
      - 6372:6379
      - 16372:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.102

  redis3:
    image: 'redis:5.0.9'
    container_name: redis3
    restart: always
    volumes:
      - ./redis3/:/etc/redis/
    ports:
      - 6373:6379
      - 16373:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.103

  redis4:
    image: 'redis:5.0.9'
    container_name: redis4
    restart: always
    volumes:
      - ./redis4/:/etc/redis/
    ports:
      - 6374:6379
      - 16374:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.104

  redis5:
    image: 'redis:5.0.9'
    container_name: redis5
    restart: always
    volumes:
      - ./redis5/:/etc/redis/
    ports:
      - 6375:6379
      - 16375:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.105

  redis6:
    image: 'redis:5.0.9'
    container_name: redis6
    restart: always
    volumes:
      - ./redis6/:/etc/redis/
    ports:
      - 6376:6379
      - 16376:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.106

  redis7:
    image: 'redis:5.0.9'
    container_name: redis7
    restart: always
    volumes:
      - ./redis7/:/etc/redis/
    ports:
      - 6377:6379
      - 16377:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.107

  redis8:
    image: 'redis:5.0.9'
    container_name: redis8
    restart: always
    volumes:
      - ./redis8/:/etc/redis/
    ports:
      - 6378:6379
      - 16378:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.108

  redis9:
    image: 'redis:5.0.9'
    container_name: redis9
    restart: always
    volumes:
      - ./redis9/:/etc/redis/
    ports:
      - 6379:6379
      - 16379:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.109

  redis10:
    image: 'redis:5.0.9'
    container_name: redis10
    restart: always
    volumes:
      - ./redis10/:/etc/redis/
    ports:
      - 6380:6379
      - 16380:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.110

  redis11:
    image: 'redis:5.0.9'
    container_name: redis11
    restart: always
    volumes:
      - ./redis11/:/etc/redis/
    ports:
      - 6381:6379
      - 16381:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.111

mynet是我们创建的网络名字

subnet是配置网络号,网段

ip地址=网络号+主机号

网络号来标识一个网段,主机号来标识这是哪一台主机

这里使用的是子网掩码的方式来区分主机号和网络号

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

所以子网掩码就是255.255.255.0

172.30.0这个就是网络号了

所以后面配置的每一个节点的ip的网络号都是一样的

还有就是注意配置的这个网络号172.30.0还必须是内网ip

什么是内网ip呢

第一就是10开头的

第二就是172.36~172.31开头

第三就是192.168开头的

第二我们配置的这个网络号,不能和当前主机上现有的其他网段冲突

bash 复制代码
ifconfig

这个命令就可以查看到当前机器上的网段了

发现有两个网段了

127.0.0.1和172.17.204.157

所以后面配置静态ip的时候,网络号部分要和网段部分一样,主机号部分就是1~255并且不重复就可以了

最后

bash 复制代码
docker-compose up -d


但是现在这些容器都还不是一个集群的

配置集群

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

bash 复制代码
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就知道了三个节点是一个分片

这样就构造出了三主六从了,这里端口号写的是容器内部的额端口号

但是构建的时候谁是主节点谁在一个分片都是不固定的

这个命令的话,随便进入一个容器里面就可以执行了,因为容器里面安装了redis-cli这个命令的,我的WIndows没有安装

这样就构建出来了

bash 复制代码
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383

这个就是给出了三个分片,然后每个分片的槽位号的范围

bash 复制代码
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

这个描述了哪个节点是哪个节点的从节点

所以看出来了101,102,103就是主节点

最后还要输入yes来确认

1.6 使用集群

同一个容器可以用这两个方式来连接

连上一个节点以后,就等价于连接上这个集群了

bash 复制代码
cluster nodes

这个命令是查看集群信息

我们可以看到ip端口号,主从,分片都显示出来了

bash 复制代码
> set key1 123
MOVED 9189 172.30.0.102:6379
> set k1 123
MOVED 12706 172.30.0.103:6379

我们这里可以看到输入不同的key就把数据转移到不同的分片上了

bash 复制代码
redis-cli -h 172.30.0.101 -p 6379 -c

我们这里设置-c表示,就算在101分片上也可以设置到102分片上的数据,而且会自动切换到102的客户端上面

bash 复制代码
# redis-cli -h 172.30.0.101 -p 6379 -c
172.30.0.101:6379> set k1 111
-> Redirected to slot [12706] located at 172.30.0.103:6379
OK
172.30.0.103:6379> get k1
"111"
172.30.0.103:6379> set key1 123
-> Redirected to slot [9189] located at 172.30.0.102:6379
OK
172.30.0.102:6379> get key1 
"123"
172.30.0.102:6379> 

redis容器里面安装了redis-cli 这个命令的

但是我用的可视化界面

bash 复制代码
> set key1 123
MOVED 9189 172.30.0.102:6379
> set k2 222
OK
> set k1 111
MOVED 12706 172.30.0.103:6379

所以它就会让我切换到对应的分片才让我操作

所以不好玩

还是用命令行吧

1.7 故障处理

主节点挂机

使用了集群之后,有些指令可以操作多个key-------------》如果key分散在不同的分片---》就会出问题了

bash 复制代码
172.30.0.102:6379> mget k1 k2
(error) CROSSSLOT Keys in request don't hash to the same slot
172.30.0.102:6379> 

这个也是有解决方案的,hash tag

如果集群中有节点挂了,怎么办,如果挂的是从节点----》没事

如果主节点挂了呢--》不好了

现在任何节点都可以进行set操作了,因为set不同的key就会切换不同主节点的客户端

在从节点上写操作---》会重定向到主节点上

所以如果主节点挂了,集群就会和哨兵一样,把从节点变为对应的主节点

我们这里停掉redis1

我们看到执行cluster nodes

101节点已经fail

然后105变成主节点了

然后106变为105的从节点了

然后又重启101节点

我们看到101已经变为了105的从节点了

具体流程

处理流程

  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 节点都挂了.---》说明集群遇到了非常严重的情况

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

  1. 故障迁移
    上述例⼦中, 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

而这里投票是为了选出新的主节点

1.8 集群扩容

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

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

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

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

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

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

bash 复制代码
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

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

我们看到110已经变为master了

但是我们这个master没有分片啊,没有槽位号,所以要重新分配槽位号

要把之前的master上面的slots拎出来一些分配给新的master

第⼆步: 重新分配 slots

bash 复制代码
redis-cli --cluster reshard 172.30.0.101:6379

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

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

我们看到它提问了,问我们要移动多少个slots,移动四分之一,就是4096个

16384/4=4096

第二个问题就是问让哪个节点接收这4096个,直接输入110主机的id就可以了

第三个问题就是问你从哪里分配这4096个slots

all就是每个分片都分一点

第二种就是手动指定一个或者几个节点的id,最后以done结尾


这个就是搬运的计划,其实还没有开始搬运

最后确认就输入yes

现在就开始真正的搬运了

可以看到现在的master都有slots了

在搬运 key 的过程中, 对于那些不需要搬运的 key, 访问的时候是没有任何问题的. 但是对于需要搬运的 key, 进⾏访问可能会出现短暂的访问错误 (key 的位置出现了变化).所以一般没有用户的时候进行扩容呗,但是最好的方法是新建一个集群,新的机器,数据导过来,这样就不会让用户决定数据有问题了

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

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

光有主节点了, 此时扩容的⽬标已经初步达成. 但是为了保证集群可⽤性, 还需要给这个新的主节点添加

从节点, 保证该主节点宕机之后, 有从节点能够顶上

bash 复制代码
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.1.110 节点的 nodeId]
bash 复制代码
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id 737ffbcfae9dddd906590a95610f98995e9f3730

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

我们看到111已经变为了Slave了,而且也是110的slave

集群缩容

缩容很少,所以了解一下就可以了

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

第⼀步: 删除从节点

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

bash 复制代码
 # redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
 redis-cli --cluster del-node 172.30.0.101:6379 
03f4a97806a0d3de2299cc16e6a3559f0c832bc1
bash 复制代码
4 >>> Removing node 03f4a97806a0d3de2299cc16e6a3559f0c832bc1 from cluster 
172.30.0.101:6379
5 >>> Sending CLUSTER FORGET messages to the cluster...
6 >>> SHUTDOWN the node.

第⼆步: 重新分配 slots

bash 复制代码
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

bash 复制代码
1 How many slots do you want to move (from 1 to 16384)? 1365
2 What is the receiving node ID? 3397c6364b43dd8a8d49057ad37be57760d3a81f
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done

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

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

bash 复制代码
1 How many slots do you want to move (from 1 to 16384)? 1365
2 What is the receiving node ID? 98736357a53c85aaebb31fa5ad286ab36b862426
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done

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

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

bash 复制代码
1 How many slots do you want to move (from 1 to 16384)? 1366
2 What is the receiving node ID? 26e98f947b99b3a2a5da5a7c3ed3875ae9cf366c
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done

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

bash 复制代码
1 127.0.0.1:6379> CLUSTER NODES

第三步: 删除主节点

把 110 节点从集群中删除.

bash 复制代码
1 # redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
2 redis-cli --cluster del-node 172.30.0.101:6379 
7c343b7e3f82f2e601ac6b9eba9f846b3065c600 
3
4 >>> Removing node 7c343b7e3f82f2e601ac6b9eba9f846b3065c600 from cluster 
172.30.0.101:6379
5 >>> Sending CLUSTER FORGET messages to the cluster...
6 >>> SHUTDOWN the node.

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

bash 复制代码
1 127.0.0.1:6379> CLUSTER nodes

使⽤ Spring Boot 连接 Redis 集群

bash 复制代码
spring:
  redis:
    cluster:
      nodes:
        - 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
    lettuce:
      cluster:
        refresh:
          adaptive: true
          period: 2000

下⽅的 lettuce 系列配置, ⽬的是为了⾃动刷新集群的拓扑结构. 当集群中有节点宕机/加⼊新节点之后, 我们的代码能够⾃动感知到集群的变化.

改完配置之后, 其他代码⽆需做出任何调整, 直接就可以正常运⾏了

注意!

由于上述 ip 都是 docker 容器的 ip

jedis访问集群

使⽤ JedisCluster 类代替 Jedis 类即可.

需要在创建实例的时候, 把多个节点的地址, 都设置进去.

JedisCluster 提供的⽅法和 Jedis 基本⼀致. 都和 Redis 命令是对应的. 具体细节我们不再演⽰了.

bash 复制代码
public static void main(String[] args) {
    Set<HostAndPort> nodes = new HashSet<>();
    nodes.add(new HostAndPort("172.30.0.101", 6379));
    nodes.add(new HostAndPort("172.30.0.102", 6379));
    nodes.add(new HostAndPort("172.30.0.103", 6379));
    nodes.add(new HostAndPort("172.30.0.104", 6379));
    nodes.add(new HostAndPort("172.30.0.105", 6379));
    nodes.add(new HostAndPort("172.30.0.106", 6379));
    nodes.add(new HostAndPort("172.30.0.107", 6379));
    nodes.add(new HostAndPort("172.30.0.108", 6379));
    nodes.add(new HostAndPort("172.30.0.109", 6379));

    try (JedisCluster jedisCluster = new JedisCluster(nodes)) {
        jedisCluster.set("k1", "111");
        String value = jedisCluster.get("k1");
        System.out.println(value);
    }
}

总结

相关推荐
言慢行善10 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星11 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟11 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z11 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可11 小时前
Java 中的实现类是什么
java·开发语言
He少年11 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新11 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏49411 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏49411 小时前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_4335021812 小时前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书