单机Redis、Redis持久化、Redis主从模式、Redis哨兵模式、Redis分片集群

一、单机Redis

数据库分类:

到目前为止,所有数据库分为两大类:

  • RDBMS:关系型数据库即传统数据库

                     像MySQLSQLServer、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 "",表示禁用RDB

2 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"
  • appendonlyAOF模式的开关。如果值是yes,表示开启;如果是no,表示关闭

  • appendfilenameAOF文件的名称。默认是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-sizeAOF文件必须大于这个值,才可能触发AOF重写

auto-aof-rewrite-percentageAOF文件上次重写后的文件对比,超过这个百分比后,会触发AOF重写

4. RDB和AOF对比

RDB和AOF两种持久化模式各有各的优缺点。在实际开发中往往是两者结合使用

对比项RDBAOF
持久化方式定时整个内存数据做快照记录每次执行写命令
数据完整性两次备份之间数据可能丢失丢失数据的可能性较小,取决于刷盘策略
文件大小相对较小,且有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端口
master192.168.126.1206380
slave192.168.126.1206381
slave192.168.126.1206382
如图所示:
 我们创建三个文件夹

#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端口
s1192.168.126.12316380
s2192.168.126.12316381
s3192.168.126.12316382

准备实例和配置

准备文件夹

要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。

先创建文件夹~/03sentinel文件夹,然后在~/03sentinel里创建三个文件夹,名字分别叫s1、s2、s3:

mkdir 03sentinel
cd 03sentinel/
mkdir s1 s2 s3

准备sentinel配置文件

准备s1实例的配置

切换到s1文件夹里:cd ~/03sentinel/s1

使用创建文件sentinel.confvi 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主从集群未启动,就执行以下命令:

  1. 启动三个Redis实例

redis-server ~/02master/6380/redis.conf
redis-server ~/02master/6381/redis.conf
redis-server ~/02master/6382/redis.conf
  1. 设置主从关系:把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.1217380master
192.168.126.1217381master
192.168.126.1217382master
192.168.126.1218380slave
192.168.126.1218381slave
192.168.126.1218382slave

 

 

准备实例和配置

准备文件夹

创建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

主要原因可能是:该节点默认生成的配置与历史存储的数据不一致导致的

解决的方法是:

  1. 关闭所有redis实例:

printf '%s\n' 7380 7381 7382 8380 8381 8382|xargs -I{} -t redis-cli -p {} shutdown
  1. 清除对应节点的dump.rdb、nodes.conf等文件

find / -name nodes.conf | xargs rm -rf
find / -name dump.rdb | xargs rm -rf
  1. 然后重启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,做如下操作:

  1. 存储数据:set num 10

  2. 获取数据:get num

  3. 再次存储:set a 1

在使用redis-cli连接分片集群时,必须加上参数-c,否则操作时可能会出错。示例:

尝试连接7380节点:redis-cli -p 7380,做如下操作:

  1. 存储数据:set num 10

  2. 获取数据:get num

  3. 再次存储: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

  1. 启动一个新的Redis实例,端口为7383

  2. 把7383节点添加到之前的集群,并作为一个master节点

  3. 给7383节点分配插槽,使得这个num可以存储到7383实例

这需要有两大步:

  1. 添加一个节点到集群中

  2. 转移插槽

创建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的插槽是多少:

  1. 连接任意一个Redis节点:redis-cli -c -p 7382

  2. 查询num的数据: get num,发现num的插槽是2765,在7380节点上

  3.  我们可以将0~3000插槽,从7380节点移动到7383节点上

转移插槽

转换插槽的命令,操作如下:

  1. 建立连接:redis-cli --cluster reshard 192.168.126.121:7380

     

    输入要移动的插槽数量,我们输入 3000  

    输入目标节点的id(哪个节点要接收这些插槽,就输入哪个节点的id)

    我们从刚刚的输出结果中,找到7383节点的id设置过来

     

     

    从哪个节点里转移出插槽?

  2. 输入all,表示全部。即从现有每个master节点中各转移一部分出来

    输入节点id,表示从指定节点中转移出来

    输入done,表示输入完毕了

    我们这里从7380节点转移出来一部分,要输入7380节点的id

     

    输入yes,确认转移  

    确认结果

     执行命令:redis-cli -p 7380 cluster nodes,可以看到7383节点拥有0~2999插槽,说明转移插槽成功了

  3.  

     

     

     

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:

  1. 利用redis-cli连接7381这个节点:redis-cli -p 7381

  2. 在7381节点上执行cluster failover命令

  3. 重新查看集群状态:cluster nodes

  4.  

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如何访问分片集群

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值