主从复制

什么是主从复制

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 主节点(master) ,后者称为 从节点(slave)**。Master以写为主,Slave以读为主。一般用来实现读写分离和容灾恢复且数据的复制是 **单向 的,只能由主节点到从节点。Redis 主从复制支持 主从同步从从同步 两种,后者是 Redis 后续版本新增的功能,以减轻主节点的同步负担。

主从复制主要的作用

  • 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 (实际上是一种服务的冗余)。
  • 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
  • 防止数据丢失:当主服务器磁盘坏掉之后,其他从服务器还保留着相关的数据,不至于数据全部丢失。
  • 高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的 基础,因此说主从复制是 Redis 高可用的基石。

如何开启&关闭

开启主从复制

原则上就是配从(库)不配主(库),所以只需要在从库输入如下命令即可。

1
2
3
4
#配置从库
slaveof 主库ip 主库端口
#查看主从信息
info replication

如果主服务设置了密码,需要在从服务器输入主服务器的密码,使用 config set masterauth 主服务密码 命令的方式,例如:

1
2
127.0.0.1:6377> config set masterauth 123456
OK

注:默认情况下,每次与master断开后,都需要自行重新连接,除非你配置进redis.conf文件

配置只要在从库的redis.conf文件加上下面的语句即可:

1
2
3
4
# replicaof <masterip> <masterport>
replicaof 127.0.0.1 6380
# masterauth 主库密码
masterauth 123456

Tips:在执行完 replicaof 命令之后,从服务器的数据会被清空,主服务会把它的数据副本同步给从服务器。

关闭主从复制

我们可以使用 replicaof no one 命令来停止从服务器的复制,操作命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> role #查询当前角色
1) "slave" #从服务器
2) "192.168.1.71"
3) (integer) 6380
4) "connected"
5) (integer) 14
127.0.0.1:6379> replicaof no one #关闭同步
OK
127.0.0.1:6379> role #查询当前角色
1) "master" #主服务器
2) (integer) 1097
3) (empty list or set)

常用的主从方式

一主多仆

含义:就是一个Master节点多个Slave节点

一主多仆

通过info replication查看主从信息

1
2
3
4
5
6
7
8
9
10
11
# Replication
role:master
connected_slaves:0
master_replid:f6baff9abfda12ca58048cfce4b0e2c1f4683da1
master_replid2:e8fe596d47d9d1d923d56d884b28128b78d2c1e0
master_repl_offset:0
second_repl_offset:1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1585217521
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:adbec19afa734e84a333b07ea2f33c43c73fe743
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

注意:

  1. 第一次slave1 和slave2切入点,是全量复制之后是增量复制

    一主二仆

  2. 主机可以写,但是从机不可以写,从机只能读

    从机写的报错

  3. 主机shutdowm后,从机进入待机状态,等主机恢复后,主机新增记录从机可以顺利复制。

  4. 从机shutdowm后,每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件

  5. 从机复制到的数据,会被本机持久化。就算shutdown断开连接依然会有数据。

  6. 重新连接或者变更master,会清除之前的数据,重新建立拷贝最新的数据

薪火相传

含义:就是上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。

薪火相传

注意事项和一主多仆差不多,但注意虽然有slave是相对master,但是依然是slave

反客为主

1
SLAVEOF no one

使当前数据库停止与其他数据库的同步,转成主数据库

数据同步方式总结

完整数据同步

当有新的从服务器连接时,为了保障多个数据库的一致性,主服务器会执行一次 bgsave 命令生成一个 RDB 文件,然后再以 Socket 的方式发送给从服务器,从服务器收到 RDB 文件之后再把所有的数据加载到自己的程序中,就完成了一次全量的数据同步。

部分数据同步

在 Redis 2.8 之前每次从服务器离线再重新上线之前,主服务器会进行一次完整的数据同步,然后这种情况如果发生在离线时间比较短的情况下,只有少量的数据不同步却要同步所有的数据是非常笨拙和不划算的,在 Redis 2.8 这个功能得到了优化。

