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命令查看复制状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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命令格式为
全量复制
全量复制是Redis最早开始就支持的复制方式,主要通过psync命令实现。全量复制的工作流程如下:
- 发送psync命令进行数据同步,由于是第一次进行复制,会直接发送psync ? -1
- 主节点根据psync ? -1判断为全量复制,回复+FULLRESYNC
- slave接收到master的运行ID和偏移量
- 主节点执行bgsave生成RDB文件到本地
- 主节点发送RDB持久化文件给slave,需要注意的是如果RDB文件比较大,传输将会比较耗时,并且时间超过repl-timeout(默认60秒),从节点会放弃接收RDB并清空已下载的文件,导致复制失败
- 主节点仍然响应读写,并把期间产生的数据命令保存在复制客户端缓冲区,待全量复制完成后再批量发送给slave。期间,如果全量复制时间太长,容易造成复制客户端缓冲区内存溢出。参数默认配置为client-output-buffer-limit slave 256MB 64MB 60,其表示为60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点自动关闭slave连接,导致复制失败。
- slave在接收完成后会后台清空自身数据
- slave加载master传过来的RDB文件
- 加载完成后,如果slave开启了AOF持久化,会立即做bgrewriteaof操作
全量复制花费的时间=master bgsave的时间+RDB文件传输时间+slave清空并加载RDB文件时间
Tips:Redis 2.8.18开始支持无盘复制,能够通过子进程直接网络发送RDB文件,适用于磁盘IO性能较差的环境,由参数repl-diskless-sync控制
部分复制
部分复制是对全量复制做出的优化,当网络闪断等异常情况时,slave会向master重新同步丢失的命令数据,如果master上的复制积压区还存在这部分数据则会直接发送给slave。从而避免了不必要的全量复制,减小复制开销。其工作流程如下:
- slave通过保存的master运行ID和自身复制偏移量运行psync命令
- master验证运行ID并根据offset在复制积压缓冲区查找,如果存在则发送+CONTINUE给slave,表示进行部分复制。
- master把数据发送给slave,保证slave进入正常复制状态。
心跳
主从复制会建立长连接并批次发送心跳检测命令,各自模拟成客户端与对方通信,主节点默认10秒发送一次PING命令,判断slave的是否存活可连接;slave每1秒发送replconf ack {offset}命令,上报自身偏移量给master。
Redis主从安装配置
解压安装包
1
|
$ tar -xvf redis-4.0.9.tar.gz
|
编译安装
1
2
3
|
$ cd redis-4.0.9
$ make && cd src
$ make install PREFIX=/usr/local/redis -j 4
|
创建文件夹
1
2
|
$ 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 <master_ip> <master_port> | 自定义 | 当前为从库时,设置同步主库信息
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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参数
配置环境变量
1
2
|
$ echo "export PATH=$PATH:/usr/local/redis/bin" >> /etc/profile
$ source /etc/profile
|
启动数据库并查看主从同步
1
2
3
|
$ redis-server /etc/redis.conf
$ redis-cli -p"66803" -a"abcd123#"
> info replication
|
keepalived安装配置
由于Redis主从并不提供高可用容灾切换,因此我们可以通过keepalived去触发VIP切换,程序通过VIP连接感知较小
安装keepalived
1
|
yum install keepalived -y
|
配置keepalived(主)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
$ 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
}
|
1
2
3
4
5
6
7
8
9
10
11
|
$ 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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ 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
|
1
2
3
4
5
6
7
8
9
10
|
$ 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
|
1
2
3
4
5
|
$ cat /etc/keepalived/redis_default.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[fault]" >> $LOGFILE
date >> $LOGFILE
|
1
2
3
4
5
|
$ cat /etc/keepalived/redis_stop.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[stop]" >> $LOGFILE
date >> $LOGFILE
|
配置keepalived(从)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
$ 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
}
|
1
2
3
4
5
6
7
8
9
10
11
|
$ 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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ 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
|
1
2
3
4
5
6
7
8
9
10
|
$ 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
|
1
2
3
4
5
|
$ cat /etc/keepalived/redis_default.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[fault]" >> $LOGFILE
date >> $LOGFILE
|
1
2
3
4
5
|
$ cat /etc/keepalived/redis_stop.sh
#!/bin/bash
LOGFILE=/var/log/keepalived-redis-state.log
echo "[stop]" >> $LOGFILE
date >> $LOGFILE
|
启动keepalived
1
|
$ systemctl start keepalived
|
附录
禁用服务自启动
在Redis主从复制的情况下,如果主服务器关闭了持久化,那应该将数据库服务自启动关闭禁用,否则在主节点异常自动重启后数据将被全部清空,而slave因为master的运行ID变化开始做全量复制,那么从节点的数据也将被连带清空。
ROLE命令
Redis 2.8.12开始提供了一个新的查询复制信息的命令。
1
2
3
4
5
6
|
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,端口和偏移量
1
2
3
4
5
6
|
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
2
3
4
5
|
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秒,则接受写入命令
1
2
|
min-replicas-to-write <number of replicas>
min-replicas-max-lag <number of seconds>
|