Redis主从复制的配置和原理

Redis主从复制的配置和原理

配置

在复制模式中,数据库分为两种:主数据库(master)和从数据库(slave)。

主库可以进行读写操作,当写操作导致数据发生变化时,会自动将数据同步给从库。从库模式是只读的,并接受主库同步过来的数据。一个主库可以有多个从库,而一个从库只能拥有一个主库。

Redis中使用复制非常容易,只需要在从库启动时输入参数:

--slaveof host port

也可以在配置文件中加入该配置,那么就不需要在启动时输入了。

下面是我修改好的从库节点的Redis自动启动脚本:

#!/bin/sh
#chkconfig: 2345 80 90
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
REDISPORT=6379                          #端口号,这是默认的,如果你安装的时候不是默认端口号,则需要修改
REDISPATH=/usr/local/bin/                #redis-server启动脚本的所在目录,你如果忘了可以用find / -name redis-server 或whereis redis-server找到
EXEC=${REDISPATH}/redis-server
CLIEXEC=${REDISPATH}/redis-cli
PIDFILE=/var/run/redis_${REDISPORT}.pid  #在redis.conf中可找到该路径
CONF="/opt/redis-5.0.7/redis.conf"           #redis.conf的位置, 如果不和redis-server在同一目录要修改成你的redis.conf所在目录
case "$1" in
  start)
    if [ -f $PIDFILE ]
    then
        echo "$PIDFILE exists, process is already running or crashed"
    else
        echo "Starting Redis server..."
        $EXEC $CONF --slaveof 192.168.2.101 6379
    fi
    ;;
  stop)
    if [ ! -f $PIDFILE ]
    then
        echo "$PIDFILE does not exist, process is not running"
    else
        PID=$(cat $PIDFILE)
        echo "Stopping ..."
        $CLIEXEC -p $REDISPORT shutdown
        while [ -x /proc/${PID} ]
        do
          echo "Waiting for Redis to shutdown ..."
          sleep 1
        done
        echo "Redis stopped"
    fi
    ;;
  *)
    echo "Please use start or stop as first argument"
    ;;
esac

在启动时加入了slaveof参数。

启动3个Redis实例后,复制信息如下:

从数据库通过在配置文件中配置slave-read-only=no来关闭从数据库的只读,向从数据库中写入数据不会同步给任何其他的数据库,并且当主数据库对应数据发生变化就会覆盖从数据库。

如果当前数据库已经是其他住数据库的从数据库了, 在执行新的slaveof命令后,当前数据库会停止和原来的主数据库的同步,转而和新数据库同步。

另外,可以对从数据库使用 SLAVEOF NO ONE命令,来使当前数据库停止接收其他数据库的同步并转换成主数据库。

原理

复制过程

复制要处理的动作有:初始化、同步,断开重连。

初始化

  • 当一个从数据库启动后,会向主数据库发送SYNC命令;
  • 主数据库接收到SYNC命令后,会开始在后台保存快照,并将保存快照期间接收到的命令缓存起来;
  • 当完成快照后,Redis会将快照文件和所有缓存的命令发送给从数据库;
  • 从数据库收到后,会载入快照文件并直接收到的缓存命令;

同步

当完成初始化后,每当主数据库收到写命令,会就将命令同步给从数据库,从而保证主从数据库的数据一致。

断开重连

当断开重连后,Redis2.6以及之前的版本会重新进行初始化,缺点是即使从数据库可能仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传输给从数据库。效率很低,在网络环境不好时尤其明显。

Redis2.8版本对这种方式进行了一大改进,断线重连能够支持增量数据传输,当从数据库重新连接后,主数据库只需要将断线期间执行的命令传输给从数据库即可,从而大大提高了Redis复制的实用性。

Redis采用了乐观复制策略,容忍在一定时间内主从数据库不一致,但最终会同步。

具体说就是Redis在的主从复制过程本身是异步的,主数据库执行完客户端请求的命令后会立即将执行结果返回给客户端,并异步将命令同步给从数据库,这里要注意,Redis不会等待从数据库结果后,在向客户端返回结果(为了性能),这里就会产生一个主从不一致的窗口期。如果在主数据库传送给从数据库命令之前,两个数据库网络连接断开了,此时主从数据是不一致的。

不过Redis提供了两个配置来限制这种情况:

min-slaves-to-write 3
min-slaves-max-lag 10

min-slaves-to-write 3 表示只有当3个或3个以上从数据库连接到主数据库时,主数据库才可写。

min-slaves-max-lag 10表示允许从数据库最长失去连接的时间(秒),这个特性默认是关闭的。

从数据库持久化

为了保证数据持久化,一种做法是开始从数据库的持久化,同时禁用主数据库的持久化。当从数据库崩溃重启后,主数据库会自动将数据同步过来,所以无需担心数据丢失。

然后当主数据库崩溃时,情况就稍显复杂,需要手动通过从数据库数据恢复主数据库数据时,要严格按照以下两步进行:

  • 在从数据库使用SLAVEOF NO ONE命令将从数据库升级成主数据库;
  • 启动之前崩溃的主数据库,然后使用SLAVEOF命令将其设置成新的主数据库的从数据库;

当开启复制切关闭主数据库的持久化功能时,一定不要使用Supervisior以及类似的进行管理工具令主数据库崩溃后自动启动。这会导致从数据库中的数据被清空,造成所有数据的丢失。

因为主数据库重新启动后,因为没有开启持久化,所以数据库中的数据是空的,这时从数据库依然会从主数据库中接收数据,导致从数据库也被清空。

无硬盘复制

在复制模式下,即使已经关闭了RDB方式的持久化,只要执行复制还是会进行快照。

在Redis2.8.18版本开始,引入了无硬盘复制选项:

repl-diskless-sync yes

开始该选项,Redis在与从数据库进行复制初始化时将不会将快照内容存储到磁盘,而是直接通过网络发送给从数据库,避免了磁盘性能瓶颈。

增量复制

从Redis2.8开始,断开重连时,将采用增量复制,其原理是:

  1. 从数据库存储主数据库的RunID,每当实例重启后,就会自动生成一个新的RunID;
  2. 在复制同步阶段,主数据库将每一个命令传送给从数据库时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前队列中存放的命令的偏移量范围;
  3. 同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量;

基于上面3点,Redis2.8后,当主从连接准备就绪后,从数据库会发送PSYNC命令:

PSYNC 主数据库RunID 断开前的最新的命令偏移量

主数据库接收到PSYNC命令后,会执行以下判断来决定此次重连是否可以执行增量复制:

  • 判断RunID是否相同;
  • 命令偏移量是否在积压队列中,如果在则可以执行增量复制,并将积压队列中响应的命令发送给从数据库;

积压队列

本质上积压队列是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件中的repl-backlog-size选项来调整。

可以理解为积压队列越大,其允许主从断线的时间就越长。根据网络状态设置一个合理的积压队列很重要。积压队列保存的是命令,所以估算时只需要估计主从断线的时间中,主数据库可能执行的命令的大小即可。

与积压队列相关的另一个配置选项是repl-backlog-ttl,即当所有从数据库与主数据库断开连接后,经过多久时间可以释放积压队列的内存空间(默认1小时)。