Redis的主从复制和哨兵机制详解

注:本篇部分知识点来源于【Redis深度历险-核心原理和应用实践】书籍当中!

在了解 Redis 的主从复制之前,让我们先来理解一下现代分布式系统的理论基石——CAP 原理。

一、CAP 原理

CAP 原理就好比分布式领域的牛顿定律,它是分布式存储的理论基石。自打 CAP 的论文发表之后,分布式存储中间件犹如雨后春笋般一个一个涌现出来。理解这个原理其实很简单,本节我们首先对这个原理进行一些简单的讲解。

  • C - Consistent ,一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • A - Availability ,可用性:保证每个请求不管成功或者失败都有响应。
  • P - Partition tolerance ,分区容忍性:系统中任意信息的丢失或失败不会影响系统的继续运作。

网络分区:分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个网络断开的场景的专业词汇叫着「网络分区」

网络分区产生的问题: 在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操作将无法同步到另外一个节点,所以数据的「一致性」将无法满足,因为两个分布式节点的数据不再保持一致。除非我们牺牲「可用性」,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。

一句话概括 CAP 原理就是——网络分区发生时,一致性和可用性两难全。

二、Redis主从同步

1、主从同步是什么能干嘛?

主从: 很多企业都没有使用到 Redis 的集群,但是至少都做了主从。有了主从,当 master 挂掉的时候,运维让从库过来接管,服务就可以继续,否则 master 需要经过数据恢复和重启的过程,这就可能会拖很长的时间,影响线上业务的持续服务。

数据同步: 主从复制也有人才称之为主从同步,其实两个说的是一个东西,主要是为了避免单节点Redis出现故障而导致服务无法使用,所以采用部署多个节点,虽然是多个节点,但是节点之间的数据是始终是保持同步(一样)的数据,要新增key/value都新增kev/value,要删除都删除,可以理解为就是克隆!

主从同步功能也不是必须的,但是只要你使用了 Redis 的持久化功能,就必须认真对待主从复制,它是系统数据安全的基础保障。

主从同步主要有两个特点:

  • 读写分离,性能扩展,降低主服务器的压力
  • 容灾,快速恢复,主机挂掉时,从机变为主机

Redis满足CAP理论当中的AP

Redis 的主从数据是异步同步的,所以分布式的 Redis 系统并不满足「一致性」要求。当客户端在 Redis 的主节点修改了数据后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以 Redis 满足「可用性」。

Redis能够保证最终一致性

从节点会努力追赶主节点,最终从节点的状态会和主节点的状态将保持一致。如果网络断开了,主从节点的数据将会出现大量不一致,一旦网络恢复,从节点会采用多种策略努力追赶上落后的数据,继续尽力保持和主节点一致。

Redis 同步支持主从同步从从同步,从从同步功能是 Redis 后续版本增加的功能,为了减轻主库的同步负担。后面为了描述上的方便,统一理解为主从同步。

主redis挂掉以后情况会如何?从机是上位还是原地待命?

主机挂掉后,从机会待命,小弟还是小弟,会等着大哥恢复,不会篡位。

从挂掉后又恢复了,会继续从主同步数据么?

会的,当从重启之后,会继续将中间缺失的数据同步过来。

可以使用 info Replication 命令查看主从复制信息

2、Redis是如何实现数据同步的?

  1. slave启动成功连接到master后,会给master发送数据同步消息(发送sync命令)
  2. master接收到slave发来的数据同步消息后,把主服务器的数据进行持久化到rdb文件,同时会收集接收到的用于修改数据的命令,master将传rdb文件发送给你slave,完成一次完全同步
  3. 全量复制:而slave服务在接收到master发来的rdb文件后,将其存盘并加载到内存
  4. 增量复制:master继续将收集到的修改命令依次传给slave完成同步,
    但是只要重新连接master,一次完全同步(全量复制)将会被自动执行

上面只是一个整体的大概流程,下面我们将对增量同步以及全量同步进行详细介绍!

2.1.增量同步

