目录
编辑一、搭建分片集群
1、集群结构
编辑 2、准备实例和配置
3、启动
4、创建集群
二、散列插槽
三、集群伸缩
四、故障转移
1、自动故障转移
2、手动故障转移
五、RedisTemplate 访问分片集群
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
1、海量数据存储问题
2、高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
1、集群中有多个master,每个master保存不同数据
2、每个master都可以有多个slave节点
3、master之间通过ping监测彼此健康状态
4、客户端请求可以访问集群任意节点,最终都会被转发到正确节点
这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:
先删除之前的 7001、7002、7003 这几个目录,重新创建7001、7002、7003、8001、8002、8003 目录
# 进入/tmp目录
cd /tmp
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003
在 /tmp 下准备一个新的 redis.conf 文件,内容如下:
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 111.229.153.16
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log
将这个文件拷贝到每个目录下:
# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:
# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
因为已经配置了后台启动模式,所以可以直接启动服务:
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
通过ps查看状态:
ps -ef | grep redis
如果要关闭所有进程,可以执行命令:
ps -ef | grep redis | awk '{print $2}' | xargs kill
我使用的是Redis6.2版本,集群管理以及集成到了redis-cli中,格式如下:
/usr/redis/bin/redis-cli --cluster create --cluster-replicas 1 111.229.153.16:7001 111.229.153.16:7002 111.229.153.16:7003 111.229.153.16:8001 111.229.153.16:8002 111.229.153.16:8003
命令说明:
输入 yes 之后,集群开始创建
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
那么为什么要做这样一个插槽呢?
假设现在,我们要 set num 123 那么这个 num 应该存储在哪个 master 上呢? ,假设我们将其存储到 7001 上,那么到时候我们找的时候又怎么知道它存储在 7001 上呢?
插槽就是用来解决这个问题的
数据key不是与节点绑定,而是与插槽绑定。redis 会根据 key 的有效部分计算插槽值,分两种情况:
1、key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
2、key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
为什么我们的 key 要去和插槽绑定,而不是和节点绑定呢?
这是因为我们 redis 的主节点是可能出现宕机等情况的,如果一个节点删除了或者宕机了,那么上面的数据也就会丢失,而如果数据是和插槽绑定,当节点宕机时,我们可以将这个节点对应的插槽转移到活着的节点,集群扩容时,我们也可以将插槽进行转移,这样数据跟着插槽走,就永远都能找到数据所在的位置了
key 和插槽绑定的体现:
总结:
Redis如何判断某个key应该在哪个实例?
1、将16384个插槽分配到不同的实例
2、根据key的有效部分计算哈希值,对16384取余
3、余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如 key 都以 {typeId} 为前缀
redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:
redis-cli --cluster help
比如,添加节点的命令:
add-node
我们可以看到,添加节点的时候需要几个参数:新节点的端口和 IP,已经存在的主机的端口和 IP
那么为什么我们添加新节点还要知道旧节点的 IP 和端口呢?
因为当你向集群中添加节点,需要通知集群中的每个角色,那么就得先连上集群,提供了已经存在的主机的端口和 IP,就能联系上集群,从而把新节点的信息通知给每一个节点
当集群中有一个 master 宕机会发生什么呢?
1、首先是该实例与其它实例失去连接
2、然后是疑似宕机:
3、最后是确定下线,自动提升一个slave为新的master:
利用 cluster failover 命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的这个 slave 节点,实现无感知的数据迁移。其流程如下:
再执行命令的那一刻,slave 会向 master 发送一个消息,告诉它 当前这个 slave 将要替换它,为了避免消息的丢失,master 就会拒绝客户端的一切请求
此时 master 会返回当前的 offset 给 slave ,如果二者 offset 不一致,就进行同步,同步完成之后,slave 与 master 数据就完全一致了。
slave 与 master 数据完全一致之后就可以进行故障转移,slave 标记自己成 master 并广播故障转移的结果,master 受到广播后,就转为 slave
手动的 failover 支持三种不同模式:
缺省:默认的流程,如图1~6歩
force:省略了对offset的一致性校验
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
RedisTemplate 底层同样基于 lettuce 实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1、引入 redis 的 starter 依赖
2、配置分片集群地址
3、配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下: