Redis主从复制

276 阅读21分钟

痛点

Redis数据持久化文章介绍了,Redis是如何实现将数据从内存备份到硬盘中,保证了数据不会因为机器故障而丢失了内存中的数据。但如果发生了天灾人祸,整台机器包括硬盘都坏掉了,数据持久化就也没用了。Redis推出了主从复制来解决这个问题。

概念

复制主要实现了数据的多机备份还有对于读操作的负载均衡和简单的故障恢复。另外,后续介绍的哨兵模式集群都是在复制的基础上实现的。所以,可以说复制是Redis高可用的基础。

原理

下面的表格解释了,复制的启动过程中,主服务器和从服务器都各自执行了哪些操作。

步骤 主服务器操作 从服务器操作
1 等待命令进入 连接(或者重连接)服务器,发送SYNC命令
2 开始执行BGSAVE命令,并使用缓存区记录BGSAVE之后执行的所有命令 根据配置项来决定是继续使用现有数据来处理客户端的命令请求,还是向发送请求的客户端返回错误
3 BGSAVE执行完毕,向从服务器发送快照文件,并在发送期间继续使用缓冲区被执行的命令 丢弃所有旧的数据(如果有的话),开始载入主服务器发来的快照文件
4 快照文件发送完毕,开始向从服务器发送存储在缓冲区里面的写命令 完成对快照文件的解释操作,开始接受命令的请求
5 缓冲区存储的写命令发送完毕,从现在开始,每执行一个写命令,就向从服务器发送相同的写命令 执行主服务器发来的所有存储在缓冲区里面的写命令,并从现在开始,接收并执行主服务器传来的每个写命令

表1 从服务器连接主服器的步骤

上面是一主一从的复制过程。一主多从,情况就需要分开讨论。但总体的思路是尽可能减少复制所需要的工作

当有新的从服务器连接主服务器时 主服务器的操作
表1 的步骤3尚未执行 所有的从服务器都会接收到相同的快照文件和相同的缓存区执行命令
表1步骤3正在执行或者执行完毕 当主服务器与较早进行连接的从服务器执行完复制所需的5个步骤之后,主服务器会与新连接的从服务器执行一次新的步骤1-5

参考自书籍Redis实战 4.2.2Redis 复制的启动过程 p70-p71

主从复制集群搭建

安装单机版的Redis网上有很多教程,这里就贴一篇网上找的教程redis安装和配置。参照这个进行安装Redis.

完成单机的安装配置,现在进行一主一从安装。

先对主服务器的配置文件进行改名

mv ./redis.conf ./redis_6379.conf

然后对配置文件进行以下更改:

# daemonize是守护进程的意思,默认配置是no,建议修改为yes
daemonize yes

# 当daemonize 为yes时生效
# 将pid写入配置文件中
pidfile /var/run/redis-6379.pid

# 配置redis日志目录
logfile /var/log/redis/redis-6379.log

# 配置redis运行端口
port 6379

# Redis默认只允许 本机访问,把 bind 修改为 bind 0.0.0.0 此设置会变成 允许所有远程访问。
bind 0.0.0.0

# 客户端和Redis服务端的连接超时时间,默认是0
timeout 300

# Redis持久化RDB模式下,备份文件名, 默认为dum.rdb
dbfilename dump-6379.rdb

# Redis持久化文件存放位置
dir ./redis-workdir

# 配置redis服务端密码
requirepass 123456

接下来,复制多一份从服务器的配置文件

cp  ./redis_6379.conf  ./redis_16379.conf

对从服务器的配置文件redis_16379.conf进行如下更改

daemonize yes
pidfile /var/run/redis-16379.pid
logfile /var/log/redis/redis-16379.log
port 16379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-16379.rdb
dir ./redis-workdir
requirepass 123456

# slaveof <masterip> <masterport>
# 该命令就表示当前配置的服务器是从服务器,并且指定了所复制主服务器的ip和端口
slaveof 127.0.0.1 16379

