一、单节点 Redis 的问题
1.1 存在的问题
1、 数据丢失问题:Redis是内存存储,服务重启可能会丢失数据。
2、 并发能力问题:单节点 Redis 并发能力虽然不错,但也无法满足如 618这样的高并发场景。
3、 故障恢复问题:如果 Redis宕机,则服务不可用,需要一种自动的故障恢复手段。
4、 存储能力问题:Redis基于内存,单节点能存储的数据量难以满足海量数据需求
1.2 解决方案
二、Redis 持久化
2.1 RDB 持久化
2.1.1 简介
RDB 全称Redis Database Backup file (Redis 数据备份文件),也被叫做 Redis 数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当 Redis 实例故障重启后,从磁盘读取快照文件,恢复数据。 快照文件称为 RDB文件,默认是保存在当前运行目录。
执行 save命令会阻塞其他的命令,如下图:
执行 bgsave命令时则会开启子进程执行备份任务,如下图:
注意:Redis 正常停机时会执行一次 RDB,突然断电啥的则不会执行。
2.1.2 触发机制
Redis 内部有触发 RDB 的机制,可以在 redis.conf文件中找到,格式如下:
RDB 的其它配置也可以在redis.conf文件中设置,如下图:
2.1.3 底层实现
bgsave 开始时会 fork 主进程得到子进程,子进程共享主进程的内存数据。完成 fork 后读取内存数据并写入 RDB文件。
如下图,首先数据是存在在物理内存上的,而 redis 的主进程如果想要实现对 redis 数据的读写就需要在物理内存上进行读写,而在 linux环境下,所有的进程都无法直接操作物理内存,而是由操作系统给每个进程分配一个虚拟内存,即主进程只能操作虚拟内存,而操作系统会维护一个虚拟内存和物理内存之间的映射关系表,又称为页表。所以主进程操作虚拟内存,而虚拟内存基于页表的映射关系操作我们的物理内存,这样就实现了对物理内存的读写了。
而当我们执行 fork 操作时,会创建一个子进程,fork的过程中会把页表做一份拷贝,也就是把映射关系拷贝给子进程,子进程有了和主进程一样的映射关系,当子进程在操作自己的虚拟内存时,一定会操作和主进程一样的物理内存区域,就实现了子进程和主进程之间的内存共享了。
此时,当子进程执行备份操作时,就可以放心的读取自己内存的数据,其实读取的就是主进程的数据。之后再把数据写入到磁盘中新的文件中去。到这为止,异步持久化就实现了。
如果子进程在写 RDB 的过程中,主进程同时在修改数据,此时读与写之间就会有冲突,甚至出现脏数据。为了避免这种事情的发生,fork 的底层采用的是copy-on-write 技术,当主进程要写的时候做一次拷贝,现在的内存是共享的,没有做拷贝的。此时 fork 会把共享内存标记成 read-only 只读模式,任何一个进程都只能来读数据而不能写。假设此时用户真的来写数据了。此时先拷贝一份数据,再对拷贝的数据完成一份写操作。一旦完成了拷贝,主进程再做读操作的时候也要从拷贝的数据里面读取了,也就是说主进程的页面的映射关系发生了变化。这个就是 copy-on-write技术。
但是在某些极端的情况下,假设 RDB 写的过程比较慢,耗时较长。就在它写的过程中,不断的有新的请求进来,不断的修改共享的数据,导致所有的数据都被修改了一遍。就会导致所有的数据都需要拷贝一份新的,也就意味着 redis 对内存的占用翻倍了,这是极端的情况,几乎不可能发生,但是理论上有可能。因此 redis 都需要预留一部分内存空间。假设服务器内存为32G ,不能把 32G 都交给 redis,还需要预留一部分。
2.1.4 优缺点
RDB 方式 bgsave的基本流程
1、fork主进程得到一个子进程,共享内存空间
2、 子进程读取内存数据并写入新的 RDB文件
3、 用新 RDB 文件替换旧的 RDB文件。
RDB 会在什么时候执行?save 60 1000代表什么含义?
**1、**默认是服务停止时。
2、 代表 60 秒内至少执行 1000 次修改则触发 RDB
RDB的缺点?
1、RDB 执行间隔时间长,两次 RDB之间写入数据有丢失的风险
2、fork 子进程、压缩、写出 RDB文件都比较耗时
2.2 AOF 持久化
2.2.1 简介
AOF 全称为Append Only File (追加文件)。Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件。其中**$3** 表示下面的字母的个数。
2.2.2 触发机制
AOF 默认是关闭的,需要修改 redis.conf 配置文件来开启 AOF:
AOF 的命令记录的频率也可以通过redis.conf文件来配:
2.2.3 底层机制
因为是记录命令,AOF 文件会比 RDB 文件大的多。而且 AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义。可以通过执行 bgrewriteaof 命令,可以让 AOF文件执行重写功能,用最少的命令达到相同效果。
Redis 也会在触发阈值时自动去重写 AOF 文件。阈值也可以在redis.conf中配置:
2.3 RDB 和 AOF 对比
RDB 和 AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
三、Redis 主从
3.1 搭建主从架构
3.1.1 简介
单节点 Redis 的并发能力是有上限的,要进一步提高 Redis的并发能力,就需要搭建主从集群,实现读写分离。
3.1.2 集群架构
我们搭建的主从集群结构如图:
共包含三个节点,一个主节点,两个从节点。这里我们会在同一台虚拟机中开启 3 个 redis实例,模拟主从集群,信息如下:
|-----------------|----------|--------|
| IP | PORT | 角色 |
| 192.168.229.175 | 7001 | master |
| 192.168.229.175 | 7002 | slave |
| 192.168.229.175 | 7003 | slave |
3.1.3 准备实例和配置
首先将 Redis 安装包上传到虚拟机的**/tmp**目录下,然后执行解压缩命令:
bash
tar -xzf redis-6.2.4.tar.gz
然后进入redis目录:
bash
cd redis-6.2.4
运行编译命令:
bash
make && make install
如果没有出错,应该就安装成功了。
要在同一台虚拟机开启 3 个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。我们创建三个文件夹,名字分别叫 7001 、7002 、7003,如下图:
bash
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir 7001 7002 7003
然后将redis-6.2.4/redis.conf 文件拷贝到三个目录中(在 /tmp目录执行下列命令):
bash
# 方式一:逐个拷贝
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
# 方式二:管道组合命令,一键拷贝
echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf
修改每个文件夹内的配置文件,将端口分别修改为 7001 、7002 、7003 ,将 rdb 文件保存位置都修改为自己所在目录(在**/tmp**目录执行下列命令):
bash
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf
虚拟机本身有多个 IP ,为了避免将来混乱,我们需要在redis.conf 文件中指定每一个实例的绑定ip 信息,每个目录都要改,我们一键完成修改(在 /tmp 目录执行下列命令):
bash
# 逐一执行
sed -i '1a replica-announce-ip 192.168.229.175' 7001/redis.conf
sed -i '1a replica-announce-ip 192.168.229.175' 7002/redis.conf
sed -i '1a replica-announce-ip 192.168.229.175' 7003/redis.conf
# 或者一键修改
printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.229.175' {}/redis.conf
3.1.4 启动
为了方便查看日志,我们打开3 个 ssh 窗口,分别启动 3 个 redis实例,启动命令:
bash
# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf
如果要一键停止,可以运行下面命令:
bash
printf '%s\n' 7001 7002 7003 | xargs -I{} -t redis-cli -p {} shutdown
3.1.5 开启主从关系
现在三个实例还没有任何关系,要配置主从可以使用 replicaof 或者 slaveof (5.0以前)命令。有临时和永久两种模式:
如果想要永久生效,就需要修改配置文件,在 redis.conf中添加一行配置,内容如下:
bash
# 在从节点上配置主节点的 ip 和端口号
slaveof <masterip> <masterport>
如果想要重启后失效,则使用 redis-cli 客户端连接到 redis 服务,执行 slaveof命令,如下:
bash
# 在从节点上配置主节点的 ip 和端口号
slaveof <masterip> <masterport>
注意:在5.0 以后新增命令 replicaof ,与 salveof效果一样。这里我们为了演示方便,使用方式二。
通过 redis-cli 命令连接 7002,执行下面命令:
bash
# 连接 7002
redis-cli -p 7002
# 执行slaveof
slaveof 192.168.150.101 7001
通过redis-cli 命令连接 7003,执行下面命令:
bash
# 连接 7003
redis-cli -p 7003
# 执行slaveof
slaveof 192.168.150.101 7001
然后连接 7001节点,查看集群状态:
bash
# 连接 7001
redis-cli -p 7001
# 查看状态
info replication
结果如下:
3.1.6 测试
1、 利用redis-cli 连接 7001 ,执行 set num 123
2、 利用 redis-cli 连接 7002 ,执行 get num ,再执行 set num 666
3、 利用redis-cli 连接 7003 ,执行 get num ,再执行 set num 888
可以发现,只有在 7001 这个 master 节点上可以执行写操作,7002 和 7003 这两个 slave节点只能执行读操作。
3.1.7 小结
假设有 A 、B 两个 Redis 实例,如何让B 作为A 的 slave节点?
在 B 节点执行命令:slaveof A 的 IP A 的 port即可。
3.2 主从数据同步原理
当我们在主节点执行写操作的时候,从节点也可以看到写入的数据,那么这种数据同步是如何实现的呢?接下来来研究下。
3.2.1 全量同步
redis 的主从第一次同步是全量同步,以下图为例,当从节点和主节点第一次建立连接时,需要执行 replicaof 命令并指定主节点的 ip 和端口,这个就是和主节点建立连接的过程。建立连接之后,slave 就向 master 发送请求数据同步的请求,master 会对请求进行判断看其是否为第一次同步,如果是第一次来则先向 slave 节点发送 master 的数据版本信息,slave先保存下来,此时完成了第一阶段,如下图:
此时 master 节点执行 bgsave 命令,生成 RDB 文件,将此文件发送给 slave 节点,此时 slave 节点清空本地数据,加载新的 RDB 文件,此时就可以确保 master 和 slave 节点的数据基本一致。在生成 RDB 的过程中,master 将新写入的命令记录到 repl_baklog (内存缓冲区)文件中,此时第二阶段完成,如下图:
接下来就是将缓冲区里面的命令发送给 slave 节点,slave拿到命令后执行即可,此时第三阶段完成,如下图:
因为这种方式有个 RDB 的过程,所以叫全量同步,比较耗费性能,只在第一次建立连接的时候会同步。那么我们的 master 是如何知道 slave是第一次来呢?这里有两个重要的概念。
Replication Id :简称 replid ,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的 replid ,slave 则会继承 master 节点的 replid。
offset :偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset 。如果 slave 的 offset 小于 master 的 offset ,说明 slave 数据落后于 master,需要更新。
因此 slave 做数据同步,必须向 master 声明自己的replication id 和 offset ,master 才可以判断到底需要同步哪些数据。
此时的全量同步第一阶段就变成了下面的样子,slave 节点发送同步请求时就需要携带 replid 和 offset ,每一个节点在成为 slave 之前都是 master ,所以他一定有自己的 replid ,带着自己的来,然后和 master 一比,判断是否一致,只要不一致一定是第一次,此时就需要返回主节点的 replid 和 offset ,此时 slave保存下来即可。
简述全量同步的流程?
1、slave节点请求增量同步
2、master 节点判断 replid,发现不一致,拒绝增量同步
3、master 将完整内存数据生成 RDB ,发送 RDB 到 slave
4、slave 清空本地数据,加载 master 的 RDB
5、master 将 RDB 期间的命令记录在 repl_baklog ,并持续将 log 中的命令发送给 slave
6、slave 执行接收到的命令,保持与 master之间的同步。
3.2.2 增量同步
主从第一次同步是全量同步,但如果 slave重启后同步,则执行增量同步。
slave 节点重启之后,发送请求同步的命令,mater 判断请求的 replid 和自己一致,则回复给 slave 节点 continue ,此时 master 去 repl_baklog 中获取 offset 后的数据,发送给 slave ,slave执行相关的命令即可。
repl_baklog 本质是个数组,他比较特殊,大小固定,当他存储数据满了之后会覆盖以前的数据继续存储,只要 salve 和 master之间的差距不超过这个数组的存储上限,那么永远可以在这个环里面找到需要的数据,如果超过了存储上限,就只能做全量同步。
repl_baklog 大小有上限,写满后会覆盖最早的数据。如果 slave 断开时间过久,导致尚未备份的数据被覆盖,则无法基于 log做增量同步,只能再次全量同步。
3.2.3 集群优化
可以从以下几个方面来优化 Redis主从就集群:
1、 在 master 中配置repl-diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘 IO。
2、Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘 IO
3、 适当提高 repl_baklog 的大小,发现 slave宕机时尽快实现故障恢复,尽可能避免全量同步
4、 限制一个 master 上的 slave 节点数量,如果实在是太多 slave ,则可以采用主-从-从链式结构,减少 master压力,如下图:
3.2.4 小结
简述全量同步和增量同步区别?
1、 全量同步:master 将完整内存数据生成 RDB ,发送 RDB 到 slave 。后续命令则记录在 repl_baklog ,逐个发送给 slave。
2、 增量同步:slave 提交自己的 offset 到 master ,master 获取 repl_baklog 中从 offset 之后的命令给 slave。
什么时候执行全量同步?
1、slave 节点第一次连接 master节点时
2、slave 节点断开时间太久,repl_baklog 中的 offset已经被覆盖时
什么时候执行增量同步?
1、slave 节点断开又恢复,并且在 repl_baklog 中能找到 offset时
四、Redis 哨兵
4.1 哨兵的作用和原理
4.1.1 作用
Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
监控:Sentinel 会不断检查您的 master 和 slave是否按预期工作。
自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master 。当故障实例恢复后也以新的 master为主。
通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端(java 这边不知道 master 节点挂了,sentinel 会通知 java这边)。
4.1.2 原理
Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping命令:
主观下线:如果某 sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum )的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半( 3 台服务器则设置为 2 )。
4.1.3 选举机制
一旦发现 master 故障,sentinel 需要在 salve 中选择一个作为新的 master,选择依据是这样的:
1、 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10 )则会排除该 slave节点。
2、 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0 则永不参与选举。
3、 如果 slave-prority 一样,则判断 slave 节点的 offset值,越大说明数据越新,优先级越高。
4、 最后是判断 slave 节点的运行id大小,越小优先级越高。
4.1.4 故障转移
当选中了其中一个 slave 为新的 master 后(例如 slave1),故障的转移的步骤如下:
1、sentinel 给备选的 slave1 节点发送 slaveof no one 命令,让该节点成为 master
2、sentinel 给所有其它 slave 发送slaveof 192.168.150.101 7002 命令,让这些 slave 成为新 master 的从节点,开始从新的 master 上同步数据。
3、 最后,sentinel 将故障节点标记为 slave ,当故障节点恢复后会自动成为新的 master 的 slave节点。
4.2 搭建哨兵集群
4.2.1 集群架构
这里我们搭建一个三节点形成的 Sentinel 集群,来监管之前的 Redis主从集群。如图:
三个 sentinel实例信息如下:
|--------|-----------------|----------|
| 节点 | IP | PORT |
| s1 | 192.168.229.175 | 27001 |
| s2 | 192.168.229.175 | 27002 |
| s3 | 192.168.229.175 | 27003 |
4.2.2 准备实例和配置
要在同一台虚拟机开启3 个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。我们创建三个文件夹,名字分别叫 s1 、s2 、s3:
bash
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3
然后我们在 s1 目录创建一个 sentinel.conf文件,添加下面的内容:
bash
# 是当前 sentinel 实例的端口
port 27001
sentinel announce-ip 192.168.229.176
# 指定主节点信息
# mymaster:主节点名称,自定义,任意写
# 192.168.150.101 7001:主节点的ip和端口
# 2:选举master时的quorum值
sentinel monitor mymaster 192.168.229.176 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"
然后将 s1/sentinel.conf 文件拷贝到 s2 、s3 两个目录中(在 /tmp 目录执行下列命令):
bash
# 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
# 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf
修改s2、s3 两个文件夹内的配置文件,将端口分别修改为 27002 、27003:
bash
sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf
4.2.3 启动
为了方便查看日志,我们打开3 个 ssh 窗口,分别启动 3 个 redis实例,启动命令:
bash
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf
启动后:
4.2.4 测试
尝试让 master 节点 7001 宕机,查看 sentinel日志:
查看 7003的日志:
查看 7002的日志:
4.3 RedisTemplate 的哨兵模式
4.3.1 简介
在 Sentinel 集群监管下的 Redis 主从集群,其节点会因为自动故障转移而发生变化,Redis 的客户端必须感知这种变化,及时更新连接信息。Spring 的 RedisTemplate 底层利用 lettuce实现了节点的感知和自动切换。
4.3.2 引入步骤
1、 在 pom 文件中引入 redis 的 starter依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、 然后在配置文件application.yml 中指定 sentinel相关信息:
bash
spring:
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群信息
- 192.168.229.175:27001
- 192.168.229.175:27002
- 192.168.229.175:27003
**3、**配置主从读写分离
java
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里的 ReadFrom 是配置 Redis的读取策略,是一个枚举,包括下面选择:
MASTER:从主节点读取
MASTER_PREFERRED :优先从 master 节点读取,master 不可用才读取 replica
REPLICA :从 slave (replica)节点读取
REPLICA _PREFERRED :优先从 slave (replica )节点读取,所有的 slave 都不可用才读取 master
五、Redis 分片集群
主从集群可以应对高并发读的问题,但是 redis 主从之间需要做同步,为了提高主从同步之间的性能,单节点 redis 的内存不能设置太高,如果过大那么在做 redis 的持久化或全量同步的时候会导致大量的 IO,性能会有一定的下降。
如果说单节点 redis 的内存降低了,那么如果我有海量的数据需要存储该怎么办呢?现在解决了高并发读的问题,那么高并发写的问题又该如何解决呢?此时就需要 redis的分片集群来解决了。
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题和高并发写的问题。
使用分片集群可以解决上述问题,分片集群特征:
1、 集群中有多个 master ,每个 master保存不同数据
2、 每个 master 都可以有多个 slave节点
3、master 之间通过 ping监测彼此健康状态
**4、**客户端请求可以访问集群任意节点,最终都会被转发到正确节点。
5.1 搭建分片集群
5.1.1 集群架构
分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含 3 个 master 节点,每个 master 包含一个 slave节点,结构如下:
这里我们会在同一台虚拟机中开启6 个 redis实例,模拟分片集群,信息如下:
|-----------------|----------|--------|
| IP | PORT | 角色 |
| 192.168.229.176 | 7001 | master |
| 192.168.229.176 | 7002 | master |
| 192.168.229.176 | 7003 | master |
| 192.168.229.176 | 8001 | slave |
| 192.168.229.176 | 8002 | slave |
| 192.168.229.176 | 8003 | slave |
5.1.2 准备实例和配置
删除之前的 7001 、7002 、7003 这几个目录,重新创建出 7001 、7002 、7003 、8001 、8002 、8003目录:
bash
# 进入/tmp目录
cd /tmp
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003
在**/tmp** 目录下准备一个新的redis.conf文件,内容如下:
bash
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.229.176
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log
将这个文件拷贝到每个目录下:
bash
# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
修改每个目录下的 redis.conf ,将其中的 6379修改为与所在目录一致:
bash
# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
5.1.3 启动
因为已经配置了后台启动模式,所以可以直接启动服务:
bash
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
查看集群状态,发现服务都已经正常启动,如下图:
如果要关闭所有进程,可以执行命令:
bash
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
5.1.4 创建集群
虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。我们需要执行命令来创建集群,命令如下:
bash
# redis-cli --cluste:代表集群操作命令
# create:代表是创建集群
# --replicas 1 或者 --cluster-replicas 1 :指定集群中每个 master 的副本个数为1,此时节点总数 ÷ (replicas + 1)` 得到的就是 master 的数量。
# 因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master
redis-cli --cluster create --cluster-replicas 1 192.168.229.176:7001 192.168.229.176:7002 192.168.229.176:7003 192.168.229.176:8001 192.168.229.176:8002 192.168.229.176:8003
运行后的样子:
通过命令可以查看集群状态:
bash
redis-cli -p 7001 cluster nodes
5.1.5 测试
尝试连接 7001节点,存储一个数据:
bash
# 连接
# 集群操作时,需要给 redis-cli 加上 -c 参数才可以
redis-cli -c -p 7001
# 存储数据
set num 123
# 读取数据
get num
# 再次存储
set a 1
5.2 散列插槽
5.2.1 简介
Redis 会把每一个 master 节点映射到 0~16383 共 16384 个插槽(hash slot)上,查看集群信息时就能看到:
假设此时要存储一个数据到集群上,比如 set num 123 ,那这个 num 应该存储到哪一个 master 上呢?既然每一个 master都可以存储数据,那应该存到哪里呢?如果随便挑一个去存,那么以后取的时候又该如何确定去哪里取呢?插槽就是来解决这个问题的。
在 redis 中数据 key 不是与节点绑定,而是与插槽绑定。redis 会根据 key的有效部分计算插槽值,分两种情况:
1、key 中包含 "{}",且 "{}" 中至少包含 1个字符,"{}" 中的部分是有效部分
2、key中不包含 "{}",整个 key 都是有效部分。
例如 key 是 num ,那么就根据 num 计算,如果是 {itcast}num ,则根据 itcast 计算。计算方式是利用 CRC16 算法得到一个 hash 值,然后对 16384 取余,得到的结果就是 slot值。
为什么我们的 key 要和插槽绑定而不和节点绑定?是因为 redis的主节点是可能出现宕机的情况的,也有可能出现集群增加节点或删除节点的情况,如果节点删除了,数据可能就丢了。如果和插槽绑定,当节点删除时,我们可以将对应的插槽转移到活着的节点上。
5.2.2 小结
Redis 如何判断某个 key应该在哪个实例?
1、 将 16384个插槽分配到不同的实例
2、 根据 key 的有效部分计算哈希值,对 16384取余
**3、**余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个 Redis实例?
1、 这一类数据使用相同的有效部分,例如 key 都以**{typeId}**为前缀
5.3 集群伸缩
5.3.1 简介
作为一个分片集群,有一个非常重要的功能就是集群伸缩,也就是说这个集群必须可以动态的增加或者删除节点。
redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:
比如添加节点的命令,后面需要添加新的节点 ip 和端口号,后面还要添加一个已经存在的集群的ip 和端口号,因为向集群中添加节点需要通知集群中每一个角色。后面还可以加一些选项,如果不加后面的选项,新增的节点默认就是 master节点。
5.3.2 添加节点
需求描述:向集群中添加一个新的 master 节点,并向其中存储 num = 10,详细要求如下:
1、 启动一个新的 redis 实例,端口为 7004
2、 添加 7004 到之前的集群,并作为一个 master节点
3、 给 7004 节点分配插槽,使得 num 这个 key 可以存储到 7004实例
首先启动一个新的 redis实例,如下:
bash
[root@localhost tmp]# mkdir 7004
[root@localhost tmp]# cp redis.conf 7004
[root@localhost tmp]# sed -i s/6379/7004/g 7004/redis.conf
[root@localhost tmp]#
[root@localhost tmp]# redis-server 7004/redis.conf
[root@localhost tmp]# ps -ef | grep redis
root 86414 1 0 01:00 ? 00:00:09 redis-server 0.0.0.0:7001 [cluster]
root 86416 1 0 01:00 ? 00:00:08 redis-server 0.0.0.0:7002 [cluster]
root 86422 1 0 01:00 ? 00:00:08 redis-server 0.0.0.0:7003 [cluster]
root 86428 1 0 01:00 ? 00:00:08 redis-server 0.0.0.0:8001 [cluster]
root 86434 1 0 01:00 ? 00:00:08 redis-server 0.0.0.0:8002 [cluster]
root 86440 1 0 01:00 ? 00:00:08 redis-server 0.0.0.0:8003 [cluster]
root 87356 1 0 02:00 ? 00:00:00 redis-server 0.0.0.0:7004 [cluster]
root 87369 84683 0 02:00 pts/1 00:00:00 grep --color=auto redis
[root@localhost tmp]#
执行下面的命令,将 7004加入到集群之中
bash
redis-cli --cluster add-node 192.168.229.176:7004 192.168.229.176:7001
但是,此时的 7004上是没有任何的插槽的,如下图:
因为我们要set num 到 7004 上面,此时就需要将 7001 上面的插槽分配到 7004 上一部分,我们先看下 num 的 slot值是多少,如下图:
此时可以看到 num 的插槽值为 2765 ,也就是说只需要把 2765 这个插槽分配到 7004上面即可,所以此时就需要做插槽分配,执行下面的命令:
bash
redis-cli --cluster reshard 192.168.229.176:7001
此时输入下面的命令,再次查看节点的信息,可以看到 7004上面也有插槽信息了
bash
redis-cli -p 7001 cluster nodes
此时再次查看 num 的信息,可以看到,存储到了 7004节点上了
5.3.3 删除节点
接下来我们将刚才添加的 7004 节点删除掉,执行下面的命令执行删除操作
没有删除成功,首先我们先转移 7004的插槽,命令如下:
转移完成后,再次尝试删除节点信息,可以到了,删除成功了。
5.4 故障转移
5.4.1 自动故障转移
当集群有一个 master宕机会发生什么呢?首先是该实例与其它实例失去连接,然后是疑似宕机,如下图:
最后是确定下线,自动提升一个 slave 为新的 master:
这种方式成为自动故障转移,一台 master 意外宕机了,自动的去选择一台新的主机
5.4.2 手动故障转移
有时候我们还需要做手动的故障转移,比如说设备升级等。
利用cluster failover 命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的这个 slave节点,实现无感知的数据迁移。其流程如下:
手动的 Failover支持三种不同模式:
1、 缺省:默认的流程,如图 1~6歩
2、force :省略了对 offset的一致性校验
3、takeover :直接执行第5 歩,忽略数据一致性、忽略 master 状态和其它 master 的意见
5.5 RedisTemplate 访问分片集群
RedisTemplate 底层同样基于 lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1、 在 pom 文件中引入 redis 的 starter依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、 然后在配置文件application.yml 中指定 cluster相关信息:
bash
spring:
redis:
cluster:
nodes: # 指定分片集群的每一个节点信息
- 192.168.229.176:7001
- 192.168.229.176:7002
- 192.168.229.176:7003
- 192.168.229.176:8001
- 192.168.229.176:8002
- 192.168.229.176:8003
**3、**配置读写分离
java
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里的 ReadFrom 是配置 Redis的读取策略,是一个枚举,包括下面选择:
MASTER:从主节点读取
MASTER_PREFERRED :优先从 master 节点读取,master 不可用才读取 replica
REPLICA :从 slave (replica)节点读取
REPLICA _PREFERRED :优先从 slave (replica )节点读取,所有的 slave 都不可用才读取 master