Redis 2.8 的优化方法是当从服务离线之后,主服务器会把离线之后的写入命令,存储在一个特定大小的队列中,队列是可以保证先进先出的执行顺序的,当从服务器重写恢复上线之后,主服务会判断离线这段时间内的命令是否还在队列中,如果在就直接把队列中的数据发送给从服务器,这样就避免了完整同步的资源浪费。

Tips:存储离线命令的队列大小默认是 1MB,使用者可以自行修改队列大小的配置项 repl-backlog-size。

无盘数据同步

从前面的内容我们可以得知,在第一次主从连接的时候,会先产生一个 RDB 文件,再把 RDB 文件发送给从服务器,如果主服务器是非固态硬盘的时候,系统的 I/O 操作是非常高的,为了缓解这个问题,Redis 2.8.18 新增了无盘复制功能,无盘复制功能不会在本地创建 RDB 文件,而是会派生出一个子进程,然后由子进程通过 Socket 的方式,直接将 RDB 文件写入到从服务器,这样主服务器就可以在不创建RDB文件的情况下,完成与从服务器的数据同步。

要使用无须复制功能,只需把配置项 repl-diskless-sync 的值设置为 yes 即可,它默认配置值为 no。

注意事项

主从同步有一些需要注意的点,我们来看一下。

数据一致性问题

当从服务器已经完成和主服务的数据同步之后,再新增的命令会以异步的方式发送至从服务器,在这个过程中主从同步会有短暂的数据不一致,如在这个异步同步发生之前主服务器宕机了,会造成数据不一致。

从服务器只读性

默认在情况下,处于复制模式的主服务器既可以执行写操作也可以执行读操作,而从服务器则只能执行读操作。

可以在从服务器上执行 config set replica-read-only no 命令,使从服务器开启写模式,但需要注意以下几点:

  • 在从服务器上写的数据不会同步到主服务器;
  • 当键值相同时主服务器上的数据可以覆盖从服务器;
  • 在进行完整数据同步时,从服务器数据会被清空。

复制命令的变化

Redis 5.0 之前使用的复制命令是 slaveof,在 Redis 5.0 之后复制命令才被改为 replicaof,在高版本(Redis 5+)中我们应该尽量使用 replicaof,因为 slaveof 命令可能会被随时废弃掉。

哨兵模式(sentinel)

反客为主的自动版,能够后台监控Master库是否故障,如果故障了根据投票数自动将slave库转换为主库。一组sentinel能同时监控多个Master。

使用步骤:

  1. 在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;

  2. 配置哨兵,在sentinel.conf文件中填入内容(可以配置多个):

    1
    2
    # 说明:最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。
    sentinel monitor 被监控数据库名字(自己起名字) ip port 1
  3. 启动哨兵模式(路径按照自己的需求进行配置):

    1
    redis-sentinel  /myredis/sentinel.conf

注意

  1. master挂掉后,会通过选票进行选出下一个master。而且只有使用了sentinel.conf启动的才能开启选票

  2. 当原来的master后来后,会变成了slave。

复制原理

  1. Slave启动成功连接到master后会发送一个sync命令;

  2. Master接到命令启动后的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步;

  3. 全量复制:而slave服务在数据库文件数据后,将其存盘并加载到内存中;

  4. 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步;

  5. 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。

复制的缺点

​ 延时,由于所有的写操作都是在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使得这个问题更加严重。

命令

命令 作用
slaveof 主库ip 主库端口 配置从库
info replication 查看redis主从复制的情况
slaveof no one 使当前数据库停止与其他数据库的同步,转成主数据库
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1 配置哨兵,监视master
redis-sentinel /myredis/sentinel.conf 以哨兵模式启动redis

SpringBoot整合哨兵模式

配置主从服务

为了方便测试,本用例只在一台机器上执行,实际情况不建议这么做,根据自己的情况调整。

在自己的redis安装目录下新建conf文件夹,并拷贝几份redis.conf文件

1
2
3
> cp redis.conf conf/redis6379.conf
> cp redis.conf conf/redis6380.conf
> cp redis.conf conf/redis6381.conf