Redis 同步的是指令流

  1. 主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中
  2. 然后异步将 buffer 中的指令同步到从节点
  3. 从节点一边执行同步的指令流来达到和主节点一样的状态,一遍向主节点反馈自己同步到哪里了 (偏移量)。

注意:因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer 中。Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。

如果因为网络状况不好,从节点在短时间内无法和主节点进行同步,那么当网络状况恢复时,Redis 的主节点中那些没有同步的指令在 buffer 中有可能已经被后续的指令覆盖掉了,从节点将无法直接通过指令流来进行同步,这个时候就需要用到更加复杂的同步机制 —— 快照同步。

2.2.快照同步

快照同步是一个非常耗费资源的操作:

  1. 它首先需要在主库上进行一次 bgsave 将当前内存的数据全部快照到磁盘文件中
  2. 然后再将快照文件的内容全部传送到从节点。
  3. 从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。
  4. 加载完毕后通知主节点继续进行增量同步。

在整个快照同步进行的过程中,主节点的复制 buffer 还在不停的往前移动,如果快照同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。

所以务必配置一个合适的复制 buffer 大小参数,避免快照复制的死循环。

当从节点刚刚加入到集群时,它必须先要进行一次快照同步,同步完成后再继续进行增量同步。

2.3.无盘复制

主节点在进行快照同步时,会进行很重的文件 IO 操作,特别是对于非 SSD 磁盘存储时,快照会对系统的负载产生较大影响。特别是当系统正在进行 AOF 的 fsync 操作时如果发生快照,fsync 将会被推迟执行,这就会严重影响主节点的服务效率。

所以从 Redis 2.8.18 版开始支持无盘复制。所谓无盘复制是指主服务器直接通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点会一边遍历内存,一边将序列化的内容发送到从节点,从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。

2.4.通过Wait 指令保证强一致性

Redis 的复制是异步进行的,wait 指令可以让异步复制变身同步复制,确保系统的强一致性 (不严格)。wait 指令是 Redis3.0 版本以后才出现的。

> set key value
OK
> wait 1 0
(integer) 1

wait 提供两个参数,第一个参数是从库的数量 N,第二个参数是时间 t,以毫秒为单位。它表示等待 wait 指令之前的所有写操作同步到 N 个从库 (也就是确保 N 个从库的同步没有滞后),最多等待时间 t。如果时间 t=0,表示无限等待直到 N 个从库同步完成达成一致。

假设此时出现了网络分区,wait 指令第二个参数时间 t=0,主从同步无法继续进行,wait 指令会永远阻塞,Redis 服务器将丧失可用性。

3、搭建Redis 1主2从

3.1.安装Redis

下面我们来配置1主2从的效果,现实中是需要3台机器的,为了方便,我们就在一台Linux机器上来演示,通过不同的端口来区分机器,3台机器的配置

(1)停止redis

假如linux当中安装的有redis,可以先全部停掉,停止所有的redis命令:

ps -ef | grep redis | awk -F" " '{print $2;}' | xargs kill -9

(2)下载redis,然后将redis上传到opt目录

如果没有安装wget请先安装:

cd /opt
yum -y install wget
wget https://download.redis.io/redis-stable.tar.gz

(3)安装redis

要编译 Redis,首先是 tar解压,切换到根目录,然后运行make:

tar -xzvf redis-stable.tar.gz
cd redis-stable
yum install gcc-c++
make install

假如make install报如下异常,直接使用make install MALLOC=libc

安装过后你会在src目录中找到几个 Redis 二进制文件,包括:

  • redis-server:Redis 服务器本身
  • redis-cli:是与 Redis 对话的命令行界面实用程序,俗称客户端。

3.2.创建1主2从配置文件

(1)创建配置文件

我们将三台机器的配置文件都放到/opt/redis-stable/config当中

mkdir /opt/redis-stable/config
cd /opt/redis-stable/config
cp /opt/redis-stable/redis.conf /opt/redis-stable/config/

(2)创建master的配置文件:redis-6379.conf

