有一点需要注意,在不关闭防火墙的情况下,可能导致slave节点同步主节点数据失败。

Redis 主从架构

搭建Redis主从

先启动一个6379端口的redis,作为主节点,master。

复制一份新的redis.conf文件进行修改,作为slave节点的配置文件。

# 修改端口
port 6380

# 修改pid文件路径
pidfile /var/run/redis_6380.pid

# 修改redis数据存放目录
dir /root/redis/data/6380/dir

# 日志文件
logfile "6380.log"

# 指定主节点,从主节点同步数据
replicaof 192.168.2.166 6379

# 设置slave节点为只读
replica‐read‐only yes

启动slave节点

src/redis-server data/6380/redis.conf

查看进程,这时候已经能看到两个进程了,一个6379,一个6380 。

[root@localhost redis]# ps -ef | grep redis
root      8966     1  0 10:14 ?        00:00:00 src/redis-server 127.0.0.1:6379
root      9020     1  0 10:15 ?        00:00:00 src/redis-server 127.0.0.1:6380
root      9053  3698  0 10:15 pts/0    00:00:00 grep --color=auto redis

进入6379主节点写入数据

[root@localhost redis]# src/redis-cli -p 6379
127.0.0.1:6379> set zhangsan 2020
OK
127.0.0.1:6379> 

进入slave从节点执行get命令,可以看到已经同步了主节点 master的数据。

[root@localhost redis]# src/redis-cli -p 6380
127.0.0.1:6380> get zhangsan
"2020"
127.0.0.1:6380> 

但是slave节点我们在配置文件里设置的是只读,不允许写,尝试一下写入命令。可以看到控制台输出错误信息。

[root@localhost redis]# src/redis-cli -p 6380
127.0.0.1:6380> set zhangsan 1
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380> 
使用Docker 搭建主从架构

先使用docker 拉取镜像

[root@localhost etc]# docker pull redis:6
6: Pulling from library/redis
8559a31e96f4: Pull complete 
85a6a5c53ff0: Pull complete 
b69876b7abed: Pull complete 
a72d84b9df6a: Pull complete 
5ce7b314b19c: Pull complete 
04c4bfb0b023: Pull complete 
Digest: sha256:800f2587bf3376cb01e6307afe599ddce9439deafbd4fb8562829da96085c9c5
Status: Downloaded newer image for redis:6
docker.io/library/redis:6

创建多个文件夹,我这里以端口为命名,文件夹下有dir文件夹(作为redis工作目录),并且复制一个未修改的redis.conf 配置文件。

需要注意的是,需要对文件夹的权限修改为757。不然后续redis会没有写入权限 。

[root@localhost redis-data]# mkdir 6379/dir
[root@localhost redis-data]# cp /root/redis/redis.conf 6379/redis.conf
[root@localhost redis-data]# chmod -R 757 6379
[root@localhost redis-data]# ls 6379
dir  redis.conf

修改redis.conf中的工作目录 其他的aof rdb持久化等配置,根据自己需要,自行修改。

# 禁止使用后台守护进程
daemonize no
# 设置redis的工作目录
dir /redis-data/dir
# 设置绑定地址
bind 0.0.0.0

运行主节点

#启动主节点 master  
docker run -d --name redis-master -p 6379:6379 -v /root/redis-data/6379:/redis-data redis:6 redis-server /redis-data/redis.conf

准备slave从节点,将master主节点的文件夹 ”6379“ 复制出一份,到 ”6380“ 。

[root@localhost redis-data]# cp -r 6379 6380
[root@localhost redis-data]# chmod -R 757 6380
[root@localhost redis-data]# ls
6379  6380

修改6380下的redis.conf配置文件,添加主节点的ip地址。

# 指定主节点,从主节点同步数据
replicaof 192.168.2.116 6379

# 设置slave节点为只读
replica‐read‐only yes

启动slave 从节点命令。

# 启动从节点 slave-6380  
docker run -d --name redis-slave-6380 -p 6380:6379 -v /root/redis-data/6380:/redis-data redis:6 redis-server /redis-data/redis.conf

测试主从是否生效,先进入主节点端口,执行set指令。

[root@localhost redis]# src/redis-cli -p 6379
127.0.0.1:6379> set zhangsan 2020
OK
127.0.0.1:6379> 

现在进入slave从节点,查看数据,已经同步到slave从节点上了。

[root@localhost redis]# src/redis-cli -p 6380
127.0.0.1:6380> get zhangsan
"2020"
127.0.0.1:6380> 
主从架构数据复制原理

Slave 节点在第一次启动时,会发送psync指令到主节点同步全量数据,主节点在获取到psync指令后,会将master主节点最新的内存数据,生成一次RDB数据快照(如果同时收到多次同步全量数据的指令,只会生成一次数据快照),并且发送到slave节点,slave节点接收到RDB数据后,加载到内存当中,然后master主节点会将生成RDB文件后的缓存在内存中的命令,发送给slave节点。

当slave节点与master节点完成同步之后,master会通过长连接的方式,持续将新的写命令,发送给slave节点。

master 主节点在执行写的命令时,会将命令存放到缓存区,每条命令都会有一个offset偏移值,缓存区大小约为1M,会滚动删除,并且master和它所有的slave节点都会持有每条命令的offset 偏移值和master 进程id。

如果因为网络波动等原因导致slave 与 master连接中断,这时候slave节点会进行重连,如果master 进程id出现了变化,则会发送全量同步的指令,否则会执行增量同步,发送psync( offset )指令,将slave节点最新的一条数据的偏移量(offset)通过psync指令发送给master。master在接到这条psync(offset)指令后,会去master的缓存区域查找这条offset,如果找到了的话,会将这个指令之后的数据一次性同步给slave节点,倘若没有找到的话,说明这个offset 已经过旧了,则会出发一次全量同步。

