通过持久化功能,Redis保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。请求量增大时,读写不分离遇到瓶颈。
为了避免单点故障 和 读写不分离,使用redis的主从模式(一主一从或多从)既能实现即使有一台服务器出现故障,其他服务器依然可以继续提供服务,又能实现读写分离。Redis 提供了复制(replication)功能,可以实现master数据库中的数据更新后,会自动将更新的数据同步到其他slave数据库上。
redis的主从模式有一下特点:
主数据库可以进行读写操作,从数据库一般进行读操作,当写操作导致数据变化时会自动将数据同步给从数据库。
一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
从节点也可以有从节点,级联结构。
#主从数据库的配置:主数据库不用配置,只在从节点的redis.conf配置文件中加入下面配置,先启动主节点,再启动从节点即可。
slaveof 主数据库ip 主数据库port
#主从模式主要有两个作用:
(1)数据备份:当一个节点损坏(指不可恢复的硬件损坏)时,数据有备份,可以方便恢复。
(2)读写分离,负载均衡。主节点负载读写,从节点负责读,提高服务器并发量。
Redis 2.8以前采用的复制都为全量复制,使用SYNC命令全量同步复制,SYNC存在很大的缺陷就是:不管slave是第一次启动,还是连接断开后的重连,主从同步都是全量数据复制,严重消耗master的资源以及大量的网络连接资源。Redis在2.8及以上版本使用PSYNC命令完成主从数据同步,PSYNC同步过程分为全量复制和部分复制,完善了SYNC存在的缺陷。
Reids主从同步复制数据主要有2种场景:
1.从服务器从来第一次和当前主服务器连接,即初次复制,不管是SYNC 还是 PSYNC第一次都是全量同步复制数据。
2.从服务器断线后重新和之前连接的主服务器恢复连接,即断线后重复制,SYNC使用的是全量复制,PSYNC使用的是增量复制。
旧版本里redis的主从同步使用的是SYSNC全量主从复制,主从数据复制主要包含:复制初始化(从数据库初始化,全量同步)、命令传播操作(复制初始化结束后的复制)。 SYSNC命令主从复制的过程如下:
当slave节点启动后或断开重连后,会向master数据库发送SYNC命令。
master节点收到SYNC命令后会开始在后台保存快照(即RDB持久化,在主从复制时,会无条件触发RDB),并将保存快照期间接收到的命令缓存起来。
master节点执行RDB持久化完后,向所有slave节点发送快照RDB文件,并在发送快照期间继续记录被执行的写命。
slave节点收到快照文件后丢弃所有旧数据(会清空所有数据),载入收到的快照。
master节点快照发送完毕、slave节点载入快照完毕后,master节点开始向slave节点发送缓冲区中的写命令。
master节点完成对快照的载入,开始接收命令请求,并执行来自主数据库缓冲区的写命令。(从数据库初始化完成)
master节点每执行一个写命令就会向slave节点发送相同的写命令,slave节点接收并执行收到的写命令。(命令传播操作,slave节点初始化完成后的操作)
1)从服务器在同步时,会清空所有数据,服务器在与主服务器进行初连接时,数据库中的所有数据都将丢失,替换成主服务器发送的数据。
2)Redis不支持主主复制。
3)主从复制不会阻塞master(不会阻塞master处理客户端请求),因为RDB是异步进行的;相反slave在初次同步数据时会阻塞不能处理客户端读请求。
4)当多个从服务器尝试连接同一个主服务器的时候,就会出现以下两种情况:
一是:如果master节点保存快照还未执行,所有从服务器都会接收到相同的快照文件和相同缓冲区写命令。
二是:master节点保存快照正已经执行完毕,当主服务器与较早的从服务器完成以上全部步骤之后,主服务器会跟新连接的从服务器重新执行RDB保存快照。
5)如果从服务器连接的时机不凑巧的话,进行多次RDB保存快照,会大量消耗主服务器资源(CPU、内存和磁盘I/O资源)。
主要是主从服务器断线后重复制,即处于命令传播阶段的主从服务器由于网络断开,从服务器再次连接主服务器连接成功后,会进行RDB全量复制主服务器数据。如下面例子:在主从服务器断开后重新连接期间,master只执行了三个SET命令,导致从slave重连接后发送SYNC命令,重新进行了“全量”复制过程,RDB文件中包含k1-k10089全部的键。
其中可以明显看出slave重新连接master服务器之后,SYNC命令创建包含k1-k10089的RDB文件。而事实上只需要再同步断线后的k10087-k10089三个SET命令即可。SYNC的“全同步”对于从服务来说是不必要的。
SYNC命令进行主从同步主要有一下几个问题:
1)master服务器执行BGSAVE命令生成RDB文件,这个生成过程会大量消耗主服务器资源(CPU、内存和磁盘I/O资源)。
2)master需要将生成的RBD文件发送给slave,这个发送操作会消耗主从服务器大量的网络资源(带宽与流量)。
3)接收到RDB文件后,slave需要载入RDB文件,载入期间slave会因为阻塞而导致没办法处理命令请求(master不会阻塞)。
4)最大的问题是重连接后回全量同步数据。如上面例子,slave在断开后,再进行重新连时,slave丢掉以前的数据,行全量同步master数据,要是断开到重连期间执行的写命令很少,这种操作就没有必要了。
redis为了解决上面问题,redis在2.8及更高的版本里引入了PSYNC命令,实现了全量重同步 和 部分重同步模式。
为了解决旧版本中断线情况下SYNC进行全量复制(同步)的低效问题,在Redis 2.8之后使用PSYNC命令代替SYNC命令执行复制同步操作,PSYNC具备了数据全量重同步 和 部分重同步模式。
1.全量重同步:跟旧版复制基本是一致的,可以理解为“全量”复制。
2.部分重同步:salve断开又重新连时,在命令传播阶段,只需要发送与master断开这段时间执行的写命给slave即可,可以理解为“增量”复制。
PSYNC执行过程中比较重要的概念有3个:runid、offset(复制偏移量)以及复制积压缓冲区。
#1.runid
每个Redis服务器都会有一个表明自己身份的ID。在PSYNC中发送的这个ID是指之前连接的Master的ID,如果没保存这个ID,PSYNC的命令会使用”PSYNC ? -1” 这种形式发送给Master,表示需要全量复制。
#2.offset(复制偏移量)
在主从复制的Master和Slave双方都会各自维持一个offset。Master成功发送N个字节的命令后会将Master里的offset加上N,Slave在接收到N个字节命令后同样会将Slave里的offset增加N。
Master和Slave如果状态是一致的那么它的的offset也应该是一致的。
#3.复制积压缓冲区
复制积压缓冲区是由Master维护的一个固定长度环形积压队列(FIFO队列),它的作用是缓存已经传播出去的命令。当Master进行命令传播时,不仅将命令发送给所有Slave,还会将命令写入到复制积压缓冲区里面。
复制积压缓冲区本质上是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件设置队列大小:
#设置复制积压缓冲区大小,积压队列越大,允许主从数据库断线的时间就越长
repl-backlog-size 1mb
Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列,默认一小时:
#没有salve连接时,多久释放一次复制积压缓冲区
repl-backlog-ttl 3600
如上图,PSYNC执行过程和SYNC的区别在于:salve连接时,判断是否需要全量同步,全量同步的逻辑过程和SYNC一样。PSYNC执行步骤如下:
1.客户端向服务器发送SLAVEOF命令,即salve向master发起连接请求时,slave根据自己是否保存Master runid来判断是否是第一次连接。
2.如果是第一次同步则向Master发送 PSYNC ? -1 命令来进行完整同步;如果是重连接,会向Master发送PSYNC runid offset命令(runid是master的身份ID,offset是从节点同步命令的全局迁移量)。
3.Master接收到PSYNC 命令后,首先判断runid是否和本机的id一致,如果一致则会再次判断offset偏移量和本机的偏移量相差有没有超过复制积压缓冲区大小,如果没有那么就给Slave发送CONTINUE,此时Slave只需要等待Master传回失去连接期间丢失的命令。
如果runid和本机id不一致或者offset差距超过了复制积压缓冲区大小,那么就会返回FULLRESYNC runid offset,Slave将runid保存起来,并进行全量同步。
主节点在命令传播时,主数据库会将每一个写命令传递给从数据库的同时,都会将写命令存放到积压队列,并记录当前积压队列中存放命令的全局偏移量offset。当salve重连接时,master会根据从节点传的offset在环形积压队列中找到断开这段时间执行的命令,并同步给salve节点,达到增量同步结果。
还是用上面SYNC的例子,如下图:在主从服务器断开后重新连接期间,主服务器只执行了三个SET命令,slave重新连接后发送PSYNC命令进行部分重同步,只需要同步k10087-10089即可,不需要生成RDB文件。
其中可以明显看出slave重新连接master之后,没有再生成RDB文件,而是将与master失去连接期间丢失的命令同步给salve,实现了增量同步。
Redis采用了乐观复制的策略,也就是在一定程度内容忍主从数据库的内容不一致,但是保持主从数据库数据的最终一致性。具体来说,Redis在主从复制的过程中,本身就是异步的,在主从数据库执行完客户端请求后会立即将结果返回给客户端,并异步的将命令同步给从数据库,但是这里并不会等待从数据库完全同步之后,再返回客户端。这一特性虽然保证了主从复制期间性能不受影响,但是也会产生一个数据不一致的时间窗口,如果在这个时间窗口期间网络突然断开连接,就会导致两者数据不一致。如果不在配置文件中添加其他策略,那就默认会采用这种方式,乐观二字也就体现在这里。 当然了,上面这种方式并不是绝对的,只要牺牲一点性能,还是可以避免上述问题。在配置文件中:
#代表至少N台从服务器完成复制,才允许主服务器可写入,否则会返回错误。
min-slaves-to-write 3
#允许从服务器断开连接的时间(单位s)
min-slaves-max-lag 10
Redis不管是旧版还是新版,复制的实现都可以分为七个步骤,流程图如下:
建立套接字连接
从服务器根据设置的套接字创建连向主服务器的套接字连接,主服务器接收从服务器的套接字连接之后,为该套接字创建响应的客户端状态,并将此时的从服务器看做是主服务器的客户端,也就是该从服务器同时具备服务器与客户端两个身份。
发送PING命令
PING命令主要有两种作用:虽然建立了套接字连接,但是还未使用过,通过发送PING命令检查套接字的读写状态是否正常;通过发送PING命令检查主服务器能否正常处理命令请求,能处理主服务器回复PONG。
身份验证
从服务器接收到主服务器返回的“PONG”回复,接下来就需要考虑身份验证的事。如果从服务器设置了masterauth选项,那么进行身份验证,如果从服务器没有设置masterauth选项,那么不进行身份验证。
发送端口信息
在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port
同步
从服务器向主服务器发送SYNC命令、PSYNC命令,执行同步操作。
命令传播
主从服务器就会进入命令传播阶段,主服务器只要将自己执行的写命令发送给从服务器,而从服务器只要一直执行并接收主服务器发来的写命令。
总结:主从刚刚连接的时候,会进行全量同步;全同步结束后,进行增量同步。当然,如果有需要slave 在任何时候都可以发起全量同步。Redis的策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
2020年06月20号 晚 于北京记