touch redis-6379.conf
vi redis-6379.conf

编辑为如下内容:

bind 0.0.0.0:表示允许所有ip地址访问,默认是127.0.0.1,只允许当前主机连接,假如这里要是配置成当前机器的ip或者是127.0.0.1,那么可能会导致在windows当中无法连接Redis。

#redis.conf是redis原配置文件,内部包含了很多默认的配置,这里使用include将其引用,相当于把redis.conf内容直接贴进来了
include /opt/redis-stable/config/redis.conf
daemonize yes
bind 0.0.0.0
#配置密码
requirepass 123456
dir /opt/redis-stable/config/
logfile /opt/redis-stable/config/6379.log
#端口
port 6379
#rdb文件
dbfilename dump_6379.rdb
#pid文件
pidfile /var/run/redis_6379.pid

(3)创建slave1的配置文件:redis-6380.conf

内容如下,和上面master的类似,多了后面2行配置,主要是指定master的ip和端口以及认证密码。

192.168.0.108是我的linux的ip,这里记住请切换成自己的!

include /opt/redis-stable/config/redis.conf
daemonize yes
bind 0.0.0.0
requirepass 123456
dir /opt/redis-stable/config/
port 6380
dbfilename dump_6380.rdb
pidfile /var/run/redis_6380.pid
logfile /opt/redis-stable/config/6380.log
#用来指定主机:slaveof 主机ip 端口
slaveof 192.168.0.108 6379
#主机的密码
masterauth 123456

(4)创建slave2的配置文件:redis-6381.conf

include /opt/redis-stable/config/redis.conf
daemonize yes
bind 0.0.0.0
requirepass 123456
dir /opt/redis-stable/config/
port 6381
dbfilename dump_6381.rdb
pidfile /var/run/redis_6381.pid
logfile /opt/redis-stable/config/6381.log
#用来指定主机:slaveof 主机ip 端口
slaveof 192.168.0.108 6379
#主机的密码
masterauth 123456

3.3.启动Redis

(1)关闭防火墙

假如不关闭防火墙,可能会出现windows无法连接我们刚刚在虚拟机安装的redis的情况

  • 设置开机启用防火墙:systemctl enable firewalld.service
  • 设置开机禁用防火墙:systemctl disable firewalld.service
  • 启动防火墙:systemctl start firewalld
  • 关闭防火墙:systemctl stop firewalld
  • 检查防火墙状态:systemctl status firewalld

(2)启动redis

启动master

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6379.conf

启动slave1

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6380.conf

启动slave2

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6381.conf

若启动有误,大家好好检查下配置,也可以看日志,3台机器启动会在 /opt/redis-stable/config 目录产生日志,如下

可以通过ps -ef|grep redis 查看启动的redis

(3)连接redis

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456

查看主机信息:info Replication

查询从机slave1的信息

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456

3.4.验证主从同步效果

在master上面执行下面2个命令

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379>  set name ready
OK
127.0.0.1:6379> set age 30
OK

到slave1上执行下面命令,可以看出来数据已经同步过来了

[root@bogon config]# /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6380>  mget name age
1) "ready"
2) "30"

同样到slave2上也执行一下,效果如下,注意在从机当中是不允许执行新增修改命令的!

[root@bogon config]# /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6381 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6381> mget name age
1) "ready"
2) "30"
127.0.0.1:6381> set aa aa
(error) READONLY You can't write against a read only replica.

3.5.通过命令配置主从关系

上面演示的就是一主二从,不过采用的都是配置文件的方式,实际上从机可以采用命令的方式配置,下面我们来演示一遍,大家看好了。

(1)将redis-6380.conf 和 redis-6381.conf的这两行配置给删掉

#用来指定主机:slaveof 主机ip 端口
slaveof 192.168.0.108 6379
#主机的密码
masterauth 123456

(2)启动三个redis

启动master

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6379.conf

启动slave1

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6380.conf

启动slave2

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6381.conf

(3)分别登陆3台机器,查看各自主从信息

