Redis 集群

目录

1. 基本概念

2. 数据分片算法

2.1 哈希求余

2.2 一致性哈希算法

2.3 哈希槽分区算法(Redis 使用)

3. 集群搭建

4. 主节点宕机 

4.1 故障判定

4.2 故障迁移


1. 基本概念

哨兵模式提高了系统的可用性,但是真正存储数据的还是 master 和 slave 节点,所有的数据都需要存储在单个 master 和 slave 节点中,如果数据量很大,超出了 master 和 slave 所在机器的物理内存,就可能会出现严重问题,而 Redis 集群就是引入多组 master 和 slave 节点,每一组 master 和 slave 存储全部数据的一部分,从而构成一个更大的整体,也就是 Redis 集群

假设整个数据量是 1TB,引入三组 master / slave 来存储,那么每一组机器只需要存储整个数据量的 1 / 3 即可

master1 和 slave11 和 slave12 保存同样的数据,占总数据的 1 / 3

master2 和 slave21 和 slave22 保存同样的数据,占总数据的 1 / 3

master3 和 slave31 和 slave32 保存同样的数据,占总数据的 1 / 3

每个 slave 都是对应 master 的备份,如果 master 挂了,对应的 slave 会变成 master,三组机器对应三个分片,如果数据量进一步增加,只需要增加更多的分片即可解决问题

2. 数据分片算法

Redis cluster 的核心思路是用多组机器来存储数据的每个部分,所以只需要确定一个数据(一个具体的 key)应该存储在哪个分片上,读取的时候在哪个分片上读取就可以了

2.1 哈希求余

假设有 N 个分片,使用 [0,N-1] 这样的序号进行编号,针对某个给定的 key,先计算其 hash 值,只把得到的结果 %N,得到的结果就是分片编号

例如,N 为3,key 为 hello,计算 hello 的哈希值(使用 md5 算法),得到的结果为 bc4b2a76b9719d91,再把这个结果 %3(hash(hello) % 3),结果为 0,那么就把 hello 这个 key 放到 0 号分片上,后续如果要取某个 key 的记录,就是针对 key 计算 hash,再对 N 求余即可

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

缺点:一旦需要进行扩容,N 改变了,原有的规则都会被打破,搬运的数据量是非常多的,开销比较大

上述只是在 3 个分片的基础上增加了一个分片,大量的数据都经过了搬运,因此开销是非常之大

2.2 一致性哈希算法

一致性哈希算法是为了降低搬运的开销,能够更高效的扩容,key 映射到分片的过程不再是简单求余了,而是改成下面的过程

1)把 0 - 2^32-1 这个数据空间,映射到一个圆环上,数据按照顺时针方向增长

2)假设当前存在三个分片,就把分片放到圆环的某个位置上

3)当有一个 key 计算到的 hash 值为 H,就从 H 所在的为止顺时针往下找,找到第一个分片,就是这个 key 所属的分片

N 个分片的位置把整个圆环分成了 N 个管辖区域,key 的 hash 值落在某个区域,就归哪个区域管理

当分片进行扩容时,原有的分片在环上的位置不变,只需要在环上新安排一个分片的位置即可

只需要把0号分片上的部分数据搬运给3号分片即可,其他分片管理的区域不变

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

缺点:数据分配不均匀

2.3 哈希槽分区算法(Redis 使用)

为了解决搬运成本高和数据分配不均匀的问题,Redis cluster 引入了哈希槽(hahs slots)算法

hash_slot = crc16(key) % 16384 

相当于把整个哈希值映射到 16384(也就是2^14)个槽位上,也就是[0,16383],然后把这些槽位比较均匀的分配给每个分片,每个分片的节点都需要记录自己持有哪些分片

假设当前有三个分片:

0 号分片:[0,5461],共 5462 个槽位

1 号分片:[5462,10923],共 5462 个槽位

2 号分片:[10924,16383],共 5460 个槽位