哨兵架构

sentinel哨兵是特殊的redis服务,它主要是用来监控redis的实例节点。

在哨兵的架构下,client端会在第一次获取redis实例,从sentinel找出主节点master,后续就会一直访问该主节点。

如果主节点master宕机了的情况下,sentinel会第一时间感知到,并对slave节点进行选举,选出一个新的master节点。如果redis的client端实现了订阅功能,那么这时候client可以订阅sentinel发布的节点变动消息。

准备工作

先运行一个master主节点,其次再运行两个slave节点,其实就是先把主从跑起来。

这里的话我就不采用docker运行了,修改配置文件,参考上面写的搭建Redis主从slave配置文件。

# 运行主节点  master
[root@myredis redis]# src/redis-server /root/redis-data/6379/redis.conf

#运行slave节点  6380
[root@myredis redis]# src/redis-server /root/redis-data/6380/redis.conf

#运行slave节点  6381
[root@myredis redis]# src/redis-server /root/redis-data/6381/redis.conf

查看进程是否运行

[root@myredis redis]# ps -ef | grep redis
root      7746     1  0 17:14 ?        00:00:01 src/redis-server 0.0.0.0:6379
root      7767     1  0 17:14 ?        00:00:01 src/redis-server 0.0.0.0:6380
root      7783     1  0 17:14 ?        00:00:01 src/redis-server 0.0.0.0:6381
搭建哨兵架构

修改配置文件,要启动多少个哨兵,就修改多少份文件。

# 创建一个文件夹 用来存储多个sentinel节点的配置文件
mkdir sentinel
# 复制一个sentinel.conf 文件,并以端口为结尾命名
cp sentinel.conf sentinel/sentinel-26379.conf

# 接下来修改 配置文件 
# 运行端口
port 26379

# 是否开启后台守护进程
daemonize yes

# 进程id保存路径 结尾以端口号命名,以防多个实例冲突。
pidfile /var/run/redis-sentinel-26379.pid

# 日志文件,同样以端口号命名,以防多个实例冲突。
logfile "26379.log"

#修改工作目录,同样以端口号结尾命名,工作目录要事先创建好,
dir /root/redis-data/sentinel/26379

# 配置主节点的ip地址和端口号,
# sentinel monitor <master‐name> <ip> <redis‐port> <quorum> 
# quorum是一个数字,指明多少个sentinel认为master失效时,master才算真正失效(一般是sentinel总数/2 + 1)。
sentinel monitor mymaster 192.168.2.116 6379 2

启动sentinel。

# 根据指定的配置文件启动sentinel 哨兵
[root@localhost redis]# src/redis-sentinel sentinel/sentinel-26379.conf
[root@localhost redis]# src/redis-sentinel sentinel/sentinel-26380.conf
[root@localhost redis]# src/redis-sentinel sentinel/sentinel-26381.conf

# 启动后,使用redis客户端进入sentinel
[root@localhost redis]# src/redis-cli -p 26379

# 查看sentinel信息,执行info命令
127.0.0.1:26379> info

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
# 可以看到 redis master的地址和端口,还有该master所对应的slaves的数量
master0:name=mymaster,status=ok,address=192.168.2.116:6379,slaves=2,sentinels=1

到这里,哨兵架构已经搭建起来了

SpringBoot 集成Redis哨兵

依赖配置

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

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

application.yaml 配置文件

spring:
  redis:
    timeout: 3000
    database: 0
    sentinel:
      # 哨兵节点,逗号分隔
      nodes: 192.168.2.116:26379,192.168.2.116:26380,192.168.2.116:26381
      # 主节点所在名称
      master: mymaster
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

编写测试接口,测试是否能从哨兵节点获取master节点,并写入数据。

// 一直向Redis 循环写入数据
    @RequestMapping("whileWriteData")
    public String whileWriteData() throws InterruptedException {
        for (int i = 0; ; i++) {
           try {
               stringRedisTemplate.opsForValue().set("zhangsan:" + i, String.valueOf(i));
               Thread.sleep(1000);
           }catch (Exception e){
               e.printStackTrace();
           }
        }
    }

调用接口后,可以看到在一直向redis写入数据

WX20200616-174401@2x

这时候尝试将redis的master节点,也就是 6379的进程kill掉。测试sentinel是否会切换slave为master。

[root@myredis redis]# ps -ef | grep redis
root      7746     1  0 17:14 ?        00:00:04 src/redis-server 0.0.0.0:6379
root      7767     1  0 17:14 ?        00:00:03 src/redis-server 0.0.0.0:6380
root      7783     1  0 17:14 ?        00:00:03 src/redis-server 0.0.0.0:6381
root      8312     1  0 17:17 ?        00:00:04 src/redis-sentinel *:26379 [sentinel]
root     10878     1  0 17:30 ?        00:00:02 src/redis-sentinel *:26380 [sentinel]
root     10907     1  0 17:30 ?        00:00:02 src/redis-sentinel *:26381 [sentinel]
root     13848  3855  0 17:45 pts/0    00:00:00 grep --color=auto redis

[root@myredis redis]# kill 7746

再看SpringBoot运行日志,先是连接超时异常,后又重新尝试重新连接master节点。当sentinel重新选举出新的master后,会获取到新的master节点地址。

io.lettuce.core.RedisCommandTimeoutException: Command timed out after 3 second(s)
  .....
//尝试重连master
Cannot reconnect to [192.168.2.116:6379]: Connection refused: /192.168.2.116:6379
// 连接新的master  
Reconnected to 192.168.2.116:6382

当SpringBoot获取到新的节点后,代码会继续往redis插入数据。

如果旧的master重新启动后,旧的master会变成slave节点。