Redis:分布式 - 主从复制

Redis:分布式 - 主从复制


概念

Redis的最佳应用,还是要在分布式系统中。对于非分布式系统,也就是只有一个主机提供服务,此时一旦该主机崩溃,那么整个服务就崩溃了,这称为单点问题

因此引入分布式系统,多个主机共同完成一个服务,既能提高服务并发的能力,又能避免单点问题,就算一个主机崩溃了,还有其他主机可以处理服务。

而分布式系统,常见的模式为以下三种:

  1. 主从模式
  2. 主从 + 哨兵模式
  3. 集群模式

主从模式,顾名思义就是有主服务器(主节点)和从属服务器从节点,此时从节点的所有数据都要去和主节点同步。那么从节点就要去复制主节点的数据,在Redis中,从节点复制了主节点的数据后,不允许修改,确保了数据完全来自于主节点。

这种模式下,写入操作都会分配给主节点,而所有查询操作都分配给从节点,这样就能保证主节点的数据都是最新的,从节点通过数据同步,完成数据更新。

本博客讲解Redis的主从模式。


配置主从模式

由于大部分人手上都只有一台主机或者云服务器,此时想要打造一个分布式系统就需要用一些其他技巧,而不是真的在多个主机上部署分布式。

其实在一台主机上,是可以允许多个redis-server进程的,只要保证每个进程的端口号不同,那么就可以有多个redis-server存在。

首先找一个合适的位置,创建一个目录,用于存放从节点的配置文件:

拷贝redis,conf配置文件到当前目录,由于我打算创建两个丛节点,所以拷贝了两份。

找到port选项:

cpp 复制代码
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379

默认的端口号是6379,此端口号修改为其它端口,不要与主节点冲突。

找到daemon选项

cpp 复制代码
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
# When Redis is supervised by upstart or systemd, this parameter has no impact.
daemonize yes

保证该选项是yes,这样Redis才能在后台运行。

修改这两个配置文件后,通过以下指令启动:

cpp 复制代码
redis-server 配置文件地址

启动后通过ps查看,可以看到同时有三个Redis在运行:

我绑定的三个端口分别是:637963806381

想要启动不同的客户端,只需要通过-p选项指定不同的端口:

但是此时三个节点是单独的三个服务,还没有构成主从结构。

配置主从需要通过slaveof,有以下三种方式:

  1. 在配置⽂件中加⼊slaveof {masterHost} {masterPort},当Redis启动时生效
  2. redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort}
  3. 直接使⽤redis命令:slaveof {masterHost} {masterPort}

此处通过修改配置文件完成主从配置,因为其是持久的,后两种方式在每车次启动时都要输入额外的命令。

在两个slave.conf的最末尾加上以下内容:

cpp 复制代码
# 配置主从复制
slaveof 127.0.0.1 6379

再重启两个服务,此时两个节点就变成了主节点的从节点了。

要先杀掉两个进行,kill -9 PID

随后再通过之前的命令启动:

cpp 复制代码
redis-server ./slave1.conf 
redis-server ./slave2.conf 

通过netstat查看网络情况:

可以发现,除了三个redis-server,还有很多其它的redis网络连接,这是因为主从之间,要进行数据传输,所以要创建额外的网络连接。

测试一下:

左侧端口为6379主节点,右侧为6380从节点,主节点设置key1 111,从节点可以get得到,但是当从节点试图写入数据,发生报错,表示不允许修改数据。


info replication

通过指令info replication,可以查看主从相关的信息。

  • 主节点6379
bash 复制代码
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1602971,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=1602971,lag=0
master_failover_state:no-failover
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1602971
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:551881
repl_backlog_histlen:1051091
  • role:表示当前节点为主节点

  • connected_slaves:当前有两个从节点

  • slave0:第一个从节点的相关信息,

    • ip:地址
    • port:端口
    • state:状态
    • offset:同步情况,多个从节点的该值可能不同,因为同步是要时间的
    • lag:当前主从节点之间数据传输的延迟
  • master_replid:主节点的专属id

  • offset:主节点的数据进度,与从节点的offset匹配,如果从节点的offset小于主节点,说明从节点没有同步完毕,数据版本是落后的

  • repl_backlog_xxx:积压缓冲区,后续讲解

  • 从节点6380