# 如果主服务器开启了密码
# 配置连接主服务器的密码
masterauth 123456

创建log目录和redis-workdi目录

mkdir ./log
mkdir ./redis-workdir

接下来启动主从服务器:

# 启动主服务器
redis-server /usr/local/redis-4.0.11/redis_6379.conf
# 启动从服务器
redis-server /usr/local/redis-4.0.11/redis_16379.conf

执行netstat -nltp命令 查看是否启动成功

可以看到6379和16379端口已经被redis进程占用, 说明一主一从已经启动成功

[root@localhost script]# netstat -nltp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      7629/redis-server 0 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      7078/sshd           
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      7362/master         
tcp        0      0 0.0.0.0:16379           0.0.0.0:*               LISTEN      7634/redis-server 0 
tcp6       0      0 :::22                   :::*                    LISTEN      7078/sshd           
tcp6       0      0 ::1:25                  :::*                    LISTEN      7362/master   

验证

接下来是验证环节,主要验证彼此的角色是否正确,验证从服务器是否能复制主服务器中的内容,验证从服务器能否写入内容,最后需要验证主从复制启动时的原理过程

验证角色

打开两个命令行终端,分别执行以下命令:

redis连接主服务器客户端
redis-cli -h 127.0.0.1 -p 6379 -a 123456

redis连接从服务器客户端
redis-cli -h 127.0.0.1 -p 16379 -a 123456

连接成功后,分别执行info Replication命令, 查看主从状态

127.0.0.1:6379> info Replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=16379,state=online,offset=56,lag=0
master_replid:dff39523df538b97f17a5b6508664ec41c8ae8a1
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

主服务器状态

127.0.0.1:16379info Replication
Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:224
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:dff39523df538b97f17a5b6508664ec41c8ae8a1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:224
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:224

从服务器状态

从上面的主从状态中role字段标识出了6379作为主服务器,16379是作为一台从服务。符合我们的配置。

验证主服务器写入

接下来,验证在主服务器写入信息,从服务器能否读取到。

在主服务器中执行写入命令:

127.0.0.1:6379> set key test_master_write_slave_get
OK
127.0.0.1:6379> get key
"test_master_write_slave_get"

从服务器执行命令 get key命令,能成功从从服务器中读取到主服器写入的的内容

127.0.0.1:6379get key
"test_master_write_slave_get"

可以看到,复制功能最最重要的功能确实是可以的。

验证从服务器写入

可以不可以从服务器写入命令,这当然不可以。只有主服器才能写入命令,不然就谁是主谁是从就乱套了。

从服务器执行写入命令

127.0.0.1:16379set key test_master_write_slave_get
(errorREADONLY You can't write against a read only slave.

从报错信息可以看出,从服务器只有读的功能。

验证主从启动原理过程

查看主从服务器启动日志

然后到log文件夹中,查看主服务器和从服务器的启动日志:

先查看主服务器的日志:

7615:C 15 Jul 06:44:46.773 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7615:C 15 Jul 06:44:46.774 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=7615, just started
7615:C 15 Jul 06:44:46.774 # Configuration loaded
7616:M 15 Jul 06:44:46.779 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 4.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 7616
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

7616:M 15 Jul 06:44:46.785 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7616:M 15 Jul 06:44:46.786 # Server initialized
7616:M 15 Jul 06:44:46.786 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7616:M 15 Jul 06:44:46.787 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
7616:M 15 Jul 06:44:46.787 * DB loaded from disk: 0.001 seconds
7616:M 15 Jul 06:44:46.788 * Ready to accept connections

# 收到来自16379的同步请求
7616:M 15 Jul 06:44:55.597 * Slave 127.0.0.1:16379 asks for synchronization
7616:M 15 Jul 06:44:55.597 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for 'dff39523df538b97f17a5b6508664ec41c8ae8a1', my replication IDs are '61bb04d45bb35642ba62699a6204e79335e8f755' and '0000000000000000000000000000000000000000')