分别登陆对3个redis,然后用 info replication 命令看下3个的主从信息,如下:

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456
info Replication

这时候我们已经解除了3个节点的主从关系!

(4)配置slave1为master的从库

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456
config set masterauth 123456
slaveof 127.0.0.1 6379
info replication

(5)配置slave2为master的从库

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6381 -a 123456
config set masterauth 123456
slaveof 127.0.0.1 6379
info replication

(6)再来看看master的主从信息

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456
info replication

注意:通过 slaveof 命令指定主从的方式,slave重启之后主从配置会失效,所以,重启后需要在slave上重新通过 slaveof 命令进行设置,这个不要忘记了。中途通过 slaveof 变更转向,本地的数据会被清除,会从新的master重新同步数据。

3.6.反客为主

当master挂掉之后,我们可以从slave中选择一个作为主机。比如我们想让slave1作为主机,那么可以在slave1上执行下面的命令就可以了。

slaveof no one

这里我通过ps-ef|grep redis查询出来了6379主节点的进程,然后直接给强制kill关闭了

此时slave1就变成主机了,然后再去其他slave上面执行 slaveof 命令将其挂在slave1上。

配置slave2为slave1的从库

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6381 -a 123456
config set masterauth 123456
slaveof 127.0.0.1 6380
info replication

假如过了一段时间master恢复了,如果还想让他当主,还得将所有redis重启一遍才能让他继续当主。

4、消息丢失

Redis 主从采用异步复制,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。Sentinel 无法保证消息完全不丢失,但是也尽可能保证消息少丢失。它有两个选项可以限制主从延迟过大。

min-slaves-to-write 1
min-slaves-max-lag 10

第一个参数表示主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性。

何为正常复制,何为异常复制?这个就是由第二个参数控制的,它的单位是秒,表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没有给反馈。

5、主从模式不足

主从模式并不完美,它也存在许多不足之处,下面做了简单地总结:

  1. Redis 主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis 集群将无法工作,此时需要人为干预,将从节点提升为主节点。
  2. 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致的问题,从而降低了系统的可用性。
  3. 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度地限制。
  4. 在进行数据全量同步时,若同步的数据量较大可能会造卡顿的现象。

下面来介绍另外一种方式:哨兵模式,主挂掉之后,自动从slave中选举一个作为主机,自动实现故障转移。

四、哨兵(Sentinel)模式

1、是什么能干嘛?

反客为主的自动版,能够自动监控master是否发生故障,如果故障了会根据投票数从slave中挑选一个作为master,其他的slave会自动转向同步新的master,实现故障自动转义。

2、哨兵(Sentinel)模式原理

  • 第一:哨兵节点会以每秒一次的频率对每个 Redis 节点发送PING命令,并通过 Redis 节点的回复来判断其运行状态。
  • 第二:当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台将机器,并其提升为主服务器,然后使用 PubSub 发布订阅模式,通知其他的从节点,修改配置文件,跟随新的主服务器。 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

但是这种情况存在误判的可能,比如:可能master并没有挂,只是sentinel和master之间的网络不通导致,导致ping失败。为了避免误判,通常会启动多个sentinel,一般是奇数个,比如3个,那么可以指定当有多个sentinel都觉得master挂掉了,此时才断定master真的挂掉了,通常这个值设置为sentinel的一半,比如sentinel的数量是3个,那么这个量就可以设置为2个。

多个哨兵之间也存在互相监控,这就形成了多哨兵模式,现在对该模式的工作过程进行讲解,介绍如下:

  1. 主观下线: 主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数:down-after-milliseconds),Sentinel节点没有收到目标服务器的有效回复,则判定该服务器为“主观下线”。比如 Sentinel1向主服务发送了PING命令,在规定时间内没收到主服务器PONG回复,则 Sentinel1 判定主服务器为“主观下线”。
  2. 客观下线: 客观(被动)下线,只适用于主服务器。 Sentinel1 发现主服务器出现了故障,它会通过相应的命令,询问其它 Sentinel 节点对主服务器的状态判断。如果超过半数以上的 Sentinel 节点认为主服务器 down 掉,则 Sentinel1 节点判定主服务为“客观下线”。
  3. 投票选举: 投票选举,所有 Sentinel 节点会通过投票机制,按照谁发现谁去处理的原则,选举 Sentinel1 为领头节点去做 Failover(故障转移)操作。Sentinel1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订阅功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