bash 复制代码
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:1602985
slave_repl_offset:1602985
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1602985
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:551895
repl_backlog_histlen:1051091
  • master_xxx:主节点的一些信息
  • slave_priority:一个优先级,如果主节点崩溃了,会从新选主节点,与该优先级有关
  • master_replid:主节点的id

这些内容也不需要记忆,可以去官网查询,官方文档有很详细的解释。


slave-read-only

默认情况下,从节点只能读取数据。其实从节点也可以通过配置文件修改的,只需要把slave-read-only=no,此时从节点就可以修改数据。但是要注意的是,从节点修改的数据,是不会同步给主节点的,这就会导致主从的数据不一致,所以不要修改这个配置文件。


tcp-nodelay

Redis主从之间,是通过TCP连接完成数据的同步的,在TCP内部,有一个nagel算法,它是一种捎带应答机制的实现。如果启用了该选项,那么TCP的延迟就会变高,但是消耗的带宽会降低。

而主从之间有时候需要高频同步数据,这就对TCP延迟有很高的要求,如果延迟太大,主从数据不一致,查询时就很有可能得到过期的数据。那么此时就可以通过设置tcp-nodelay,关闭这个nagel算法,这样虽然带宽增加了,但是延迟就会降低。

这两种方式传输没有好坏之分,而是根据场景需求,如果网络环境比较复杂,消耗带宽太高,就很容易丢包,这就得不偿失了。比如在同一个机房内部署的服务器,那么此时网络就很简单,建议关闭这个nagel算法。但是如果是跨越很远的数据传输,比如说在好几个地域布置了机房,构建了一个跨越全国甚至全球的分布式系统,此时还是开启这个nagel比较好。


命令

slaveof

  • slaveof no one用于断开主从关系,从节点执行该命令后,就不再有主节点了,而是自成主节点。

示例:

从节点6380执行slaveof no one后,再次info replicationrole:master说明此时自己已经是主节点了,不再从属其他节点。要注意的是,从节点变成主节点后,原先的数据不会丢失,可以继续操作原先的数据。

  • slaveof用于设置当前节点为其它节点的主节点。

语法:

bash 复制代码
slaveof ip port 

示例:

此处通过slaveof重新将6379设置为了6380的主节点。

不论是salveof,还是slaveof no one,都是临时修改主从关系,一旦服务重启,仍然依照配置文件设置主从关系。


主从结构

Redis中,有很多种主从的组织方式,它们构成了不同的拓扑结构。

一主一从

最简单的主从结构,自然是一主一从,如下:

这种结构的功能,一般是为了防止单点问题,如果主节点崩溃,此时从节点立刻代替主节点完成服务。当写命令并发量较高时,从节点上开始AOF持久化方式,但是主节点只使用RDB方式。这样从节点就代替主节点完成持久化,而主节点可以空出更多的资源来完成写入操作。

但是这种方式要关闭主节点的自动重启功能,因为主节点使用RDB持久化,此时数据往往不是最新的。一旦主节点重启,那么就会通过RDB恢复数据,导致主节点得到旧数据。而这个旧数据又会同步给从节点,此时从节点的AOF新数据就被旧数据覆盖了,这就很坑了。


一主多从

实际上,数据库的查询操作频率是远大于插入操作的,所以一主多从往往是更常见的选择,让多个从节点完成读取数据的任务,来提高并发能力。

  • 星形结构

这种情况下,往往还需要一个负载均衡器,来分发读取的流量到多个从节点上,让多个从节点接收差不多的访问量。

但是这就会导致主节点要同时与好几个从节点进行数据同步,那么主节点的网络压力会很大,因此又出现了以下的树形结构。

  • 树形结构

这种结构下,主节点只需要与少量的从节点进行同步,而其它节点作为从节点的从节点,完成二次同步。这样就可以有效减少主节点的网络压力,但是这种情况下,数据的同步时间会变长,因为要经过多层的同步。

树形结构和星形结构也是各有优劣,如果希望降低同步的时延,不怕主节点承担太多网络压力,就用星形结构。如果希望主节点的网络压力得到缓解,可以牺牲一部分同步时延,那么使用树形结构。


主从复制流程

接下来看一下主从关系是如何建立的,流程如下:

  1. 保存主节点信息:

保存主节点的ipport等信息

  1. 建立主从TCP连接

