分片集群
主从和哨兵解决了高可用,高并发读问题,但是以下问题需要分片集群解决
- 海量数据存储问题
- 高并发写问题
特征
- 集群有多个master,每个master保存不同数据
- 每个master都可以有多个slave
- 不需要哨兵,每个master之间通过ping检测彼此健康状态
- 没有哨兵,客户端请求可以访问集群任意节点,最终都会被转发到正确节点
搭建集群
redis.conf内容
port 7001
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们自己创建,由redis自己维护
cluster-config-file /usr/local/bin/fconfig/7001/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /usr/local/bin/fconfig/7001
#绑定地址
bind 0.0.0.0
#设置后台运行
daemonize yes
#replica-announce-ip 81.70.144.36
# 关闭保护模式
protected-mode no
#数据库数量
databases 1
#日志位置
logfile /usr/local/bin/fconfig/7001/run.log
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
#相当于批量执行以下内容
redis-server 7001/redis.conf
redis-server 7002/redis.conf
redis-server 7003/redis.conf
redis-server 8001/redis.conf
redis-server 8002/redis.conf
redis-server 8003/redis.conf
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003
# redis-cli --cluster create 创建集群
# redis-cli --cluster-replicas 1 指定副本数量为1,即一主一从,那么redis自己就会默认后面的地址分成两部分,前一半是主,后一半是从
执行命令后,会发现
[root@VM-20-9-centos fconfig]# redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:8002 to 127.0.0.1:7001
Adding replica 127.0.0.1:8003 to 127.0.0.1:7002
Adding replica 127.0.0.1:8001 to 127.0.0.1:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 75f53bb044e28223053863dbb719a01503fb9629 127.0.0.1:7001
slots:[0-5460] (5461 slots) master
M: b71b4ce2635aef066bb707078787ea2f1b285fbb 127.0.0.1:7002
slots:[5461-10922] (5462 slots) master
M: a4906bbb1b6a8187d48cf7494178acf6f4197b10 127.0.0.1:7003
slots:[10923-16383] (5461 slots) master
S: a00c581794b18af96faa778a461673c0e2bf038e 127.0.0.1:8001
replicates 75f53bb044e28223053863dbb719a01503fb9629
S: b6de3dad15a5dc6304c036cf73e1de45a77abc4c 127.0.0.1:8002
replicates b71b4ce2635aef066bb707078787ea2f1b285fbb
S: 365ee01c6d778c208dd92840c0f9342491198d30 127.0.0.1:8003
replicates a4906bbb1b6a8187d48cf7494178acf6f4197b10
每个master节点上都有slots —–散列插槽
启动集群中节点的服务
#加上-c表示启动的是集群
redis-cli -c -p 7001
redis-cli –cluster是redis操作集群的命令
redis-cli -p 7002 cluster nodes
可以查看集群中节点的情况(端口号是集群中任意节点的均可)
散列插槽
Redis会把每个master节点映射到0~16383共16384个插槽上,查看集群信息时就能看到,每个master节点会映射到一部分
作用:
当要写入一个key时,这个key不知道应该被写入哪个master上,所以,redis会根据key的有效部分计算插槽值
- key中包含{},且{}中至少包含1个字符,{}中的部分是有效部分
- key中不包含{},整个key都是有效部分
计算方法是通过CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值
而且,如果key时和节点绑定,节点宕机后,数据就丢失了,而和插槽绑定,节点宕机后将他上面的插槽转移即可
[root@VM-20-9-centos fconfig]# redis-cli -c -p 7001
127.0.0.1:7001> set num 123
OK
127.0.0.1:7001> set a 1
-> Redirected to slot [15495] located at 127.0.0.1:7003 #计算出a的散列值对应插槽在7003节点范围内,重定向到7003
OK
127.0.0.1:7003> get a
"1"
127.0.0.1:7003> get num # 计算出num的对应插槽在7001节点范围内,重定向到7001
-> Redirected to slot [2765] located at 127.0.0.1:7001
"123"
所以在任意节点操作任意key时,都会先计算他的插槽值,判断哪个节点,得到正确的请求路由
如果根据业务需求,我们需要把某一类数据放在同一个节点上,避免重定向,可以给这类数据的每个key加一个{}前缀,{}中填相同的内容
127.0.0.1:7001> set {a}num 55
-> Redirected to slot [15495] located at 127.0.0.1:7003
OK
127.0.0.1:7003> get {a}num
"55"
127.0.0.1:7003> set {a}kkk 66
OK
集群伸缩
添加一个节点到集群
如果是新添加一个master,上面一开始是没有插槽的,我们可以使用redis-cli --cluster reshard
命令将别的master的插槽移过去
故障转移
我们通过故意将一个master端口的Redis宕机,可以发现之后redis自动选取了他的从节点作为新的master,原来的宕机的那个在重新启动后,变成了slave,整个过程不需要哨兵,自动完成。
实际业务中,可能由于某台master老旧,就为他添加了新的slave,想要将这个新的slave变成master,实现手动故障转移
可以先连接这个slaveredis-cli -p 7002
,再使用cluster failover
命令,完成主从切换。
RedisTemplate访问
底层基于lettuce实现了分片集群的支持
-
yml文件中配置分片集群地址
spring: redis: cluster: nodes: # 指定分片集群的每个节点信息 - 81.70.144.36:7001 - 81.70.144.36:7002 - 81.70.144.36:7003 - 81.70.144.36:8001 - 81.70.144.36:8002 - 81.70.144.36:8003
-
配置读写分离
@Bean public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer() { return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); }