Redis复制概述

主从配置

在Redis Replication中,节点分为master和slave两个角色,复制是单向由master复制到slave中。配置slave同步有三种方式:

  • 在配置文件中加入slaveof {master_host} {master_port}
  • redis-server启动实例时加入–slaveof {master_host} {master_port}
  • 直接执行slaveof {master_host} {master_port}命令

Tips:如果启用了AUTH认证登陆还需要指定MASTERAUTH参数来设置主节点的密码

当slaveof执行后,复制流程便开始工作。其主要步骤如下:

  • 保存master信息(IP和PORT)
  • 从节点内部每秒运行定时任务维护复制逻辑,在发现master时建立socket来接收master发送的命令,如果无法连接会一直尝试
  • 发送PING命令
  • 权限验证
  • 同步数据集
  • 持续复制

数据同步

当主从配置完成可以通过info replication命令查看复制状态

master:
10.0.139.163:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:199352d10e47e6ca6ef062e1855cb72920f76320
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

slave:
10.0.139.162:6379> info replication
# Replication
role:slave
master_host:10.0.139.163
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:56
slave_priority:50
slave_read_only:1
connected_slaves:0
master_replid:265e58b0e92fc2c7399f4e0c231e32bc331823a3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:56

默认情况下,从节点为只读模式,由参数slave-read-only控制,为避免修改从节点带来的数据不一致,强烈建议设置为只读模式。如果想要断开当前复制,可以执行slave no one断开主从的复制关系。

当Redis复制跨机房部署,就需要考虑网络延迟对复制的影响。Redis提供了参数repl-disable-tcp-nodelay控制是否关闭TCP_NODELAY,默认为关闭。当关闭时,主节点的命令数据无论大小都会及时发送给slave,这样就减少延迟增加了网络带宽消耗;当开启时,主节点会合并较小的TCP数据库包节省带宽,默认发送时间取决于Linux内核,通常为40毫秒。

psync

Redis 2.8开始采用psync命令完成主从数据同步,同步分为全量复制和部分复制。全量复制一般用于初始化复制阶段,它会把master所有数据一次性发送给slave;部分复制用于处理因网络闪断等原因造成的同步数据丢失,当slave再次连上时master会重新发送丢失的数据。

psync命令需要复制偏移量、积压缓冲区、运行ID来支持。

  • 复制偏移量:每个节点都会维护自身的复制偏移量,master执行完命令后会把命令的字节长度做累加记录,其指标在master_repl_offset中可以查看。slave每秒上报自身的复制偏移量给master,因此master也保留了slave的复制偏移量。slave在同步执行命令后也会累加自身的复制偏移量,slave_repl_offset可以看到。
  • 复制积压区:复制积压区是master上一个固定长度的队列,默认为1MB,master不仅将命令发送给slave,也会同步也入复制积压区。该队列采用先进先出,能够保存最近复制的数据。在master是上可以通过info replication查看积压区相关信息,repl_backlog_archive=1表示开启复制缓冲区,repl_backlog_size为缓冲区最大长度,repl_backlog_first_byte_offset表示起始偏移量,repl_backlog_histlen表示已保存数据的长度。
  • 运行ID:Redis在启动时会分配一个40位的十六进程字符串作为运行ID,能够标识唯一Redis实例。当Redis重新启动时,运行ID也随之改变,从节点将做全量复制。如果想要重启后运行ID不变可以debug reload命令重新加载并保持ID不变,但命令会阻塞Redis进程,生成RDB文件并清空数据再加载RDB文件。

psync命令格式为

psync {runId} {offset}

全量复制