对上对述过程做简单总结:

Sentinel 负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。

3、搭建1主2从3个哨兵

下面我们来实现1主2从以及3个sentinel的配置,当从的挂掉之后,要求最少有2个sentinel认为主的挂掉了,才进行故障转移。

为了方便,我们在一台机器上进行模拟,通过端口来区分6个不同的节点(1个master、2个slave、3个sentinel),节点配置信息如下

3.1、创建3个sentinel配置文件

我们直接就是基于上面的 1主2从 的 主从复制 配置然后给他配置3个sentinel哨兵。

(1)创建sentinel1的配置文件:sentinel-26379.conf

在/opt/redis-stable/config目录创建 sentinel-26379.conf 文件,内容如下

注意:127.0.0.1需要替换成master主机的ip,不然windows连接哨兵会出现问题!

cd /opt/redis-stable/config/
touch sentinel-26379.conf
vi sentinel-26379.conf
bind 0.0.0.0
# 配置文件目录
dir /opt/redis-stable/config/
# 日志文件位置
logfile "./sentinel-26379.log"
# pid文件
pidfile /var/run/sentinel_26379.pid
# 是否后台运行
daemonize yes
# 端口
port 26379
# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移
sentinel monitor mymaster 127.0.0.1 6379 2
# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用
sentinel down-after-milliseconds mymaster 20000
# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败
sentinel failover-timeout mymaster 180000
# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制
sentinel parallel-syncs mymaster 1
# 指定mymaster主的密码(没有就不指定)
sentinel auth-pass mymaster 123456

(2)创建sentinel2的配置文件:sentinel-26380.conf

touch sentinel-26380.conf
vi sentinel-26380.conf
bind 0.0.0.0
# 配置文件目录
dir /opt/redis-stable/config/
# 日志文件位置
logfile "./sentinel-26380.log"
# pid文件
pidfile /var/run/sentinel_26380.pid
# 是否后台运行
daemonize yes
# 端口
port 26380
# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移
sentinel monitor mymaster 127.0.0.1 6379 2
# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用
sentinel down-after-milliseconds mymaster 20000
# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败
sentinel failover-timeout mymaster 180000
# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制
sentinel parallel-syncs mymaster 1
# 指定mymaster主的密码(没有就不指定)
sentinel auth-pass mymaster 123456

(3)创建sentinel3的配置文件:sentinel-26381.conf

touch sentinel-26381.conf
vi sentinel-26381.conf
bind 0.0.0.0
# 配置文件目录
dir /opt/redis-stable/config/
# 日志文件位置
logfile "./sentinel-26381.log"
# pid文件
pidfile /var/run/sentinel_26381.pid
# 是否后台运行
daemonize yes
# 端口
port 26381
# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移
sentinel monitor mymaster 127.0.0.1 6379 2
# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用
sentinel down-after-milliseconds mymaster 20000
# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败
sentinel failover-timeout mymaster 180000
# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制
sentinel parallel-syncs mymaster 1
# 指定mymaster主的密码(没有就不指定)
sentinel auth-pass mymaster 123456

3.2、启动3个sentinel

启动sentinel有2种方式

  • 方式1:redis-server sentinel.conf --sentinel
  • 方式2:redis-sentinel sentinel.conf

下面我们使用方式2来启动3个sentinel

/opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26379.conf
/opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26380.conf
/opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26381.conf

分别对3个sentinel执行下面命令,查看每个sentinel的信息

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 26379
info sentinel

sentinel1 的信息如下,其他2个sentinel的信息这里就不列了,大家自己去看一下

注意:连接哨兵并不等于连接redis,我们会发现输入redis的相关命令会直接异常。关于Sentinel支持的命令在文章下方有记录!