从节点内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与主节点建立基于TCP 的网络连接。如果从节点无法建立连接,定时任务会无限重试直到连接成功或者用户停止主从复制。

  1. 从节点发送ping命令

连接建立成功之后,从节点通过 ping命令确认主节点在应用层上是工作良好的。

  1. 权限验证

如果主节点设置了requirepass 参数,则需要密码验证,从节点通过配置 masterauth参数来设置密码。如果验证失败,则从节点的复制将会停止。

  1. 同步数据集

对于首次建立复制的场景,主节点会把当前持有的所有数据全部发送给从节点,这步操作基本是耗时最长的,所以又划分称两种情况:全量同步部分同步

  1. 命令持续复制

当从节点复制了主节点的所有数据之后,针对之后的修改命令,主节点会持续的把命令发送给从节点,从节点执行修改命令,保证主从数据的一致性。

接下来详细讲解一下,到底Redis是如何完成第5,6步骤中数据的同步的。


数据同步命令

Redis中提供了一个命令psync,其可以完成主从之间的数据同步过程。

语法:

cpp 复制代码
psync replcationid offset

这两个参数,需要进行简单的讲解。

  • replication id

replid称为复制ID,每次主节点重启,或者从节点晋升为主节点,都会生成一个replid,当从节点与主节点建立连接后,从节点就可以得到主节点的replid

通过info replication,可以查询到以下内容:

cpp 复制代码
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000

第一个master_replid,就记录着主节点的replid,那么master_replid2的作用是什么?

有的时候,主节点网络不好,从节点会误判主节点下线,此时从节点就会晋升为主节点,也会生成自己的replid。此处的master_replid2,会把之前的主节点的replid记录下来。

等到主节点网络恢复后,重新与从节点建立连接,此时从节点不知道主节点是重启了,还是网络恢复了。就会拿master_replid2与主节点的replid对比,如果相同,说明主节点没有重启,只是网络出问题了,此时从节点会重新认主。如果不同,那么就是主节点下线了。

  • offset

offset称为偏移量,其用于维护节点的数据进度,每当主节点写入数据后,都会把命令的字节长度进行累加记录到offset中。也就是说,随着数据的写入,这个值会越来越大。

而从节点每次更新数据,也会增加自己的偏移量,表示自己同步的进度。当从节点的偏移量和主节点的偏移量完全相同,那么说明从节点已经同步到主节点的所有数据了。

replidoffset 共同确定一个唯一的数据集

只要 replidoffset 相同,那么两个节点上的所有数据完全相同。

cpp 复制代码
psync replcationid offset

如果 offset-1,那么此时进行全量复制,将所有的数据都复制一份到从节点。如果offset为具体的一个偏移量,那么从节点将从偏移量开始往后的所有数据都复制过来。

如果psync不传入任何参数,那么replid offset默认为? -1,表示从节点不知道主节点的replidoffset

但是不论从饥饿点发送什么命令,如果主节点过于繁忙,那么此时不一定会执行从节点预期的复制方式。比如从节点申请进行全量复制,此时主节点无法承受这么多网络压力,那么可能就会变成部分复制。

当输入psync后,可能得到以下三种结果:

  1. +FULLRSYNC replid offset:进行全量复制
  2. +CONTINUE:进行部分复制
  3. -ERR:说明主节点不支持psync,此时可以使用sync

此处sync是在前台运行的命令,一旦执行sync,主节点的所有命令都会被阻塞。


全量同步

全量复制是 Redis 最早支持的复制方式,主从第一次建立复制时必须进行一次全量复制。

全量复制流程图如下:

  1. 从节点发送 psync命令给主节点进行数据同步,由于是第一次进行复制,从节点没有主节点的运行 ID 和复制偏移量,所以发送 psync ? -1
  2. 主节点根据命令,解析出要进行全量复制,回复+FULLRESYNC响应。
  3. 从节点接收主节点的运行信息进行保存,比如主节点的replid
  4. 主节点执行 bgsave 进行 RDB 文件的持久化。这一步与第三步同时进行,就算主节点已经有RDB文件了,但是由于RDB的数据进度是比较落后的,所以还是要重新生成一个。
  5. 主节点发送 RDB 文件给从节点,从节点保存 RDB数据到本地硬盘。
  6. 主节点将从生成 RDB 到接收完成期间执行的写命令,写入缓冲区中,等从节点保存完 RDB 文件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照RDB的二进制格式追加写入到收到的 RDB文件中,保持主从一致性。
  7. 从节点清空自身原有旧数据。
  8. 从节点加载 RDB 文件得到与主节点一致的数据。
  9. 如果从节点加载 RDB完成之后,开启了 AOF持久化功能,它会进行bgrewrite 重写操作,因为上面这个过程从节点收到了大量数据,此时AOF也在同步记录数据,此时进行一次重写,对刚才收到的数据进行一个压缩。
  • 无硬盘模式