全量复制是Redis最早开始就支持的复制方式,主要通过psync命令实现。全量复制的工作流程如下:

  1. 发送psync命令进行数据同步,由于是第一次进行复制,会直接发送psync ? -1
  2. 主节点根据psync ? -1判断为全量复制,回复+FULLRESYNC
  3. slave接收到master的运行ID和偏移量
  4. 主节点执行bgsave生成RDB文件到本地
  5. 主节点发送RDB持久化文件给slave,需要注意的是如果RDB文件比较大,传输将会比较耗时,并且时间超过repl-timeout(默认60秒),从节点会放弃接收RDB并清空已下载的文件,导致复制失败
  6. 主节点仍然响应读写,并把期间产生的数据命令保存在复制客户端缓冲区,待全量复制完成后再批量发送给slave。期间,如果全量复制时间太长,容易造成复制客户端缓冲区内存溢出。参数默认配置为client-output-buffer-limit slave 256MB 64MB 60,其表示为60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点自动关闭slave连接,导致复制失败。
  7. slave在接收完成后会后台清空自身数据
  8. slave加载master传过来的RDB文件
  9. 加载完成后,如果slave开启了AOF持久化,会立即做bgrewriteaof操作

全量复制花费的时间=master bgsave的时间+RDB文件传输时间+slave清空并加载RDB文件时间

Tips:Redis 2.8.18开始支持无盘复制,能够通过子进程直接网络发送RDB文件,适用于磁盘IO性能较差的环境,由参数repl-diskless-sync控制

部分复制

部分复制是对全量复制做出的优化,当网络闪断等异常情况时,slave会向master重新同步丢失的命令数据,如果master上的复制积压区还存在这部分数据则会直接发送给slave。从而避免了不必要的全量复制,减小复制开销。其工作流程如下:

  1. slave通过保存的master运行ID和自身复制偏移量运行psync命令
  2. master验证运行ID并根据offset在复制积压缓冲区查找,如果存在则发送+CONTINUE给slave,表示进行部分复制。
  3. master把数据发送给slave,保证slave进入正常复制状态。

心跳

主从复制会建立长连接并批次发送心跳检测命令,各自模拟成客户端与对方通信,主节点默认10秒发送一次PING命令,判断slave的是否存活可连接;slave每1秒发送replconf ack {offset}命令,上报自身偏移量给master。

Redis主从安装配置

解压安装包

$ tar -xvf redis-4.0.9.tar.gz

编译安装

$ cd redis-4.0.9
$ make && cd src
$ make install PREFIX=/usr/local/redis -j 4

创建文件夹

$ mdkir /service/redis/data
$ mkdir /service/redis/logs

配置参数文件
基础参数

参数 建议值 说明
daemonize yes 是否以后台进程启动
databases 6 创建database的数量(默认选中的是database 0)
port 自定义 进程对应的端口,默认为6379
tcp-keepalive 60 指定TCP连接是否为长连接,0则关闭,非0则开启
loglevel notice Server日志级别:debug调试模式、verbose、notice、warning
protected-mode no 外网保护模式,禁止外网访问redis,如果要设置为no,需确保数据库不会暴漏在外网,设置为YES的话需要设置bind绑定内网IP或设置实例密码
pidfile 自定义 指定pid文件的存放位置
logfile 自定义 指定日志文件的存放位置
requirepass 自定义 设置redis登录密码
maxmemory 80% 设置redis最大内存大小,单位为byte
bind 自定义 设置绑定的网口IP,用于接收服务请求
hz 10 Server执行后台任务的频率

持久化参数

参数 建议值 说明
Save 生产仅用做缓存时不建议开启 rdb持久化策略,例如save 300 1表示5分钟内至少一个key变更则触发持久化操作,禁用rdb持久化可设置save “”
rdbcompression yes 是否启用rdb文件压缩,默认为yes
rdbchecksum yes 是否对rdb文件使用CRC64校验和
stop-writes-on-bgsave-error yes 当bgsave持久化写入错误时是否停止
dbfilename 自定义 设置rdb文件名称,默认为dump.rdb
dir 自定义 指定持久化文件的存放位置
appendonly yes 是否开启AOF持久化
appendfilename 自定义 指定AOF持久化文件名称,默认为appendonly.aof
appendfsync everysec AOF持久化策略,可选值有always、everysec、no
no-appendfsync-on-rewrite no 表示rewrite重写AOF时,是否阻塞AOF持久化,no表示会阻塞
auto-aof-rewrite-percentage 100 触发AOF文件rewrite的条件之一,AOF文件大小增长一倍
auto-aof-rewrite-min-size 64mb 触发AOF文件rewrite的条件之一,AOF文件大小大于64M
aof-rewrite-incremental-fsync yes AOF rewrite是否采取增量文件同步策略

