什么是主从复制
我们知道要避免单点故障,即保证高可用,便需要冗余(副本)方式提供集群服务。而Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
主从复制的作用
主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从库之间采用的是读写分离的方式。
- 读操作:主库、从库都可以接收;
- 写操作:首先到主库执行,然后,主库将写操作同步给从库。
主从复制原理
全量复制
当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。
确立主从关系
例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例 2 上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:
replicaof 172.16.19.3 6379
全量复制的三个阶段
- 第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。这里有个地方需要注意,FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
- 第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。
具体来说,主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。这是因为从库在通过 replicaof 命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
- 第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。
增量复制
如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。
- repl_backlog_buffer:它是为了从库断开之后,如何找到主从差异数据而设计的环形缓冲区,从而避免全量复制带来的性能开销。如果从库断开时间太久,repl_backlog_buffer环形缓冲区被主库的写命令覆盖了,那么从库连上主库后只能乖乖地进行一次全量复制,所以repl_backlog_buffer配置尽量大一些,可以降低主从断开后全量复制的概率。而在repl_backlog_buffer中找主从差异的数据后,如何发给从库呢?这就用到了replication buffer。
- replication buffer:Redis和客户端通信也好,和从库通信也好,Redis都需要给分配一个 内存buffer进行数据交互,客户端是一个client,从库也是一个client,我们每个client连上Redis后,Redis都会分配一个client buffer,所有数据交互都是通过这个buffer进行的:Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中再通过网络发送出去,这样就完成了数据交互。所以主从在增量同步时,从库作为一个client,也会分配一个buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证主从数据一致,我们通常把它叫做replication buffer。
如果在网络断开期间,repl_backlog_size环形缓冲区写满之后,从库是会丢失掉那部分被覆盖掉的数据,还是直接进行全量复制呢?
对于这个问题来说,有两个关键点:
- 一个从库如果和主库断连时间过长,造成它在主库repl_backlog_buffer的slave_repl_offset位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。
每个从库会记录自己的slave_repl_offset,每个从库的复制进度也不一定相同。在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。
简单搭建一组多从
配置文件设置
首先在服务器跟目录创建一个文件夹
myredis
(方便操作)- 把redis.conf配置文件复制到/myredis文件夹中
- 因为在一个服务中,不能用同一个端口,每个redis服务都设置不同的端口
比如我们要创建一主两从的操作,就需要三个配置文件
- redis6379.conf
- redis6380.conf
- redis6381.conf
公共的配置可以用include
引入同一个redis.conf
- 在三个配置文件中写入内容
三个配置文件只有这么点东西,因为公共的配置都引用了redis.conf,单独的配置文件就不用重复写一些共同的配置了。
下面的redis6379.conf
的配置,其他文件的配置,改对应的端口名。如果有访问密码,在从库的配置文件里面应该设置主库访问密码
主库的配置文件也要设置,哨兵模式,主库挂了,就变从库了,就要去连接新的主库
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
masterauth 主库密码
- 启动三个redis服务
连接这些redis服务器
#在任意目录都可以运行redis-cli,-p 后面跟启动redis的端口
redis-cli -p 6379
通过
info replication
可以查看当前redis是主还是从
没做任何配置之前,三个都是主服务。
我在6379上设置了一个值k1,在6380上查看不到,他们都是相互独立的
配置主从关系
在从服务器上执行
#成为某个实例的从服务器
slaveof <ip> <port>
然后查看一下,就发现有了主从关系
去6379这个客户端查看一下,有了从库
现在我在主库里面设置的key,从库也可以访问。就不能在从库里面设置值了一主二仆
如上简单搭建,如果6380从库挂了,这时候6379主库的从库就只有一个6381了。
6380重新连上,这时候6380是作为主库,并不是6379的从库。先前设置主从关系是用命令行设置的,一次性的。服务器挂了就失效了。想要固定主从关系,需要设置配置文件。
如果在6380挂了的期间,6379又设置了一些值。6380设置了6379的从库后,这些6379全部的数据会同步给6380,这是主从复制原理里面的全量复制。
而如果6379挂了,6380,6381的主库还是6379,不过状态是down,从库不会篡位。6379连上后还是主库,还是有两个从库。薪火相传
套娃。
从库还还可以当其他库的主库。跟上面的配置主从关系一样的用法。
这种薪火相传的结构其实就是,树结构。反客为主
主库挂了,从库上位做主库。
最好在实现薪火相传的基础上,不然从库要是没有子从库,上位做了主库,光杆司令。#在从库端执行,将从库变主库,不过这是手动执行的,不方便,哨兵模式就是自动执行的。
slaveof no one
哨兵模式
反客为主的自动版,能够后台监督主机是否故障,如果故障了,根据投票数自动将从库转换为主库。
搭建哨兵模式
上面的例子,6379主,6380从,6381从。
在
/myredis
目录里面创建sentinel.conf
文件,名字绝不能错。
这个文件其实在redis的解压目录里面就有,可以直接复制过去用。
- 在sentinel.conf里面填上内容
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel auth-pass mymaster 密码
- sentinel:哨兵
- monitor:监视
- mymaster:给被监视的主机取得一个名称
- 被监视的ip
- 被监视的port
- 最后的数字1为:至少有多少个哨兵(从机)同意迁移的数量
- -
- 第二排是设置主机的密码
在一个新的客户端运行启动哨兵
redis-sentinel sentinel.conf
测试哨兵
我把主机挂了
不一会,哨兵端就有消息打印出来了,大概就是说6379主机挂了,现在选举6381作为主机,6380和6379作为6381的从机
在原来的主机挂了的期间,新主机是没有这个原主机的从机的。当原主机上线了,就自动成为了新主机的从机,哨兵修改了配置文件,配置文件启动就是从机。
原主机上线后,如果发现新主机的从机里面没有原主机,而原主机又是新主机的从机,说明原主机的配置文件里面没有设置主机的密码,访问不了主机。
选举新主
- 优先级:在 redis.conf 中默认 slave-priority 100(高版本的Redis是:replica-priority 100),值越小优先级越高。
- 偏移量:指获得原主机数据最全的概率,A原主机有10条数据,B从机有9条,C从机有8条,B上位
- runid:每个 redis 实例启动后都会随机生成一个 40 位的 runid。