新版的Redis,对以上过程又做了优化,主节点在生成RDB文件时,要把数据写入文件,然后再把文件传输给从节点。而无硬盘模式下,主节点生成了RDB格式的数据后,不会写入文件,而是直接发送给从节点。这样就减少了大量硬盘IO,提高了同步效率。

从节点也相同,之前的从节点会把收到的RDB数据存储到硬盘生成文件,然后再加载文件。无硬盘模式下,从节点直接加载网络中的RDB数据,不再写入硬盘。


部分同步

部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,使用 psync replicationld offset 命令实现。当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区存在数据,则直接发送给从节点,这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据,所以开销很小。

部分复制流程如图所示:

  1. 当主从节点之间出现网络中断时,如果超过repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
  2. 主从连接中断期间,主节点依然响应命令,但这些复制命令都因网络中断无法及时发送给从节点,所以暂时将这些命令滞留在复制积压缓冲区中。
  3. 当主从节点网络恢复后,从节点再次连上主节点。
  4. 从节点将之前保存的 replicationldoffset 作为 psync 的参数发送给主节点,请求进行部分复制。
  5. 主节点接到 psync 请求后,进行必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据并响应 +CONTINUE 给从节点。
  6. 主节点将需要从节点同步的数据发送给从节点,最终完成一致性。

实时同步

主从节点在建立复制连接后,主节点会把自己收到的修改操作,通过 TCP 长连接的方式,源源不断的传输给从节点,从节点就会根据这些请求来同时修改自身的数据,从而保持和主节点数据的一致性。

另外,这样的长连接,需要通过心跳包的方式来维护连接状态,(这里的心跳是指应用层自己实现的心跳,而不是 TCP 自带的心跳)。

  1. 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信。
  2. 主节点默认每隔 10 秒对从节点发送 ping命令,判断从节点的存活性和连接状态。
  3. 从节点默认每隔1秒向主节点发送 replconfack{offset}命令,给主节点上报自身当前的复制偏移重。

如果主节点发现从节点通信延迟超过repl-timeout配置的值(默认60秒),则判定从节点下线,断开复制客户端连接。从节点恢复连接后,心跳机制继续进行。


那么以上三种同步机制,和主从复制有什么关系?在整个主从同步的过程中,最后两步分别是同步数据集持续复制命令

  • 同步数据集:
    • 如果是第一次同步,触发全量同步
    • 如果是断线重连,触发部分同步
  • 持续复制命令:
    • 进行实时同步

节点晋升

先前提及过很多次,从节点是可以晋升为主节点的,那么什么时候从节点会晋升?

这分情况,对于一般的主从复制,情况如下:

  1. 主动断开主从关系

用户可以通过slaveof no one命令,断开与主节点的连接,此时从节点断开连接后,自动晋升为主节点。

  1. 主节点崩溃

在一般的主从情况下,如果主节点崩溃,此时从节点不会自动晋升,需要用户进行手动操作。这是一个非常难办的问题,因为服务器崩溃是不可预知的,如果大半夜服务崩溃了,此时程序员又不能及时重启,就会造成很大麻烦。后续Redis引入了哨兵机制,处理这种情况下的自动晋升。


相关推荐
夜泉_ly1 小时前
MySQL -安装与初识
数据库·mysql
power-辰南2 小时前
高并发系统架构设计全链路指南
分布式·系统架构·高并发·springcloud
qq_529835352 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New5 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6755 小时前
数据库基础1
数据库
我爱松子鱼5 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo5 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser6 小时前
【SQL】多表查询案例
数据库·sql
Galeoto6 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto7 小时前
详解Redis在Centos上的安装
redis·centos