复制参数

参数 建议值 说明
slaveof 自定义 当前为从库时,设置同步主库信息
masterauth 自定义 当前为从库时,设置主库的登录密码
repl-timeout 3600 复制超时时间,单位为秒,默认为60
slave-server-stale-data yes 当前为从库时,在与主库连接断开时,是否继续提供服务
slave-read-only yes 当前为从库时,可读
redis-disable-tcp-nodelay no 主从是否延迟传输
slave-priority 自定义 指定slave的优先级,在哨兵模式下,主库宕机,会将优先级最小的提升为master。如果设置为0则永远不会提升为master

慢日志参数

参数 建议值 说明
slowlog-log-slower-than 10000 慢查询日志记录阀值,单位为微秒,超过阀值将被记录慢查询日志
slowlog-max-len 500 控制slow log保留多少条记录,超过后将删除最旧的一条记录

对象参数

参数 建议值 说明
hash-max-ziplist-entries 512 hash类型默认采用ziplist结构编码,超过该值则采用hashtable
hash-max-ziplist-value 64 ziplist允许条目value值的最大字节数
list-max-ziplist-entries 512 list类型默认采用ziplist结构编码,超过该值则采用linkedlist
list-max-ziplist-value 64 ziplist允许条目value值的最大字节数
set-max-intset-entries 512 intset中允许保存的最大条目数,如果达到阀值,才重构为hashtable
zset-max-ziplist-entries 128 zset类型默认采用ziplist编码结构,超过阀值将重构为skiplist
Zset-max-ziplist-value 64 zset允许条目value值的最大字节数
activerehashing yes 是否开启顶层数据结构得rehash功能

客户端及安全配置

参数 建议值 说明
client-output-buffer-limit normal 0 0 0 客户端buffer控制,normal表示普通连接,hard表示最大值,一旦到达立即关闭连接,soft表示容忍值,和seconds配合表示在超过seconds则关闭连接,都设置为0则表示禁用
client-output-buffer-limit slave 0 0 0 客户端buffer控制,slave表示从库连接,hard表示最大值,一旦到达立即关闭连接,soft表示容忍值,和seconds配合表示在超过seconds则关闭连接,都设置为0则表示禁用
client-output-buffer-limit pubsub 32mb 8mb 60 客户端buffer控制,pubsub表示pub/sub类型连接,hard表示最大值,一旦到达立即关闭连接,soft表示容忍值,和seconds配合表示在超过seconds则关闭连接,都设置为0则表示禁用
rename-command KEYS alias-keys 重命名keys命令,生产环境执行keys *可能会造成实例阻塞
rename-command FLUSHALL alias-flushall 重命名FLUSHALL命令
rename-command FLUSHDB alias-flushdb 重命名FLUSHDB命令

参数文件(/etc/redis.conf)

daemonize yes
port 66803
timeout 0
tcp-keepalive 60
loglevel notice
databases 6
protected-mode no
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename "dump.rdb"
dir "/service/redis"
pidfile "/service/redis/pid.file"
logfile "/service/redis/redis.log"
requirepass "Abcd123#"
maxmemory 24576000000
bind 10.240.204.157
repl-timeout 3600
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 0 0 0
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
slave-priority 50
rename-command KEYS alias-keys
rename-command FLUSHALL alias-flushall
rename-command FLUSHDB alias-flushdb

Tips: 从库还需要配置slaveof和masterauth参数

配置环境变量

$ echo "export PATH=$PATH:/usr/local/redis/bin" >> /etc/profile
$ source /etc/profile

启动数据库并查看主从同步

$ redis-server /etc/redis.conf
$ redis-cli -p"66803" -a"abcd123#"
> info replication

keepalived安装配置

由于Redis主从并不提供高可用容灾切换,因此我们可以通过keepalived去触发VIP切换,程序通过VIP连接感知较小

安装keepalived

yum install keepalived -y

配置keepalived(主)

$ cat /etc/keepalived/keepalived.conf
global_defs {
router_id redis01
}