修改reids.conf配置文件,以6379配置为例

1
2
3
4
5
6
7
8
9
10
11
12
13
vim redis6379.conf
#启用后台启动(测试情况下可以不设置)
daemonize yes
#pidfile位置
pidfile "/usr/local/redis/6379/redis6379.pid"
#端口
port 6379
#日志文件位置
logfile "/usr/local/redis/6379/log6379.log"
#rdb备份文件名称
dbfilename "dump6379.rdb"
#rdb备份文件路径
dir "/usr/local/redis/rdb/"

参考上面的步骤,修改6380、6381节点配置文件,但是要新增一行内容,如下:

1
replicaof 127.0.0.1 6379

:在很多地方都说这里不能配置成127.0.0.1localhost,但是我本地试了,这个ip可以配成127.0.0.1,而配置成具体的本地ip(例如192.168.50.96)不行,感觉这个和主Redis中bind的ip有关

打开三个标签页,依次启动master及两个slave

1
2
3
> redis-server conf/redis-6379.conf
> redis-server conf/redis-6380.conf
> redis-server conf/redis-6381.conf

连接master查看情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# master
> redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master # 主机
connected_slaves:2 # 有两台从机连接上了
slave0:ip=127.0.0.1,port=6380,state=online,offset=2012,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=2012,lag=1
master_replid:49a8783f6f8a2e425d38bcf97f5e36658b608ab3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2012
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2012
# slave
> redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
...

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 读写分离验证
127.0.0.1:6381> set k1 v1
(error) READONLY You can't write against a read only slave.

127.0.0.1:6380> set k1 v1
(error) READONLY You can't write against a read only slave.

127.0.0.1:6379> set k2 v2
OK

# 验证主从复制
127.0.0.1:6380> get k2
"v2"
127.0.0.1:6381> get k2
"v2"

配置哨兵

拷贝三份sentinel.conf文件到conf目录下

1
2
3
cp sentinel.conf conf/sentinel26379.conf
cp sentinel.conf conf/sentinel26380.conf
cp sentinel.conf conf/sentinel26381.conf

:看情况,也可以只拷贝一份,但是建议单数。

修改sentinel26379.conf

1
2
3
4
5
6
7
8
9
port 26379
daemonize no
pidfile /var/run/redis-sentinel26379.pid
logfile "sentinel-26379.log"
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes

同样的方法,修改sentinel26380.confsentinel26381.conf

开启三个标签页,启动哨兵

1
2
3
> redis-sentinel conf/sentinel26379.conf
> redis-sentinel conf/sentinel26380.conf
> redis-sentinel conf/sentinel26381.conf

代码

pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<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>
<!--redis连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8080

spring:
redis:
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认为8
max-active: 8
# 连接池中的最大空闲连接 默认为8
max-idle: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1
max-wait: -1ms
# 连接池中的最小空闲连接 默认为 0
min-idle: 0
sentinel:
# 主节点的别名
master: mymaster
# sentinel服务的ip和端口
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381

controller代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.redis.sentinel.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author D丶Cheng
* @Description: redis哨兵模式测试
* @date 2020/11/24 11:43 上午
*/
@RestController
@RequestMapping("/redis")
public class TestController {

@Autowired
RedisTemplate<String, String> redisTemplate;

@RequestMapping("/get")
public String get(String key) {
String value = redisTemplate.opsForValue().get(key);
return value;
}

@RequestMapping("/set")
public String set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
return "success";
}
}

此时运行项目,访问接口,即可进行redis的设置及获取。

测试重连

手动关闭master(截图中对应的是6380),可以看到系统会自动重连上最新选举出来的master(新选举出来的为6379)

image-20201124125413894

当重连上以后,无需重启项目,服务即可恢复正常。

如果不使用默认的lettuce,使用jedis的话,可以参考下面的文章:

https://www.cnblogs.com/spec-dog/p/12572120.html

https://blog.csdn.net/weixin_30409927/article/details/105793877