目录
- 分布式缓存 Redis 四大问题
- 搭建Redis分片集群
- 分片原理
- 散列插槽(插槽原理)
- 集群伸缩
- 需求设定
- 配置集群伸缩
- 故障转移
- 自动故障转移
- 手动故障转移
- RedisTemplate访问分片集群
分布式缓存 Redis 四大问题
基于 Redis 集群解决单机 Redis 存在的四大问题:
主从 和 哨兵 可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
-
海量数据存储问题
-
高并发写的问题
使用分片集群可以解决上述问题
搭建Redis分片集群
分片集群需要的节点数量较多,这里搭建一个最小的分片集群,包含 3 个 master 节点,每个 master 包含一个 slave 节点,结构图:
在同一台虚拟机中开启 6 个 Redis 实例,模拟分片集群,信息:
IP | PORT | 角色 |
---|---|---|
192.168.150.101 | 7001 | master |
192.168.150.101 | 7002 | master |
192.168.150.101 | 7003 | master |
192.168.150.101 | 8001 | slave |
192.168.150.101 | 8002 | slave |
192.168.150.101 | 8003 | slave |
准备实例和配置:
# 进入/tmp目录
cd /tmp
# 删除之前的7001、7002、7003这几个目录,避免配置干扰,重新创建出7001、7002、7003、8001、8002、8003目录
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 192.168.150.101
# 保护模式
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
# 或者(推荐这种方式):
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。需要执行命令来创建集群,在 Redis 5.0
之前创建集群比较麻烦,5.0 之后集群管理命令都集成到了 redis-cli 中。
(1)Redis 5.0之前
Redis 5.0 之前集群命令都是用 Redis 安装包下的 src/redis-trib.rb
来实现的。因为redis-trib.rb
是有Ruby
语言编写的所以需要安装Ruby
环境。
# 安装依赖
yum -y install zlib ruby rubygems
gem install redis
然后通过命令来管理集群:
# 进入redis的src目录
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
(2)Redis 5.0以后
使用的是Redis 6.2.4
版本,集群管理以及集成到了 redis-cli 中,格式如下:
redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
命令说明:
redis-cli --cluster
或者./redis-trib.rb
:代表集群操作命令。create
:代表是创建集群。--replicas 1
或者--cluster-replicas 1
:指定集群中每个 master 的副本个数为 1,此时节点总数 ÷ (replicas + 1)
得到的就是master的数量。因此节点列表中的前 n 个就是 master,其它节点都是 slave 节点,随机分配到不同 master。
查看集群状态:
redis-cli -p 7001 cluster nodes
测试尝试连接7001节点,存储一个数据:
# 连接
redis-cli -p 7001
# 存储数据
set num 123
# 读取数据
get num
# 再次存储
set a 1
# 报错
# (error) MOVED 15495 192.168.150.101:7003
集群操作时,需要给redis-cli
加上-c
参数才可以:
redis-cli -c -p 7001
# 读取数据
get num
redis-cli
-c
参数:连接集群结点时使用,此选项可防止 moved 和 ask 异常。
分片原理
分片集群特征:
-
集群中有多个 master,每个 master 保存不同数据。
-
每个 master 都可以有多个 slave 节点。
-
master 之间通过 ping 监测彼此健康状态。
-
客户端请求可以访问集群任意节点,最终都会被转发到正确节点。
散列插槽(插槽原理)
Redis 会把每一个 master 节点映射到 0~16383 共 16384 个插槽(hash slot)上,查看集群信息时就能看到。
查看集群状态:
redis-cli -p 7001 cluster nodes
数据 key 不是与节点绑定,而是与插槽绑定。Redis 会根据 key 的有效部分计算插槽值,分两种情况:
- key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分。
- key中不包含“{}”,整个key都是有效部分。
例如:key 是num,那么就根据 num 计算,如果是 {itcast}num,则根据 itcast 计算。计算方式是利用CRC16 算法得到一个 hash 值,然后对 16384 取余,得到的结果就是 slot 值。
在 7001 这个节点执行set a 1
时,对 a 做 hash 运算,对 16384 取余,得到的结果是 15495,因此要存储到 103 节点。到了 7003 后,执行 get num
时,对 num 做 hash 运算,对16384取余,得到的结果是 2765,因此需要切换到 7001 节点。
Redis如何判断某个 key 应该在哪个实例?
- 将 16384 个插槽分配到不同的实例。
- 根据 key 的有效部分计算哈希值,对 16384 取余。
- 余数作为插槽,寻找插槽所在实例即可。
如何将同一类数据固定的保存在同一个 Redis 实例?
- 这一类数据使用相同的有效部分,例如key都以 {typeId} 为前缀。
集群伸缩
redis-cli --cluster
提供了很多操作集群的命令,可以通过redis-cli --cluster help
查看,例如添加节点命令 add-node
。
需求设定
向集群中添加一个新的 master 节点,并向其中存储 num = 10
- 启动一个新的 Redis 实例,端口为 7004。
- 添加 7004 到之前的集群,并作为一个 master 节点。
- 给 7004 节点分配插槽,使得 num 这个 key 可以存储到 7004 实例。
这里需要两个新的功能:
- 添加一个节点到集群中。
- 将部分插槽分配到新插槽。
配置集群伸缩
# 创建一个文件夹
mkdir 7004
# 拷贝配置文件
cp redis.conf /7004
# 修改配置文件
sed /s/6379/7004/g 7004/redis.conf
# 启动
redis-server 7004/redis.conf
# 添加节点 add-node: new_host:new_port existing_host:existing_port
redis-cli --cluster add-node 192.168.150.101:7004 192.168.150.101:7001
# 通过命令查看集群状态:
redis-cli -p 7001 cluster nodes
7004 加入了集群,并且默认是一个 master 节点,可以看到 7004 节点的插槽数量为 0,因此没有任何数据可以存储到 7004 上。
转移插槽配置:
# 将num存储到7004节点,因此需要先看看num的插槽是多少
192.168.150.101:7003> get num
-> Redirected to slot [2765] located at 192.168.150.101:7001 # num的插槽为 2765
"123"
将 0~3000 的插槽从 7001 转移到 7004
[root@localhost tmp]redis-cli --cluster help
...
reshard host:port
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
...
[root@localhost tmp]redis-cli --cluster reshard 192.168.150.101:7001
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move(from 1 to 16384)? 3000 # 询问要移动多少个插槽,计划是3000个
What is the receiving node ID? # 哪个node来接收这些插槽,配置是7004,那么7004节点的id是多少呢
前面一串就是 7004 节点的 id
继续询问,你的插槽是从哪里移动过来的?
- all:代表全部,也就是三个节点各转移一部分
- 具体的id:目标节点的id
- done:没有了
命令查看结果
# # 通过命令查看集群状态
[root@localhost tmp]redis-cli -p 7001 cluster node
故障转移
之前 7001、7002、7003 都是 master,我们计划让 7002 宕机。
自动故障转移
# 直接停止一个redis实例,例如7002
redis-cli -p 7002 shutdown
-
首先是该实例与其它实例失去连接
-
然后是疑似宕机
-
最后是确定下线,自动提升一个 slave 为新的 master
-
当 7002 再次启动,就会变为一个 slave 节点
手动故障转移
&emsp利用 cluster failover
命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover
命令的这个 slave 节点,实现无感知的数据迁移。
这种 failover
命令可以指定三种模式:
- 缺省:默认的流程,如图 1~6 步。
- force:省略了对 offset 的一致性校验。
- takeover:直接执行第 5 步,忽略数据一致性、忽略 master 状态和其它 master 的意见。
例子:在 7002 这个 slave 节点执行手动故障转移,重新夺回 master 地位
步骤如下:
- 利用 redis-cli 连接7002这个节点
- 执行
cluster failover
命令
redis-cli -p 7002
> cluster failover
OK
RedisTemplate访问分片集群
RedisTemplate 底层同样基于 lettuce 实现了分片集群的支持,而使用的步骤与哨兵模式基本一致。
【Redis】内存数据库Redis进阶(Redis哨兵集群)
- 引入 Redis 的 starter 依赖
- 配置分片集群地址
- 配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异
spring:
redis:
cluster:
nodes:
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003