3.3、使用Redis DeskTop连接sentinel

使用windows当中的Redis DeskTop客户端可以通过连接哨兵来操作redis。

本质上他就是通过哨兵来获取到的Redis的主机,然后相当于是直接连接的Redis主机!

sentinel当中配置的Redis的master的ip一定不可以是127.0.0.1,不然可能连接不上!

3.4、验证故障自动转移是否成功

(1)在master中执行下面命令,停止master

[root@localhost config]# /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> shutdown
(0.76s)

(2)等待2分钟,等待完成故障转移

sentinel中我们配置 down-after-milliseconds 的值是20秒,表示判断主机下线时间是20秒,所以我们等2分钟,让系统先自动完成故障转移。

(3)查看slave1的主从信息,如下

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456
info replication

(4)查看slave2的主从信息,如下

slave2变成master了,且slave2变成slave1的从库了,完成了故障转移。

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6381 -a 123456
info replication

(5)redis的配置文件以及哨兵的配置文件都发生了变化:

(6)下面验证下slave1和slave2是否同步

在slave2中执行下面命令

127.0.0.1:6381>set address china
OK

在slave1中执行下面命令,查询一下address的值,效果如下,说明slave2和slave1同步正常

127.0.0.1:6381> get address
"china"

3.4、恢复旧的master自动俯首称臣

当旧的master恢复之后,会自动挂在新的master下面,咱们来验证下是不是这样的。

(1)执行下面命令,启动旧的master

/opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6379.conf

(2)执行下面命令,连接旧的master

/opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456

(3)执行下面命令,查看其主从信息:info replication

效果如下,确实和期望的一致。

4、Sentinel支持的命令

哨兵节点作为运行在特殊模式下的redis节点,其支持的命令与普通的redis节点不同。在运维中,我们可以通过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各种功能,离不开哨兵节点之间的通信,而通信的很大一部分是通过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令。

(1)基础查询:通过这些命令,可以查询哨兵系统的拓扑结构、节点信息、配置信息等。

  • info sentinel:获取监控的所有主节点的基本信息
  • sentinel masters:获取监控的所有主节点的详细信息
  • sentinel master mymaster:获取监控的主节点mymaster的详细信息
  • sentinel slaves mymaster:获取监控的主节点mymaster的从节点的详细信息
  • sentinel sentinels mymaster:获取监控的主节点mymaster的哨兵节点的详细信息
  • sentinel get-master-addr-by-name mymaster:获取监控的主节点mymaster的地址信息,前文已有介绍
  • sentinel is-master-down-by-addr:哨兵节点之间可以通过该命令询问主节点是否下线,从而对是否客观下线做出判断

(2)增加/移除对主节点的监控

  • sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的sentinel monitor功能完全一样,不再详述

  • sentinel remove mymaster2:取消当前哨兵节点对主节点mymaster2的监控

(3)强制故障转移

  • sentinel failover mymaster:该命令可以强制对mymaster执行故障转移,即便当前的主节点运行完好;例如,如果当前主节点所在机器即将报废,便可以提前通过failover命令进行故障转移。

五、Jedis连接Sentinel模式

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.2</version>
</dependency>
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Set;

public class SentinelDemo {
    
    
    private static JedisSentinelPool jedisSentinelPool;

    static {
    
    
        try {
    
    
            JedisPoolConfig config = new JedisPoolConfig();
            //最大空闲连接数, 默认8个
            config.setMaxIdle(8);
            //最大连接数, 默认8个
            config.setMaxTotal(8);
            //最小空闲连接数, 默认0
            config.setMinIdle(0);
            //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
            config.setMaxWaitMillis(3000);
            //在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false
            config.setTestOnBorrow(true);


            //redis服务器列表
            Set<String> sentinels = new HashSet<>();
            sentinels.add(new HostAndPort("192.168.115.78", 26379).toString());
            sentinels.add(new HostAndPort("192.168.115.78", 26380).toString());
            sentinels.add(new HostAndPort("192.168.115.78", 26381).toString());

            //初始化连接池
            jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, config, "123456");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        // 从池中获取一个Jedis对象
        Jedis jedis = jedisSentinelPool.getResource();
        String aaa = jedis.get("aaa");
        System.out.println(aaa);
        jedis.close();
    }
}

