一、单机Redis
数据库分类:
到目前为止,所有数据库分为两大类:
RDBMS:关系型数据库,即传统数据库。
像MySQL、SQLServer、DB2、Sybase、SQLite、Oracle等等
以表的形式存储数据,表与表之间要维护数据的关系
这一类数据库有非常严谨的事务,所以可以保证数据的一致性和完整性
NoSQL:泛指一切非关系数据库,
有非常非常多不同结构的数据库,数据模式非常灵活,
例如:
Redis:key-value结构的数据库
Hbase:列式存储数据库
MongoDB:文档型数据库
Neo4J:图数据库,擅长表示拓扑关系
1. 问题说明
Redis:是一个key-value结构的NoSQL,为了进一步提升性能,Redis把数据存储到内存里。所以Redis有极高的读写速度。官方数据 读11万次/秒, 写8.1万次/秒
在实际开发中,Redis通常作为缓存使用
之前我们在使用Redis的时候,全部使用的是Redis的单机模式。因为单机模式Redis搭建简单、使用方便,利于学习入门。但是在实际使用中,单机Redis存在一些问题需要解决:
持久化存储问题
Redis为了提高性能,数据是在内存中存储的。而内存不能持久化存储数据,所以Redis提供了持久化机制
并发能力问题
单机Redis的并发能力有限,可以通过主从集群来实现读写分离,从而提高并发能力
故障恢复问题
在Redis集群中,一旦某个节点宕机,就必须要手动进行故障转换与恢复。Redis提供了哨兵模式来实现自动故障恢复
存储能力问题
无论是主从模式的集群,还是哨兵模式的集群,都没有解决存储能力的扩展问题。Redis提供了分片集群模式,可以很方便的动态扩容
2. 安装Redis
提示:虚拟机里,我们都以root帐号登录CentOS7
提示:虚拟机里,先关闭防火墙,再做后边的操作
关闭防火墙:
systemctl stop firewalld
防火墙 取消开机自启:
systemctl disable firewalld
2.1 解压安装Redis
把资料里的《redis-6.2.4.tar.gz》上传到Linux的
/root
目录下执行命令安装redis:
#1. 安装Redis需要的依赖程序包 yum install -y gcc tcl #2. 切换到root用户的家目录 cd ~ #3. 解压Redis程序包 tar -xvf redis-6.2.4.tar.gz #4. 切换到Redis目录里 cd ~/redis-6.2.4/ #5. 编译并安装Redis。如果此命令不出错,通常就表示安装成功了 make && make install
2.2 配置Redis
#1. 在root家目录里创建文件夹 mkdir ~/01standalone #2. 把Redis配置文件拷贝进来 cp ~/redis-6.2.4/redis.conf ~/01standalone/ #3. 切换到~/01standalone/目录里 cd ~/01standalone/ #4. 使用vi编辑redis.conf vi redis.conf
使用vi修改redis.conf的如下内容: n命令 搜索名
# 绑定地址,默认是127.0.0.1,只能在本机访问Redis服务。修改为0.0.0.0则可以在任意IP访问Redis服务 bind 0.0.0.0 # 数据库数量,设置为1 databases 1
2.3 启动Redis
所有单机问题,我们全在
~/01standalone/
目录里操作,使用这个目录里的配置文件
首先:
cd ~/01standalone/
启动Redis服务:
redis-server redis.conf
使用Redis:
redis-cli
,然后就可以使用Redis的命令了关闭Redis服务:
redis-cli shutdown
💡Redis服务安装好以后,可以同时启动多个实例,只要加载不同的配置文件,使用不同的端口即可
3. 小结
1. 请介绍一下Redis
Redis是key-value结构的NoSQL,为了提升数据的读写速度,把数据存储到了内存中
Redis在实际开发中,通常作为缓存来使用2. 单机Redis有哪些问题
数据持久化存储的问题:因为Redis的数据在内存中,当Redis服务关闭时要避免数据被清空释放掉
单机Redis并发能力有限:可以搭建Redis主从集群,实现读写分离
要解决故障恢复的问题:要能够实现主节点故障后自动恢复
要解决存储容量的问题:可以搭建分片集群提升存储容量
二、Redis持久化
1. 持久化机制介绍
所谓持久化,指的是数据的永久保存。
Redis是一个内存数据库,它的所有数据都在内存中,所以有极高的读写性能,
但是内存中的数据全部是临时的,所以一旦Redis进程结束,所有数据就会清空了。
为了防止这样的问题,Redis提供了持久化机制:
RDB模式:快照模式
AOF模式:日志模式
注意:在后续的所有演示中,只要修改了配置文件,就必须要重启redis服务
2. RDB模式
RDB,Redis Database Backup file,称为Redis数据快照。
当执行RDB持久化时,会把Redis内存中的数据全部都保存到磁盘文件上;
当RDB恢复数据时,会读取磁盘文件,把文件里所有的数据恢复到内存中。
快照文件,也称为RDB文件,默认保存在当前运行目录
(在哪个目录里启动Redis服务,就保存在哪个目录)
1 RDB执行时机
在以下情况下,会执行RDB快照持久化:
执行了
save
命令执行了
bgsave
命令Redis服务关闭时
触发自动RDB的条件时
1 save命令
说明
当执行save命令时,Redis会执行一次RDB持久化。
但是save命令是一个阻塞式命令:
它由Redis主进程执行RDB持久化,在持久化过程中Redis将不能处理客户端的操作,直到RDB执行完毕
如果Redis中数据非常多,持久化将会花费比较长的时间,Redis也将会阻塞比较长的时间
目前,
save
命令已经很少使用了,通常使用bgsave
代替它示例:
使用redis-cli连接Redis服务之后,执行命令:
save
,就会执行一次RDB持久化[root@ithma ~]# redis-cli 127.0.0.1:6379> save OK 127.0.0.1:6379>
退出redis-cli,查看一下有没有RDB文件:
127.0.0.1:6379> exit [root@itma 01standalone]# ls dump.rdb redis.conf
2 bgsave命令
说明
bgsave,指background save。
当执行
bgsave
命令时,将会执行一次RDB持久化。而bgsave是非阻塞的、
异步式的:
Redis会fork一个子进程,由子进程负责RDB持久化
而主进程仍然可以处理客户端的操作,使用体验更好
示例
使用redis-cli连接Redis服务之后,执行命令:
bgsave
,就会执行一次RDB持久化127.0.0.1:6379> bgsave Background saving started 127.0.0.1:6379>
退出redis-cli,查看一下有没有RDB文件:
127.0.0.1:6379> exit [root@ithiema 01standalone]# ls dump.rdb redis.conf
3 Redis服务关闭
当结束Redis服务时,Redis会先执行一次save命令,把内存中的数据进行RDB持久化。 关闭Redis服务的方式有很多,可以: * 在redis-server服务进程中,按`ctrl + c`,立即结束redis服务 * 在redis-cli客户端中,执行命令`shutdown`,关闭redis服务 * 或者直接执行Linux命令`redis-cli shutdown`命令
4 RDB自动触发
Redis的RDB模式提供了默认的自动触发机制,
相关的配置参数在配置文件
redis.conf
中。默认值如下:
如果3600秒内有1次数据变更,就执行一次RDB
如果300秒内有100次数据变更,就执行一次RDB
如果60秒内有10000次数据变更,就执行一次RDB
save 3600 1;save 300 100;save 60 10000
提示:如果写成
save ""
,表示禁用RDB2 RDB的其它参数
在Redis的配置文件redis.conf中,还有一些RDB相关的参数,我们可以了解一下
rdbcompression yes dbfilename dump.rdb dir ./
rdbcompression:是否开启RDB文件压缩
如果参数值为yes,Redis将会对rdb文件进行压缩,最终rdb文件会更小
占用磁盘空间更小,但是压缩与解压时会消耗CPU
dbfilename:RDB文件名称
自定义RDB文件名称
dir:工作路径
RDB文件保存的路径,默认是
./
3 RDB执行原理
默认情况下,Redis服务只有一个进程,由这个进程负责处理客户端的一切操作请求。
当执行
bgsave
命令时,Redis会fork一个子进程出来,实现异步的RDB:
由子进程负责执行RDB,
而主进程继续负责处理客户端的操作请求。
虽然子进程执行RDB持久化的过程中,并不会对主进程造成影响,所以主进程不会阻塞,
可以继续处理客户端的操作请求。
但是还有一些新问题需要考虑:
fork子进程,也需要一定的时间。但这个时间相对于RDB持久化而言,就非常短暂了,只需要几十毫秒多则1秒钟的时间
如果子进程执行RDB的过程中,客户端有新的操作改变了数据,为了避免这些新改变的数据对RDB过程造成影响,Redis采用了copy-on-write技术:
当主进程执行读操作时,访问共享内存;
当主进程执行写操作时,则会拷贝一份数据,执行写操作。
3. AOF模式
AOF,Append Only File。
当执行AOF持久化时,会把执行的每个数据变更的命令追加保存到磁盘文件里。
当AOF恢复数据时,会读取磁盘文件,按顺序依次执行所有的命令,恢复数据。
1 开启AOF
Redis的AOF默认是关闭状态的,如果要开启AOF模式,
则需要:
修改
redis.conf
配置文件appendonly yes # AOF文件的名称 appendfilename "appendonly.aof"
appendonly
:AOF模式的开关。如果值是yes,表示开启;如果是no,表示关闭
appendfilename
:AOF文件的名称。默认是appendonly.aof
,可以自定义文件名重启Redis服务
先关闭Redis服务,执行命令:
redis-cli shutdown
再重启Redis服务,执行命令:
redis-server redis.conf
2 AOF执行时机
开启AOF模式之后,在redis.conf中配置有的AOF持久化执行时机,
可通过
appendfsync
参数进行配置#appendfsync always appendfsync everysec #appendfsync no
如果值为
always
:每次的写数据的命令,就立即追加到aof文件中如果值为
everysec
:每次的写命令先放到AOF缓冲区,然后以每秒一次的频率,把缓冲区里的命令保存到aof文件
如果值为
no
:每次的写命令先放到AOF缓冲区,然后完全由操作系统决定何时把缓冲区里命令保存到aof文件
三种时机的对比:
配置项 刷新时机 优点 缺点 always 同步刷盘 可靠性高,几乎不丢数据· 对性能影响大 everysec 每秒刷盘 性能适中 最多丢失1秒的数据 no 由操作系统控制 性能最好 可靠性较差,可能丢失大量数据 注意:只要修改了配置文件,就必须要重启redis服务3 AOF文件重写
1.AOF冗余记录
因为AOF保存的是写命令,类似于日志文件,
所以通常情况下AOF日志文件都比RDB文件大,
且随着使用时间的推移,AOF文件通常会越来越大,
从而导致:
磁盘空间的占用越来越大
恢复数据的速度越来越慢
主要原因在于,AOF忠实的记录每次写操作的命令,就可能有大量重复的命令;
比如对同一key有多次修改操作,每次修改操作都会记录到AOF文件里,
而实际上只有最后一次操作命令才有效。
假如先后执行以下命令:set num 1; set num 10; set num 100 ;set num 1
那么AOF文件里会保存4条命令,实际上只需要最后一条命令就可以了
2.bgrewriteaof重写
手动执行AOF重写
通过在redis-cli中执行
bgrewriteaof
命令,可以让AOF文件执行重写:删除无效的命令。例如:
set a A
,set a B
,del a
三个命令,其实全部都可以删除掉合并多余的命令。例如:
自动触发AOF重写
Redis也会在触发阈值时自动去重写AOF文件,阈值的配置在
redis.conf
文件中auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-min-size
:AOF文件必须大于这个值,才可能触发AOF重写
auto-aof-rewrite-percentage
:当AOF文件与上次重写后的文件对比,超过这个百分比后,会触发AOF重写4. RDB和AOF对比
RDB和AOF两种持久化模式各有各的优缺点。在实际开发中往往是两者结合使用
对比项 RDB AOF 持久化方式 定时对整个内存的数据做快照 记录每次执行的写命令 数据完整性 两次备份之间数据可能丢失 丢失数据的可能性较小,取决于刷盘策略 文件大小 相对较小,且有RDB压缩 记录命令,文件相对较大 恢复速度 很快 慢 恢复时优先级 低 高 系统资源占用 高,大量CPU和内存消耗 低,主要是磁盘IO较高 但AOF重写时会占用较高的CPU和内存 使用场景 可能容忍数分钟内的数据丢失, 追求更快的启动速度 对数据安全性要求较高时使用 5. 小结
1. Redis是如何解决持久化问题的,介绍一下Redis的两种持久化模式
Redis提供了两种持久化机制,分别是RDB和AOF
RDB:叫快照模式。Redis会把内存里的数据,保存到磁盘文件上。当重启Redis的时候,加载文件恢复数据到内存
AOF:叫日志模式。2. Redis什么时候会执行RDB
手动执行save命令:用的不多,因为是阻塞式的命令。在RDB过程中,Redis不能处理客户端的操作请求
手动执行bgsave命令:用的较多,因为是非阻塞式的命令。会开启一个子进程,由子进程负责RDB;主进程处理客户端的操作请求
关闭Redis服务的时候:redis会自动RDB之后,才会退出
RDB的自动触发:在配置文件里有触发的时机3. 说一下RDB的原理 面试
当执行bgsave命令的时候,Redis做了:
fork一个子进程,把主进程的页表复制到子进程里,会把物理内存的数据设置为read-only只读
子进程:根据页表访问物理内存里的数据,把这些数据保存到磁盘文件上
主进程:可以继续处理客户端的请求
如果客户端要改数据:会把数据复制一个副本,修改这个副本数据
如果客户端要读数据:让它读取副本的新数据
4. AOF的文件过大,Redis是怎么解决的
重写AOF文件,有两种实现方式
手动重写AOF文件:执行命令bgrewriteaof
自动重写AOF文件:在redis.conf里有配置阈值 aof文件超过64M,并且比上次重写时文件大小翻一倍,就重写
5. RDB和AOF对比,各有什么优缺点
丢数据的可能性:RDB丢数据的可能性更高
数据的可靠安全:AOF更安全
使用的时候:如果追求更快的速度,并且能够容忍一些数据的丢失,就选择RDB;否则就使用AOF
三、Redis主从模式
1. 介绍
单节点的Redis服务支持的并发是有限的:
所有客户端的请求全部冲击到仅有的一台服务器上,
服务器可能因为压力过大而阻塞不能及时响应。
这时候可以采用主从架构
做到读写分离:
多个Redis节点共同提供服务,所有写数据的操作都请求到主服务器,
所有读数据的操作都请求到从服务器。读写分离,提升并发能力
2. 搭建Redis主从架构【备用】
1 架构说明
我们搭建的Redis主从集群结构,共包含三个节点。其中一个主节点,两个从节点
我们将会在同一个虚拟机里开启三个Redis实例,模拟主从集群,信息如下:
角色ip端口如图所示:
角色 ip 端口 master 192.168.126.120 6380 slave 192.168.126.120 6381 slave 192.168.126.120 6382
我们创建三个文件夹
#1. 准备一个文件夹02master,把主从集群所有相关的配置全放到这个文件夹里 mkdir ~/02master #2. 在02master里准备三个文件夹,作为三个redis实例的工作目录 cd ~/02master/ mkdir 6380 6381 6382 #3. 把Redis的配置文件,分别拷贝到6380,6381,6382三个文件夹里。 echo 6380 6381 6382 | xargs -t -n 1 cp ~/redis-6.2.4/redis.conf #4. 分别修改6380、6381、6382三个文件夹里的配置文件 # 把端口分别修改为6380、6381、6382 # 把配置文件里的dir,全部修改为各自的工作目录 cd ~/02master sed -i -e 's/6379/6380/g' -e 's/dir .\//dir \/root\/02master\/6380\//g' -e 's/bind 127.0.0.1 -::1/bind 0.0.0.0/g' 6380/redis.conf sed -i -e 's/6379/6381/g' -e 's/dir .\//dir \/root\/02master\/6381\//g' -e 's/bind 127.0.0.1 -::1/bind 0.0.0.0/g' 6381/redis.conf sed -i -e 's/6379/6382/g' -e 's/dir .\//dir \/root\/02master\/6382\//g' -e 's/bind 127.0.0.1 -::1/bind 0.0.0.0/g' 6382/redis.conf #5. 修改每个Redis实例的ip # 虚拟机本身有多个虚拟网卡,有多个ip地址。为了防止地址混乱,我们直接在配置文件里指定要绑定的ip地址 # 6380、6381、6382三个文件夹里的配置文件都要修改,执行以下命令 cd ~/02master printf '%s\n' 6380 6381 6382 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.200.136' {}/redis.conf
3 启动集群
为了方便查看每个Redis实例的日志,我们开启三个ssh容器,分别启动3个redis实例。
命令如下:
# 第一个Redis实例 redis-server ~/02master/6380/redis.conf # 第二个Redis实例 redis-server ~/02master/6381/redis.conf # 第三个Redis实例 redis-server ~/02master/6382/redis.conf
附加:如果想要一键停止所有Redis实例,可以执行以下命令:
printf '%s\n' 6380 6381 6382 | xargs -I{} -t redis-cli -p {} shutdown
4 开启主从关系
到目前为止,三个Redis实例之间还没有任何关系。
要配置主从关系,可以使用Redis提供的
replicaof
命令(Redis5.0开始)或者slaveof
命令(Redis5.0以前),两个命令效果一致。主从关系的配置有临时配置和永久配置两种
永久生效:修改配置文件,然后重启所有Redis服务
在redis.conf中增加一行配置:
slaveof masterIp masterPort
临时生效:使用redis-cli连接Redis服务,执行slaveof命令(Redis实例重启后失效)
slaveof masterIp masterPort
这里为了方便演示,我们使用方式二临时生效,将6380节点设置为master:
设置从节点:连接6381,执行命令
#连接6381实例 redis-cli -p 6381 #设置为6380的从节点 slaveof 192.168.200.123 380
设置从节点:连接6382,执行命令
#连接6382实例 redis-cli -p 6382 #设置为6380的从节点 slaveof 192.168.200.123 6380
连接主节点6380,查看集群状态
#连接6380实例 redis-cli -p 6380 #查看集群状态 info replication
5 测试
按顺序执行以下操作:
使用redis-cli连接6380,执行
set sum 100
使用redis-cli连接6381,执行
get num
, 然后再执行set num 101
使用redis-cli连接6382,执行
get num
,然后再执行set num 102
结果发现:
只有6380这个节点上可以进行写操作
6381和6382两个节点上只能进行读操作
3. 主从数据同步原理
1 全量同步
当主从第一次建立连接时,会执行全量同步:将master节点的所有数据都拷贝给slave节点,流程如下
但是这里有一个问题:Master如何判断 slave是否第一次连接呢?
我们需要先了解一个概念:
Replication Id:简称replid,由40位十六进制字符组成。每个Master节点启动时都会生成一个replid,而slave会继承其master的replid。
offset:偏移量,是一个数字,是记录的指令的位置,会随着记录在repl_baklog中的数据增多而增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave的数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据:
而slave原本也是一个master,有自己的replid和offset。当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。
完整流程描述:
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
2 增量同步
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
什么是增量同步?就是只更新slave与master存在差异的部分数据。如图:
3 repl_backlog原理
那么master怎么知道slave与自己的数据差异在哪里呢?
这就需要理解全量同步时的repl_backlog文件了
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_backlog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
注意:repl_backlog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过长,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次进行全量同步
4. 主从同步优化
主从同步可以保证主从数据的一致性,非常重要。
可以从以下几个方面来优化Redis主从就集群:
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
主从从架构图:
5. 小结
1. 怎么解决Redis的并发问题
搭建Redis的主从集群
主节点:可以读、写
从节点:只能读
在实际使用中:
通常是写数据,找主节点
通常是读数据,找从节点2. 主从节点之间数据是如何同步的
有两种同步方式:全量同步,增量同步
全量同步:master把自己的所有数据,全部发送给slave节点。同步速度慢
slave向master请求同步数据时,会带自己的replid
master判断:replid和自己的是否相同
如果不同:说明是第一次同步,要进行全量同步,会把master自己的replid发给slave存储起来
如果相同:说明之前同步过,要进行增量同步
增量同步:master把自己的新的数据(命令),发送给slave节点,slave节点执行命令,增量同步数据
slave向master发请求同步数据时,会带自己的replid
master判断:和自己的replid相同
master会把 上次同步之后,新的数据变更的命令,发送给slave;slave执行命令,实现增量同步
3. repl_baklog原理
数组在逻辑上以首尾相接的环形处理
master把写命令 不断的添加到环形里。如果添加了一圈了,就会把最旧的命令覆盖掉
slave不断从环形里获取落后的命令,同步到slave
如果slave同步的速度足够快:可以正常进行同步
如果slave因为网络原因或其它原因,导致长时间连接不上master,就会导致落后的过多,断开时间太长,会进行全量同步
4. 主从集群的优化
配置无磁盘同步,减少磁盘的IO
减少单个Redis节点的内存占用
适当提高repl_backlog的大小,减少全量同步的机会
一个master不在关联过多slave,如果slave确实多,可以使用主-从-从链式结构,减少master的压力
四、Redis哨兵模式
1. 介绍
Redis的主从模式,可以保证主从的数据一致性,并且可以实现读写分离,有效提升并发能力。
但是主从集群有一个缺陷:当Master宕机之后,需要人工干预进行故障恢复。
这时候,可以引入哨兵模式,实现集群故障的自动恢复
2. 哨兵模式的架构原理
哨兵模式的架构
哨兵的作用如下:
监控:Sentinel 会不断检查您的master和slave是否按预期工作
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
集群故障恢复原理
选举原则
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
首先会判断slave节点与master节点断开时间长短,
如果超过指定值(down-after-milliseconds * 10 )则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的replid大小,越小优先级越高。
切换Master
当选出一个新的master后,该如何实现切换呢?
流程如下:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
3. 搭建哨兵集群
架构说明
这里我们搭建一个三节点形成的Sentinel集群,来监管之前的Redis主从集群。如图
三个Sentinel实例信息如下:
节点 ip 端口 s1 192.168.126.123 16380 s2 192.168.126.123 16381 s3 192.168.126.123 16382 准备实例和配置
准备文件夹
要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。
先创建文件夹
~/03sentinel
文件夹,然后在~/03sentinel
里创建三个文件夹,名字分别叫s1、s2、s3:mkdir 03sentinel cd 03sentinel/ mkdir s1 s2 s3
准备sentinel配置文件
准备s1实例的配置
切换到s1文件夹里:
cd ~/03sentinel/s1
使用创建文件
sentinel.conf
:vi sentinel.conf
,添加如下内容:port 16380 sentinel announce-ip 192.168.126.123 sentinel monitor mymaster 192.168.126.123 6380 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 dir "/root/03sentinel/s1"
说明:
port 16380
: 当前Sentinel实例的端口号
sentinel monitor mymaster 192.168.126.129 6380 2
:指定主节点master的信息
mymaster
:主节点名称,任意写192.168.126.129
6380
:主节点的ip和端口
2
:选举时的quorum
值准备s2和s3实例的配置
#将`~/03sentinel/s1/sentinel.conf`拷贝到s2和s3两个目录中 cp ~/03sentinel/s1/sentinel.conf ~/03sentinel/s2 cp ~/03sentinel/s1/sentinel.conf ~/03sentinel/s3 #修改s2和s3的配置文件,将其端口分别修改为16381、16382 sed -i -e 's/16380/16381/g' -e 's/s1/s2/g' ~/03sentinel/s2/sentinel.conf sed -i -e 's/16380/16382/g' -e 's/s1/s3/g' ~/03sentinel/s3/sentinel.conf
启动
要使用Sentinel,我们必须保证刚才的Redis主从集群是启动运行状态,并且要保证6380是Master节点
如果Redis主从集群未启动,就执行以下命令:
启动三个Redis实例
redis-server ~/02master/6380/redis.conf redis-server ~/02master/6381/redis.conf redis-server ~/02master/6382/redis.conf
设置主从关系:把6380设置为master节点
redis-cli -p 6381
,然后执行命令slaveof 192.168.126.129 6380
redis-cli -p 6382
,然后执行命令slaveof 192.168.126.129 6380
redis-cli -p 6380
,然后执行命令查看集群状态info replication
确定6380是master节点,6381和6382是slave节点
为了方便查看日志,我们打开3个ssh客户端,分别启动3个redis实例,启动命令:
# 第1个 redis-sentinel ~/03sentinel/s1/sentinel.conf # 第2个 redis-sentinel ~/03sentinel/s2/sentinel.conf # 第3个 redis-sentinel ~/03sentinel/s3/sentinel.conf
测试
尝试让master节点6380宕机:
redis-cli -p 6380 shutdown
查看sentinel日志。发现已经有了新的master节点:6382
4. RestTemplate访问哨兵集群
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
导入资料里的《redis-cluster》工程,然后:
添加依赖坐标
添加spring-data-redis起步依赖
最终依赖如下:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.9.RELEASE</version> <relativePath/> </parent> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--Redis起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
准备配置文件
在引导类或者配置类里,添加以下代码,配置Redis集群的读写策略:
spring: redis: sentinel: master: mymaster #主节点的名称。在创建Sentinel集群时指定的 nodes: #Sentinel哨兵的地址 - 192.168.126.129:16380 - 192.168.126.129:16381 - 192.168.126.129:16382
配置读写策略
/** * 配置Redis的Sentinel集群读写策略 * ReadFrom.MASTER: 仅从master节点读数据 * ReadForm.REPLICA:仅从slave节点读数据 * ReadForm.MASTER_PREFERRED:优先从Master节点读数据 * ReadFrom.REPLICA_PREFERRED:优先从Slave节点读数据 */ @Bean public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){ return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); }
测试
创建测试类,读写数据
@SpringBootTest public class RedisSentinelTest { @Autowired private RedisTemplate<String,String> redisTemplate; @Test public void test(){ // redisTemplate.opsForValue().set("key::sentinel", "hello, sentinel"); String s = redisTemplate.opsForValue().get("key::sentinel"); System.out.println("s = " + s); } }
5. 小结
1. Redis主从集群的故障转移问题怎么解决 给主从集群进行增强,增加哨兵,使用哨兵模式 2. 哨兵模式中哨兵起了什么作用 监控:监控集群中所有节点的状态。靠心跳监控 主观下线:一个哨兵,在规定时间内一直ping不能某个节点,就认为它主观下线 客观下线:如果过半哨兵都认为这个节点主观下线,就标记成为客观下线 切换Master:哨兵会在Master失联之后,自动挑选一个slave提升成为Master 选举原则: 如果某些slave与master之间失联的时间超过配置的参数,排除这个节点,不参与选举 再根据节点的slave-priority优先级,值越小,优先级越高 再根据节点的offset值,值越大,说明它的数据同步的越完整,优先级越高 最后根据replid判断,越小,优先级越高 切换Master:当确定了某个节点要提升成为master之后 哨兵先向这个节点发出一个命令 slaveof no one,这个节点变成master 哨兵再向其它所有节点发出命令 slaveof 新的master节点 当旧的master重启之后,就只能成为slave了 通知:哨兵可以向客户端(Java程序)推送集群的地址 3. 哨兵模式中怎样进行集群监控的 4. 哨兵模式中如何进行故障恢复的 5. RedisTemplate如何访问Redis哨兵模式集群
五、Redis分片集群
1. 介绍
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
海量数据存储问题
高并发写的问题
使用分片集群可以解决上述问题
2. 分片集群的架构
分片集群特征:
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
3. 搭建分片集群
架构说明
分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:
这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:
ip 端口 角色 192.168.126.121 7380 master 192.168.126.121 7381 master 192.168.126.121 7382 master 192.168.126.121 8380 slave 192.168.126.121 8381 slave 192.168.126.121 8382 slave
准备实例和配置
准备文件夹
创建04cluster文件夹,并在文件夹里准备7380 7381 7382 8380 8381 8382六个文件夹
mkdir ~/04cluster cd ~/04cluster/ mkdir 7380 7381 7382 8380 8381 8382
准备配置文件准备7380的配置
切换到7380目录:
cd ~/04cluster/7380
使用vi编辑文件:
vi redis.conf
, 内容如下port 7380 # 开启集群功能 cluster-enabled yes # 集群的配置文件名称,不需要我们创建,由redis自己维护 cluster-config-file /root/04cluster/7380/nodes.conf # 节点心跳失败的超时时间 cluster-node-timeout 5000 # 持久化文件存放目录 dir /root/04cluster/7380 # 绑定地址 bind 0.0.0.0 # 让redis后台运行 daemonize yes # 注册的实例ip replica-announce-ip 192.168.126.121 # 保护模式 protected-mode no # 数据库数量 databases 1 # 日志 logfile /root/04cluster/7380/run.log
准备其它实例的配置#修改每个目录下的redis.conf,修改其端口: cd ~/04cluster # 执行修改 printf '%s\n' 7381 7382 8380 8381 8382 | xargs -I{} -t sed -i 's/7380/{}/g' {}/redis.conf #修改每个目录下的redis.conf,修改其端口: cd ~/04cluster # 执行修改 printf '%s\n' 7381 7382 8380 8381 8382 | xargs -I{} -t sed -i 's/7380/{}/g' {}/redis.conf
启动
#因为已经配置了Redis后台运行,所以可以直接启动,不需要开多个shell端口。执行以下命令: cd ~/04cluster #启动7380 7381 7382 8380 8381 8382 六个Redis服务 printf '%s\n' 7380 7381 7382 8380 8381 8382 | xargs -I{} -t redis-server {}/redis.conf #查看是否成功启动,执行命令:`ps -ef | grep redis`。如果看到以下结果,说明6个redis实例都启动成功
如果要关闭所有进程,可以执行命令:
方式一:
ps -ef | grep redis | awk '{print $2}' | xargs kill
方式二:
printf '%s\n' 7380 7381 7382 8380 8381 8382 | xargs -I{} -t redis-cli -p {} shutdown
创建集群
虽然服务已经成功启动,但目前6个Redis实例还是独立的,它们之间没有任何关系。
我们需要执行命令来创建集群。在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中
命令说明
redis5.0之前
Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境:
# 安装依赖 yum -y install zlib ruby rubygems gem install redis
然后通过命令管理集群:
# 进入redis的src目录 cd ~/redis-6.2.4/src # 创建集群 ./redis-trib.rb create --replicas 1 192.168.126.121:7380 192.168.126.121:7381 192.168.126.121:7382 192.168.126.121:8380 192.168.126.121:8381 192.168.126.129:8381
redis5.0开始
我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
redis-cli --cluster create --cluster-replicas 1 192.168.126.129:7380 192.168.126.121:7381 192.168.126.121:7382 192.168.126.121:8380 192.168.126.129:8381 192.168.126.121:8382
命令说明:
redis-cli --cluster
或者./redis-trib.rb
:表示要操作redis集群
create
:表示要创建集群
--cluster-replicas 1
或者--replicas
:指令集群中每个master的副本个数为1。这样:master节点的数量:节点总数 / (replicas + 1),得到的就是master节点的数量
节点列表中前n个就是master节点,其它是slave节点。这些slave随机分配给不同的master
创建集群
如果在执行下面的命令创建集群时报错:[ERR] Node 192.168.126.129:7380 is not empty. Either the node already knows other nodes
主要原因可能是:该节点默认生成的配置与历史存储的数据不一致导致的
解决的方法是:
关闭所有redis实例:
printf '%s\n' 7380 7381 7382 8380 8381 8382|xargs -I{} -t redis-cli -p {} shutdown
清除对应节点的dump.rdb、nodes.conf等文件
find / -name nodes.conf | xargs rm -rf find / -name dump.rdb | xargs rm -rf
然后重启redis实例,再执行创建集群的命令
cd ~/04cluster #启动7380 7381 7382 8380 8381 8382 六个Redis服务 printf '%s\n' 7380 7381 7382 8380 8381 8382 | xargs -I{} -t redis-server {}/redis.conf执行以下的命令,创建集群
redis-cli --cluster create --cluster-replicas 1 192.168.126.121:7380 192.168.126.121:7381 192.168.126.121:7382 192.168.126.121:8380 192.168.126.121:8381 192.168.126.121:8382需要我们确证一下,输入“yes“,就开始创建集群了:
开始创建集群
查看集群状态: redis-cli -p 7380 cluster nodes
测试
使用命令连接7380节点:
redis-cli -c -p 7380
,做如下操作:
存储数据:
set num 10
获取数据:
get num
再次存储:
set a 1
在使用redis-cli连接分片集群时,必须加上参数
-c
,否则操作时可能会出错。示例:尝试连接7380节点:
redis-cli -p 7380
,做如下操作:
存储数据:
set num 10
获取数据:
get num
再次存储:
set a 1
发现报错了:
4. 散列插槽
查看插槽命令:cluster nodes
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算;如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
提问:如何将一批key固定的保存在同一个Redis实例上呢?
答案:可以给这些数据的key,增加相同的
{标识}
。例如这些key都以{typeId}
为前缀5. 集群伸缩
因为Redis的分片集群,数据并没有与Redis节点直接进行绑定,而是:数据与插槽绑定,插槽与Redis节点绑定。这样就可以很方便的进行集群的伸缩,增加或减少Redis节点。
我们通过下面例子,演示一下给集群增加节点,需要如何实现。要求:向集群中添加一个新的master节点,并向其中存储num = 10
启动一个新的Redis实例,端口为7383
把7383节点添加到之前的集群,并作为一个master节点
给7383节点分配插槽,使得这个num可以存储到7383实例
这需要有两大步:
添加一个节点到集群中
转移插槽
创建7383节点
#创建7383文件夹 mkdir ~/04cluster/7383 #把配置文件拷贝到7383文件夹里 cp ~/04cluster/7380/redis.conf ~/04cluster/7383 #修改配置文件 sed -i s/7380/7383/g ~/04cluster/7383/redis.conf #启动7383实例 redis-server ~/04cluster/7383/redis.conf
添加到集群
查看redis集群操作帮助:
redis-cli --cluster help
执行命令:
#把7383节点添加到集群 redis-cli --cluster add-node 192.168.126.121:7383 192.168.126.121:7380 #查看集群状态 redis-cli -p 7380 cluster nodes
转移插槽
查看num的插槽
我们要将num存储到7383节点,因此需要先看看num的插槽是多少:
连接任意一个Redis节点:
redis-cli -c -p 7382
查询num的数据:
get num
,发现num的插槽是2765,在7380节点上我们可以将0~3000插槽,从7380节点移动到7383节点上
转移插槽
转换插槽的命令,操作如下:
建立连接:
redis-cli --cluster reshard 192.168.126.121:7380
输入要移动的插槽数量,我们输入 3000
输入目标节点的id(哪个节点要接收这些插槽,就输入哪个节点的id)
我们从刚刚的输出结果中,找到7383节点的id设置过来
从哪个节点里转移出插槽?
输入
all
,表示全部。即从现有每个master节点中各转移一部分出来输入节点id,表示从指定节点中转移出来
输入
done
,表示输入完毕了我们这里从7380节点转移出来一部分,要输入7380节点的id
输入
yes
,确认转移确认结果
执行命令:
redis-cli -p 7380 cluster nodes
,可以看到7383节点拥有0~2999插槽,说明转移插槽成功了
6. 故障转移
当master宕机时,Redis的分片集群同样具备故障转移的能力。
接下来给大家演示一下分片集群的故障转换,先确认一下集群的初始状态:
7380是master节点,8382是其slave节点
7381是master节点,8380是其slave节点
7382是master节点,8381是其slave节点
7383是master节点,没有slave节点
自动故障转移
当master宕机时,分片集群会自动将其slave节点提升为master。
我们直接将7381节点关闭:
redis-cli -p 7381 shutdown
,然后查看集群状态
redis-cli -p 7380 cluster nodes
,发现7381是fail状态,8380已经提升成为master再次启动7381节点:
redis-server 7381/redis.conf
再次查看集群状态:
redis-cli -p 7380 cluster nodes
,发现7381节点成为了slave手动故障转移
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。
这种failover命令可以指定三种模式:
缺省:默认的流程,如图1~6歩
force:省略了对offset的一致性校验
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
其流程如下:
我们以7381这个slave节点为例,演示如何手动进行故障转移,让7381重新夺回master:
利用redis-cli连接7381这个节点:
redis-cli -p 7381
在7381节点上执行
cluster failover
命令重新查看集群状态:
cluster nodes
7. RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1)引入redis的starter依赖
2)配置分片集群地址
3)配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:
spring:
redis:
cluster:
nodes:
- 192.168.126.129:7380
- 192.168.126.129:7381
- 192.168.126.129:7382
- 192.168.126.129:8380
- 192.168.126.129:8381
- 192.168.126.129:8382
8. 小结
1. Redis分片集群解决了什么问题
海量数据存储的问题。有多个Master都可以存储数据
高并发写的问题。有多个Master都可以提供写数据的服务连接分片集群,使用命令:redis-cli -c -p 任意Master节点的端口
2. 分片集群中的插槽是什么
在存取每个key时,Redis会根据key通过CRC16算法做hash计算得到hash值,再拿hash值对16384求余数,是slot插槽值
分片集群里,每个master节点都会负责一部分插槽的管理
当存取数据时,Redis会根据key的slot插槽的值,判断这个key归哪个节点管理,就重定向到该节点上,执行存取3. 分片集群伸缩的步骤
4. 分片集群如何实现故障转移
5. 用RedisTemplate如何访问分片集群