每个分片的节点使用位图来表示自己持有哪些槽位,对于 16384 个槽位来说,需要 2048 个字节(2 kb)大小的内存空间表示

如果需要进行扩容,比如新增一个 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 个分片吗? 

如果有1.6w个分片,整个数据服务器的集群规模是非常庞大的,一个系统越复杂,出现故障的概率越高,实际上 Redis 的作者建议集群分片数不应该超过 1000

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

1)节点之间通过心跳包通信,心跳包中包含了该节点持有哪些 slots(槽位),并且使用位图这样的数据结构来表示,16384(16k)个 slots,需要的位图大小为 2kb,如果给定的 slots 数更多了,比如 65536 个,此时就需要消耗更多的空间,8kb位图来表示,8kb对于在频繁的网络心跳包中是一个不小的开销

2)Redis 集群一般不建议超过 1000 个分片,所以 16k 对于最大 1000 个分片来说足够使用

3. 集群搭建

基于 docker 搭建一个集群,每个节点都是一个容器,拓扑结构如下:

1)创建目录和配置

创建 redis-cluster 目录,内部创建两个文件

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

 generate.sh 内容如下

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

执行命令

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

2)编写 docker-compose.yml

先编写 networks,并分配网段 172.30.0.0/24

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

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

3-11 的配置类似 

3)启动容器

docker-compose up -d

4)构建集群 

启动一个 docker 客户端,此处是把前面 9 个主机构建成集群,3 主 6 从

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 表示每个直接点需要两个从节点备份

执行之后,容器之间会进行加入集群操作

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

>>> 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 构建集群, 出现[OK]说明集群构建完成

此时,使用客户端连上集群中的任何一个节点,就相当于连上了整个集群,客户端后面要加上 -c 选项,否则如果没有 key 落到当前节点上,是不呢个操作的, -c 会自动把请求重定向在对应的节点上

redis-cli -h 172.30.0.101 -p 6379 -c

4. 主节点宕机 

此处和哨兵模式的主节点宕机一样,主节点挂了,会在属于该主节点的从节点中选出一个从节点作为主节点,当挂了的主节点连接之后,会成为从节点

可以使用 cluster failover 进行集群恢复,把挂了的主节点重新设置成 master

4.1 故障判定

在集群中的所有结点,都会周期性的使用心跳包进行通信

1)节点 A 给节点 B 发送 ping 包,B就会给 A 返回 pong 包

2)每个节点每秒钟都会给一些随机的节点发起 ping 包,不是全发一遍,这样设定是为了避免在节点过多的时候,心跳包也很多

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

4)A 判定 B 为 PFALL 之后,会通过 redis 内置的 Gossip 协议,和其他节点进行沟通,向其他节点确认 B 的状态

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

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

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

某个分片所有的主节点和从节点都挂了

某个分片主节点挂了,但是没有从节点

超过半数的 master 节点都挂了

4.2 故障迁移

上述中 B 故障,并且 A 把 B FALL 的消息告知集群中的其他节点,如果 B 是从节点,那么不需要进行故障迁移,B 是主节点,就会由 B 的从节点(例如 C 和 D)触发故障迁移,所谓的故障迁移就是把从节点提升为主节点

具体流程:

1)从节点判定自己是否有参选资格,如果从节点和主节点已经太久没有通信,那么从节点的数据和主节点差异就会很大,就失去资格

2)具有资格的节点,例如 C 和 D,就会休眠一定的时间,休眠时间 = 500ms 基础时间 + [0,500ms] 随机时间 + 排名 * 1000ms,offset 的值越大,则排名越靠前

3)例如 C 休眠的时间到了,C 就会给其他所有集群中的节点,进行拉票操作,只有主节点才能投票

4)直接点把自己的票投给 C,当 C 超过主节点数目一般的票数,C 晋升为主节点

5)C 会把自己成为主节点的消息,同步给其他集群的节点,大家也会更新自己保存的集群结构信息

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值