【Redis入门到精通十一】Redis集群

目录

集群

1.三种分片算法

1.哈希求余算法

2.一致性哈希算法

3.哈希槽分区算法

2.搭建集群环境

3.集群故障处理

4.集群扩容


集群

上篇文章我们了解Redis哨兵的相关操作,使用哨兵只是解决了主节点瘫痪,从节点不能自动变为主节点的问题,并没有解决“单点问题”,(写操作只能由主节点完成)。 为了能存储更多的数据,分担主从节点的读/写压力,于是Redis引入了集群的相关操作, 引⼊多组 Master / Slave ,每⼀组 Master / Slave 存储数据全集的 ⼀部分,从⽽构成⼀个更⼤的整体,称为 Redis 集群 (Cluster)。

1.三种分片算法

如上图所示,在把全集数据平均分成多个部分,交给多个Redis主从服务器存储数据,这就是Redis集群,其中每个master与其对应的slave保存的是同样的数据,占总数据的1/3。

每个 Slave 都是对应 Master 的备份(当 Master 挂了, 对应的 Slave 会补位成 Master)。每个Redis主节点与其对应的从节点称为是⼀个 分⽚ (Sharding)。如果全量数据进⼀步增加, 只要再增加更多的分⽚, 即可解决。我们需要把全量数据尽可能的平均分给每个分片,以免造成Redis服务器压力不均衡,这个时候就需要用到分片算法了。Redis在设计时使用的便是哈希槽分区算法。
1.哈希求余算法
设有 N 个分⽚, 使⽤ [0, N-1] 这样序号进行编号。
针对某个给定的 key, 先计算 hash 值, 再把得到的结果 % N, 得到的结果即为分⽚编号。
例如, N 为 3. 给定 key 为 hello, 对 hello 计算 hash 值(⽐如使⽤ md5 算法), 得到的结果为bc4b2a76b9719d91 , 再把这个结果 % 3, 结果为 0, 那么就把 hello 这个 key 放到 0 号分片上。

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

优点: 简单高效, 数据分配均匀。
缺点: ⼀旦需要进行扩容, N 改变了, 原有的映射规则被破坏, 就需要让节点之间的数据相互传输, 重新排列, 以满足新的映射规则。此时需要搬运的数据量是比较多的, 开销较⼤。
2.一致性哈希算法
为了降低上述的搬运开销, 能够更⾼效扩容, 业界提出了 "⼀致性哈希算法"。
key 映射到分⽚序号的过程不再是简单求余了, ⽽是改成以下过程:
  • 第⼀步, 把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上. 数据按照顺时针方向增⻓.
  • 第⼆步, 假设当前存在三个分⽚, 就把分⽚放到圆环的某个位置上.
  • 第三步, 假定有⼀个 key, 计算得到 hash 值 H, 那么这个 key 映射到哪个分⽚呢? 规则很简单, 就是从 H所在位置, 顺时针往下找, 找到的第⼀个分⽚, 即为该 key 所从属的分片.

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

 在这种情况下,扩容只需要原有分片在环上的位置不动, 只要在环上新安排⼀个分⽚位置即可。

优点: 大大降低了扩容时数据搬运的规模, 提⾼了扩容操作的效率。
缺点: 数据分配不均匀 (有的多有的少, 数据倾斜)。
3.哈希槽分区算法
为了解决上述问题 (搬运成本⾼ 和 数据分配不均匀), Redis cluster 引⼊了哈希槽 (hash slots) 算法。
hash_slot = crc16(key) % 16384

crc16是一种哈希算法,16384其实就是2^14。

相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0, 16383].
然后再把这些槽位⽐较均匀的分配给每个分⽚. 每个分⽚的节点都需要记录⾃⼰持有哪些分⽚.
假设当前有三个分⽚, ⼀种可能的分配方式:
  1. 0 号分⽚: [0, 5461], 共 5462 个槽位
  2. 1 号分⽚: [5462, 10923], 共 5462 个槽位
  3. 2 号分⽚: [10924, 16383], 共 5460 个槽位
这⾥的分⽚规则是很灵活的. 每个分⽚持有的槽位也不⼀定连续.
每个分⽚的节点使⽤ 位图 来表⽰⾃⼰持有哪些槽位. 对于 16384 个槽位来说, 需要 2048 个字节(2KB) 大小的内存空间表⽰.

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

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

⼀种可能的分配方式:

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

2.搭建集群环境

接下来给大家教一种基于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
配置说明:
  • 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 节点⾃⾝的总线端⼝. 集群管理的信息交互是通过这个端⼝进⾏的.

2.编写docker-compose.yml

  • 先创建 networks, 并分配⽹段为 172.30.0.0/24
  • 配置每个节点. 注意配置⽂件映射, 端⼝映射, 以及容器的 ip 地址. 设定成固定 ip ⽅便后续的观察和操作.
此处的端⼝映射不配置也可以, 配置的⽬的是为了可以通过宿主机 ip + 映射的端⼝进⾏访问. 通过 容器⾃⾝ ip:6379 的⽅式也可以访问。
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

3.启动容器

 使用docker-compose up -d命令启动容器。

4.构建集群

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 表⽰每个主节点需要两个从节点备份

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

此时, 使⽤客⼾端连上集群中的任何⼀个节点, 都相当于连上了整个集群.
  • 客⼾端后⾯要加上 -c 选项, 否则如果 key 没有落到当前节点上, 是不能操作的. -c 会⾃动把请求重定向到对应节点.
  • 使⽤ cluster nodes 可以查看到整个集群的情况。

3.集群故障处理

故障判定

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

  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 状态).
以下三种情况会出现集群宕机:
  1. 某个分⽚, 所有的主节点和从节点都挂了.
  2. 某个分⽚, 主节点挂了, 但是没有从节点.
  3. 超过半数的 master 节点都挂了.

 故障迁移

一个节点A发生了故障,如果A是从节点不会发生故障迁移,但是如果A是主节点,那么便会触发故障迁移,所谓故障迁移,就是指把从节点提拔成主节点,继续给整个 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算法,是一种在分布式系统中广泛使用的算法。在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功。

4.集群扩容

扩容是⼀个在开发中⽐较常遇到的场景。
随着业务的发展, 现有集群很可能⽆法容纳⽇益增⻓的数据. 此时给集群中加⼊更多新的机器, 就可以使存储的空间更⼤了。

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

 集群扩容我们可以分为三个步骤:

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

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
第⼆步: 重新分配 slots
redis-cli --cluster reshard 172.30.0.101:6379
执⾏之后, 会进⼊交互式操作, redis 会提⽰⽤⼾输⼊以下内容:
  • 多少个 slots 要进⾏ reshard ? (此处我们填写 4096)
  • 哪个节点来接收这些 slots ? (此处我们填写 172.30.0.110 这个节点的集群节点 id)
  • 这些 slots 从哪些节点搬运过来? (此处我们填写 all, 表示从其他所有的节点都进行搬运)

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

光有主节点了, 此时扩容的⽬标已经初步达成. 但是为了保证集群可⽤性, 还需要给这个新的主节点添加从节点, 保证该主节点宕机之后, 有从节点能够顶上。
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --clusterslave --cluster-master-id [172.30.1.110 节点的 nodeId]

❤️😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍

🍔我是小皮侠,谢谢大家都能看到这里!!

🦚主页已更新Java基础内容,数据结构基础,数据库,算法,Redis相关内容。

🚕未来会更新Java项目,SpringBoot,docker,mq,微服务以及各种Java路线会用到的技术。

🎃求点赞!求收藏!求评论!求关注!

🤷‍♀️谢谢大家!!!!!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值