执行BGSAVE命令生成RDB文件
7616:M 15 Jul 06:44:55.597 * Starting BGSAVE for SYNC with target: disk
7616:M 15 Jul 06:44:55.597 * Background saving started by pid 7625
7625:C 15 Jul 06:44:55.602 * DB saved on disk
7625:C 15 Jul 06:44:55.603 * RDB: 0 MB of memory used by copy-on-write
7616:M 15 Jul 06:44:55.666 * Background saving terminated with success

# 同步成功
7616:M 15 Jul 06:44:55.667 * Synchronization with slave 127.0.0.1:16379 succeeded

主服务器日志

7620:C 15 Jul 06:44:55.585 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7620:C 15 Jul 06:44:55.586 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=7620, just started
7620:C 15 Jul 06:44:55.586 # Configuration loaded
7621:S 15 Jul 06:44:55.589 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 4.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 16379
 |    `-._   `._    /     _.-'    |     PID: 7621
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

7621:S 15 Jul 06:44:55.591 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7621:S 15 Jul 06:44:55.591 # Server initialized
7621:S 15 Jul 06:44:55.591 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7621:S 15 Jul 06:44:55.592 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
7621:S 15 Jul 06:44:55.594 * DB loaded from disk: 0.002 seconds
7621:S 15 Jul 06:44:55.594 * Before turning into a slave, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.
7621:S 15 Jul 06:44:55.594 * Ready to accept connections

# 连接6379主服器进行同步数据    
7621:S 15 Jul 06:44:55.594 * Connecting to MASTER 127.0.0.1:6379
7621:S 15 Jul 06:44:55.595 * MASTER <-> SLAVE sync started
7621:S 15 Jul 06:44:55.595 * Non blocking connect for SYNC fired the event.

# 主服器回应了请求
7621:S 15 Jul 06:44:55.596 * Master replied to PING, replication can continue...
7621:S 15 Jul 06:44:55.597 * Trying a partial resynchronization (request dff39523df538b97f17a5b6508664ec41c8ae8a1:1).

# 从主服务接收到了RDB文件   
7621:S 15 Jul 06:44:55.598 * Full resync from master: 86ae804de984ca31de69ca807170156cb19cebf6:0
7621:S 15 Jul 06:44:55.598 * Discarding previously cached master state.
7621:S 15 Jul 06:44:55.667 * MASTER <-> SLAVE sync: receiving 176 bytes from master

# 清空了自己原本的旧数据,并且同步成功   
7621:S 15 Jul 06:44:55.667 * MASTER <-> SLAVE sync: Flushing old data
7621:S 15 Jul 06:44:55.667 * MASTER <-> SLAVE sync: Loading DB in memory
7621:S 15 Jul 06:44:55.667 * MASTER <-> SLAVE sync: Finished with success
7621:S 15 Jul 06:59:56.093 * 1 changes in 900 seconds. Saving...
7621:S 15 Jul 06:59:56.094 * Background saving started by pid 7705
7705:C 15 Jul 06:59:56.107 * DB saved on disk
7705:C 15 Jul 06:59:56.108 * RDB: 0 MB of memory used by copy-on-write
7621:S 15 Jul 06:59:56.194 * Background saving terminated with success

从服务器日志

从主从服务器的日志可以粗略地看出,复制的启动过程与上面提到的原理大体是一致的

遗留的问题

Redis主从复制模式已经实现了数据的多机备份,以及对于读操作的负载均衡。 但是有个主从复制有个致命的缺陷,一旦主节点由于故障不能继续提供服务,需要手动选定一个从节点晋升为主节点,同时还要更改其他从节点的配置指向新的主节点。有没有自动地更改主节点的方法呢,还真有,Redis 2.8正式推出了哨兵模式实现了自动故障恢复。

如果想详细地了解哨兵模式,可以参考一下Redis 哨兵模式这篇文章。

掘金用户零壹技术栈的文章--深入剖析Redis系列(一) - Redis入门简介与主从搭建