六、SpringBoot整合Sentinel模式

(1)引入redis的maven配置

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)application.properties中配置redis sentinel信息

spring:
  redis:
    sentinel:
      master: mymaster
      nodes: 192.168.115.78:26379,192.168.115.78:26380,192.168.115.78:26381
    # 连接超时时间(毫秒)
    timeout: 60000
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 0
    # redis的密码
    password: 123456

(3)使用RedisTemplate工具类操作redis

springboot中使用RedisTemplate来操作redis,需要在我们的bean中注入这个对象,代码如下:

@Autowired
private RedisTemplate<String, String> redisTemplate;

// 用下面5个对象来操作对应的类型
this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
this.redisTemplate.opsForSet(); //提供了操作set的所有方法
this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法

(4)RedisTemplate示例代码

@RestController
@RequestMapping("/redis")
public class RedisController {
    
    

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * string测试
     *
     * @return
     */
    @RequestMapping("/stringTest")
    public String stringTest() {
    
    
        this.redisTemplate.delete("name");
        this.redisTemplate.opsForValue().set("name", "路人");
        String name = this.redisTemplate.opsForValue().get("name");
        return name;
    }

    /**
     * list测试
     *
     * @return
     */
    @RequestMapping("/listTest")
    public List<String> listTest() {
    
    
        this.redisTemplate.delete("names");
        this.redisTemplate.opsForList().rightPushAll("names", "刘德华", "张学友",
                "郭富城", "黎明");

        List<String> courses = this.redisTemplate.opsForList().range("names", 0,
                -1);
        return courses;
    }

    /**
     * set类型测试
     *
     * @return
     */
    @RequestMapping("setTest")
    public Set<String> setTest() {
    
    
        this.redisTemplate.delete("courses");
        this.redisTemplate.opsForSet().add("courses", "java", "spring",
                "springboot");
        Set<String> courses = this.redisTemplate.opsForSet().members("courses");
        return courses;
    }

    /**
     * hash表测试
     *
     * @return
     */
    @RequestMapping("hashTest")
    public Map<Object, Object> hashTest() {
    
    
        this.redisTemplate.delete("userMap");
        Map<String, String> map = new HashMap<>();
        map.put("name", "路人");
        map.put("age", "30");
        this.redisTemplate.opsForHash().putAll("userMap", map);
        Map<Object, Object> userMap =
                this.redisTemplate.opsForHash().entries("userMap");
        return userMap;
    }

    /**
     * zset测试
     *
     * @return
     */
    @RequestMapping("zsetTest")
    public Set<String> zsetTest() {
    
    
        this.redisTemplate.delete("languages");
        this.redisTemplate.opsForZSet().add("languages", "java", 100d);
        this.redisTemplate.opsForZSet().add("languages", "c", 95d);
        this.redisTemplate.opsForZSet().add("languages", "php", 70);
        Set<String> languages =
                this.redisTemplate.opsForZSet().range("languages", 0, -1);
        return languages;
    }

    /**
     * 查看redis机器信息
     *
     * @return
     */
    @RequestMapping(value = "/info", produces = MediaType.TEXT_PLAIN_VALUE)
    public String info() {
    
    
        Object obj = this.redisTemplate.execute(new RedisCallback<Object>() {
    
    
            @Override
            public Object doInRedis(RedisConnection connection) throws
                    DataAccessException {
    
    
                return connection.execute("info");
            }
        });
        return obj.toString();
    }

(5)访问的时候出现如下异常

在这里插入图片描述

解决方案:在redis.conf 当中将no-writes-on-bgsave-error设置为 no

stop-writes-on-bgsave-error no

猜你喜欢

转载自blog.csdn.net/weixin_43888891/article/details/131039418