vrrp_script chk_redis {
script "/etc/keepalived/redis_check.sh"
interval 3
timeout 5
}

vrrp_instance VI_1 {
state MASTER
interface ens192
virtual_router_id 51
priority 100
authentication {
auth_type PASS
auth_pass 1111
}

track_script {
chk_redis
}
virtual_ipaddress {
10.240.204.169
}

notify_master /etc/keepalived/redis_master.sh
notify_backup /etc/keepalived/redis_backup.sh
notify_fault /etc/keepalived/redis_fault.sh
notify_stop /etc/keepalived/redis_stop.sh
}
$ cat /etc/keepalived/redis_check.sh
#!/bin/bash

ALIVE=`/usr/local/redis/bin/redis-cli -p 66803 -a "Abcd123#" PING`
if [ "$ALIVE" == "PONG" ];then
echo $ALIVE
exit 0
else
echo $ALIVE
exit 1
fi
$ cat /etc/keepalived/redis_master.sh
#!/bin/bash
REDISCLI="/usr/local/redis/bin/redis-cli -p 66803 -a "Abcd123#""
LOGFILE="/var/log/keepalived-redis-state.log"
sleep 15
echo "[master]" >> $LOGFILE
date >> $LOGFILE
echo "Being master...." >>$LOGFILE 2>&1
echo "Run SLAVEOF cmd ...">> $LOGFILE
$REDISCLI SLAVEOF 10.240.204.160 66803 >>$LOGFILE 2>&1
if [ $? -ne 0 ];then
echo "data rsync fail." >>$LOGFILE 2>&1
else
echo "data rsync OK." >> $LOGFILE 2>&1
fi
sleep 10 #延迟10秒以后待数据同步完成后再取消同步状态
echo "Run SLAVEOF NO ONE cmd ...">> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1
if [ $? -ne 0 ];then
echo "Run SLAVEOF NO ONE cmd fail." >>$LOGFILE 2>&1
else
echo "Run SLAVEOF NO ONE cmd OK." >> $LOGFILE 2>&1
fi
$ cat /etc/keepalived/redis_backup.sh
#!/bin/bash
REDISCLI="/usr/local/redis/bin/redis-cli -p 6666 -a "Abcd123#""
LOGFILE="/var/log/keepalived-redis-state.log"
echo "[backup]" >> $LOGFILE
date >> $LOGFILE
echo "Being slave...." >>$LOGFILE 2>&1
sleep 15 #延迟15秒待数据被对方同步完成之后再切换主从角色
echo "Run SLAVEOF cmd ...">> $LOGFILE
$REDISCLI SLAVEOF 10.240.204.160 66803 >>$LOGFILE 2>&1
$ cat /etc/keepalived/redis_default.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[fault]" >> $LOGFILE
date >> $LOGFILE
$ cat /etc/keepalived/redis_stop.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[stop]" >> $LOGFILE
date >> $LOGFILE

配置keepalived(从)

$ cat /etc/keepalived/keepalived.conf 
global_defs {
router_id redis02
}

vrrp_script chk_redis {
script "/etc/keepalived/redis_check.sh"
interval 3
timeout 5
}

vrrp_instance VI_1 {
state BACKUP
interface ens192
virtual_router_id 51
priority 90
authentication {
auth_type PASS
auth_pass 1111
}

track_script {
chk_redis
}
virtual_ipaddress {
10.240.204.169
}

notify_master /etc/keepalived/redis_master.sh
notify_backup /etc/keepalived/redis_backup.sh
notify_fault /etc/keepalived/redis_fault.sh
notify_stop /etc/keepalived/redis_stop.sh
}
$ cat /etc/keepalived/redis_check.sh
#!/bin/bash

ALIVE=`/usr/local/redis/bin/redis-cli -p 66803 -a "Abcd123#" PING`
if [ "$ALIVE" == "PONG" ];then
echo $ALIVE
exit 0
else
echo $ALIVE
exit 1
fi
$ cat /etc/keepalived/redis_master.sh
#!/bin/bash
REDISCLI="/usr/local/redis/bin/redis-cli -p 66803 -a "Abcd123#""
LOGFILE="/var/log/keepalived-redis-state.log"
sleep 15
echo "[master]" >> $LOGFILE
date >> $LOGFILE
echo "Being master...." >>$LOGFILE 2>&1
echo "Run SLAVEOF cmd ...">> $LOGFILE
$REDISCLI SLAVEOF 10.240.204.157 66803 >>$LOGFILE 2>&1
if [ $? -ne 0 ];then
echo "data rsync fail." >>$LOGFILE 2>&1
else
echo "data rsync OK." >> $LOGFILE 2>&1
fi
sleep 10 #延迟10秒以后待数据同步完成后再取消同步状态
echo "Run SLAVEOF NO ONE cmd ...">> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1
if [ $? -ne 0 ];then
echo "Run SLAVEOF NO ONE cmd fail." >>$LOGFILE 2>&1
else
echo "Run SLAVEOF NO ONE cmd OK." >> $LOGFILE 2>&1
fi
$ cat /etc/keepalived/redis_backup.sh
#!/bin/bash
REDISCLI="/usr/local/redis/bin/redis-cli -p 6666 -a "Abcd123#""
LOGFILE="/var/log/keepalived-redis-state.log"
echo "[backup]" >> $LOGFILE
date >> $LOGFILE
echo "Being slave...." >>$LOGFILE 2>&1
sleep 15 #延迟15秒待数据被对方同步完成之后再切换主从角色
echo "Run SLAVEOF cmd ...">> $LOGFILE
$REDISCLI SLAVEOF 10.240.204.157 66803 >>$LOGFILE 2>&1
$ cat /etc/keepalived/redis_default.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[fault]" >> $LOGFILE
date >> $LOGFILE
$ cat /etc/keepalived/redis_stop.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[stop]" >> $LOGFILE
date >> $LOGFILE

启动keepalived

$ systemctl start keepalived

附录

禁用服务自启动

在Redis主从复制的情况下,如果主服务器关闭了持久化,那应该将数据库服务自启动关闭禁用,否则在主节点异常自动重启后数据将被全部清空,而slave因为master的运行ID变化开始做全量复制,那么从节点的数据也将被连带清空。

ROLE命令

Redis 2.8.12开始提供了一个新的查询复制信息的命令。

10.0.139.163:6379> ROLE
1) "master"
2) (integer) 19866
3) 1) 1) "10.0.139.162"
1) "6379"
2) "19866"

1)master为当前角色
2)当前主复制偏移量
3)一个包含三个元素的数组,表示slave的IP,端口和偏移量

10.0.139.162:6379> ROLE
1) "slave"
2) "10.0.139.163"
3) (integer) 6379
4) "connected"
5) (integer) 19866

1)slave为当前角色
2)master节点IP
3)master节点端口
4)connect表示实例需要连接到master服务器,connecting表示正在连接master服务器,sync表示正在与master进行同步,connected表示slave online
5)slave节点的偏移量

1) "sentinel"
2) 1) "resque-master"
2) "html-fragments-master"
3) "stats-master"
4) "metadata-master"

1)sentinel为当前角色
2)sentinel实例监控的主机名数组

键过期

当master存储大量设置了TTL的数据时,Redis内部需要定期清理过期数据,删除策略分为惰性删除和定时删除。

  • 惰性删除:主节点每次处理读命令时,都会检查键是否超时,如果超时则del删除对象,再把del命令异步发送给slave,slave不会主动删除过期键
  • 定时删除:Redis主节点再内部定时循环采样一定数量的键,当发现采样的键过期时执行del命令再同步到slave

当大量数据超时,主节点采样速度跟不上且没有读取过过期键,slave也就没法收到del命令,这时在从节点上就能读到已经过期的键。Redis在3.2中解决了该问题,从节点读取时会检查键的过期时间来判断是否返回数据。

N个副本才允许写入

从Redis 2.8开始,可以将Redis设置为仅在当前至少有N个副本连接到master才允许执行写入。如果至少有N个slave副本,并且延迟小于M秒,则接受写入命令

min-replicas-to-write <number of replicas>
min-replicas-max-lag <number of seconds>