Redis(十三):进阶篇 - 复制

复制

  通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同在服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此,Redis提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

1、配置

  在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库,如下图:
在这里插入图片描述

  在Redis中使用复制给你非常容易,只需要在从数据库的配置文件中加入 “slaveof 主数据库地址 主数据库端口” 即可,主数据库无需进行任何配置。
  下面展示一个简单的复制系统:
  在一台服务器上启动两个Redis实例,监听不同端口,其中一个作为主数据库,另一个作为从数据库。

  首先不加任何参数启动一个Redis实例作为主数据库:

[root@VM-0-17-centos ~]# redis-server 
10018:C 01 Jul 2021 17:23:27.000 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
10018:C 01 Jul 2021 17:23:27.000 # Redis version=6.2.4, bits=64, commit=00000000, modified=0, pid=10018, just started
10018:C 01 Jul 2021 17:23:27.000 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
10018:M 01 Jul 2021 17:23:27.001 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 10018
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

10018:M 01 Jul 2021 17:23:27.001 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
10018:M 01 Jul 2021 17:23:27.001 # Server initialized
10018:M 01 Jul 2021 17:23:27.002 # 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.
10018:M 01 Jul 2021 17:23:27.003 * Loading RDB produced by version 6.2.4
10018:M 01 Jul 2021 17:23:27.003 * RDB age 761342 seconds
10018:M 01 Jul 2021 17:23:27.003 * RDB memory usage when created 0.83 Mb
10018:M 01 Jul 2021 17:23:27.003 * DB loaded from disk: 0.001 seconds
10018:M 01 Jul 2021 17:23:27.003 * Ready to accept connections

该实例默认监听6379端口。

  然后加上 slaveof 参数启动另一个Redis实例作为从数据库,让其监听6380端口:

[root@VM-0-17-centos ~]# redis-server --port 6380 --slaveof 127.0.0.1 6379
10459:C 01 Jul 2021 17:26:05.662 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
10459:C 01 Jul 2021 17:26:05.662 # Redis version=6.2.4, bits=64, commit=00000000, modified=0, pid=10459, just started
10459:C 01 Jul 2021 17:26:05.662 # Configuration loaded
10459:S 01 Jul 2021 17:26:05.662 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6380
 |    `-._   `._    /     _.-'    |     PID: 10459
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

10459:S 01 Jul 2021 17:26:05.663 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
10459:S 01 Jul 2021 17:26:05.663 # Server initialized
10459:S 01 Jul 2021 17:26:05.663 # 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.
10459:S 01 Jul 2021 17:26:05.663 * Loading RDB produced by version 6.2.4
10459:S 01 Jul 2021 17:26:05.663 * RDB age 761500 seconds
10459:S 01 Jul 2021 17:26:05.663 * RDB memory usage when created 0.83 Mb
10459:S 01 Jul 2021 17:26:05.663 * DB loaded from disk: 0.000 seconds
10459:S 01 Jul 2021 17:26:05.663 * Ready to accept connections
10459:S 01 Jul 2021 17:26:05.665 * Connecting to MASTER 127.0.0.1:6379
10459:S 01 Jul 2021 17:26:05.665 * MASTER <-> REPLICA sync started
10459:S 01 Jul 2021 17:26:05.665 * Non blocking connect for SYNC fired the event.
10459:S 01 Jul 2021 17:26:05.665 * Master replied to PING, replication can continue...
10459:S 01 Jul 2021 17:26:05.666 * Partial resynchronization not possible (no cached master)
10459:S 01 Jul 2021 17:26:05.666 * Full resync from master: 1205a99f0f9176fc117e91d63d557c7fe20016ab:0
10459:S 01 Jul 2021 17:26:05.732 * MASTER <-> REPLICA sync: receiving 175 bytes from master to disk
10459:S 01 Jul 2021 17:26:05.732 * MASTER <-> REPLICA sync: Flushing old data
10459:S 01 Jul 2021 17:26:05.732 * MASTER <-> REPLICA sync: Loading DB in memory
10459:S 01 Jul 2021 17:26:05.738 * Loading RDB produced by version 6.2.4
10459:S 01 Jul 2021 17:26:05.739 * RDB age 0 seconds
10459:S 01 Jul 2021 17:26:05.739 * RDB memory usage when created 1.83 Mb
10459:S 01 Jul 2021 17:26:05.739 * MASTER <-> REPLICA sync: Finished with success

此时在主数据库中的任何数据变化都会自动地同步到从数据库中。

主数据库的控制台出现以下信息:

10018:M 01 Jul 2021 17:26:05.666 * Replica 127.0.0.1:6380 asks for synchronization
10018:M 01 Jul 2021 17:26:05.666 * Full resync requested by replica 127.0.0.1:6380
10018:M 01 Jul 2021 17:26:05.666 * Replication backlog created, my new replication IDs are '1205a99f0f9176fc117e91d63d557c7fe20016ab' and '0000000000000000000000000000000000000000'
10018:M 01 Jul 2021 17:26:05.666 * Starting BGSAVE for SYNC with target: disk
10018:M 01 Jul 2021 17:26:05.666 * Background saving started by pid 10464
10464:C 01 Jul 2021 17:26:05.673 * DB saved on disk
10464:C 01 Jul 2021 17:26:05.673 * RDB: 0 MB of memory used by copy-on-write
10018:M 01 Jul 2021 17:26:05.732 * Background saving terminated with success
10018:M 01 Jul 2021 17:26:05.732 * Synchronization with replica 127.0.0.1:6380 succeeded

  打开redis-cli实例A并连接到主数据库,并通过INFO命令获取replication信息:

[root@VM-0-17-centos ~]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=420,lag=1
master_failover_state:no-failover
master_replid:1205a99f0f9176fc117e91d63d557c7fe20016ab
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:420
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:420
127.0.0.1:6379> 

可以看到实例A的角色(上面输出中的role)是master,即主数据库,同时已连接的从数据库(上面输出中connected_slaves)的个数是1。

  再打开redis-cli实例B并连接到从数据库,并通过INFO命令获取replication信息:

[root@VM-0-17-centos ~]# redis-cli -p 6380
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_repl_offset:462
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:1205a99f0f9176fc117e91d63d557c7fe20016ab
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:462
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:462
127.0.0.1:6380> 

可以看到实例B的role是slave,即从数据库,同时其主数据库的地址为127.0.0.1,端口为6379。

  在实例A中使用set命令设置一个键的值:

127.0.0.1:6379> set key 111
OK

  然后再实例B中就可以获得该键的值了:

127.0.0.1:6380> get key
"111"

  默认情况下,从数据库是只读的,如果直接修改从数据库的数据会出现错误:

127.0.0.1:6380> set key 222
(error) READONLY You can't write against a read only replica.

  当然,可以通过设置从数据库的配置文件中的 slave-read-onlyno 以使从数据库可写。但是因为对从数据库的任何更改都不会同步给任何其他数据库,并且一旦主数据库中更新了对应的数据就会覆盖从数据库中的改动,所以通常的场景下不应该设置从数据库可写,一面导致易被忽略的潜在应用逻辑错误
  配置多台从数据库的方法也一样,在所有的从数据库的配置文件中都加上slaveof参数指向同一个主数据库即可。
  除了通过配置文件或命令行参数配置slaveof参数,也可以在运行时使用slaveof命令修改:

127.0.0.1:6381> slaveof 127.0.0.1 6379

如果该数据库已经是其他主数据库的从数据库了,slaveof命令会停止和原来数据库的同步转而和新数据库同步。此外对于从数据库来说,还可以使用 slaveof no one 命令来使当前数据库停止接收其他数据库的同步并转换成主数据库

2、原理

Reids实现复制的过程:
  当一个从数据库启动后,会向主数据库发送 SYNC 命令。同时主数据库接收到 SYNC 命令后开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到缓存的命令。以上过程称为复制初始化。复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数据库,从而保证主从数据库数据一致

  当主数据库之间的连接断开重连后Redis2.6以及之前的版本重新进行复制初始化(即主数据库重新保存快照并传送给从数据库),即使从数据库可以仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使得主从数据库断线重连后的数据恢复过程效率很低下,在网络环境不好的时候这一问题尤其明显。Redis2.8版的一个重要改进就是断线重连能够支持有条件的增量数据传输,当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从数据库,从而大大提高Redis的实用性。

  从具体协议角度详细介绍复制初始化的过程。由于Redis服务器使用TCP协议通信,所以我们可以使用telnet工具伪装成一个从数据库来与主数据库通信。首先在命令行中连接主数据库(默认端口为6379,假设目前没有任何从数据库连接):
由于服务器的redis连接具有保护,为了方便我在本地电脑启动了redis服务
在这里插入图片描述
在xshell中通过telnet命令连接本地的redis:

[c:\~]$ telnet 127.0.0.1 6379
Connecting to 127.0.0.1:6379...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.

  然后作为从数据库,先要发送PING命令确认主数据库是否可以连接:

PING
+PONG

在这里插入图片描述

  主数据库会回复 +PONG 。如果没有收到主数据库的回复,则向用户提示错误。如果主数据库需要密码才能连接,还要发送 AUTH 命令进行验证。而后向主数据库发送 replconf 命令说明自己的端口号(这里随便选择了一个):

replconf listening-port 6381
+OK

  这时就可以开始同步的过程了:向主数据发送 sync 命令开始同步,此时主数据库发送回快照文件和缓存的命令。目前数据库中set了一个key键:

sync
$87
REDIS0007	redis-ver3.2.100
                                redis-bits󿿀󲧥llctime¬used-mem~󂪥y󿿯6X¨ª1

  从数据库会将收到的内容写入到硬盘上的临时文件中,当写入完成后从数据库会用该临时文件替换RDB快照文件(RDB快照文件的位置就是持久化时配置的位置,由dir和dbfilename两个参数确定),之后的操作就和RDB持久化时启动恢复的过程一样了。需要注意的是在同步的过程中从数据库并不会阻塞,而是可以继续处理客户端发来的命令。默认情况下,从数据库会用同步前的数据对哪里进行响应。可以配置 slave-serve-stale-data 参数为 no 来使从数据库在同步完成前对所有命令(除了 INFO 和 SLAVEOF)都回复错误:“SYNC with master in progress.”
  复制初始化阶段结束后,主数据库指定的任何会导致数据变化的命令都会异步地传送给从数据库,这一过程为复制同步阶段。同步的内容和Redis通信协议一样,比如我们在主数据库中执行 set test hello,通过telnet我们收到了:

*2
$6
SELECT
$1
0
*3
$3
set
$4
test
$5
hello

  复制同步阶段会贯穿整个主从同步过程的始终。知道主从关系终止为止。
  在复制的过程中,快照无论在主数据库还是从数据库中都起了很大的作用,只要执行复制就会进行快照。即使我们关闭了RDB方式的持久化(通过删除所有save参数)。

乐观复制
Redis采用了乐观复制(optimistic replication)的复制策略,容忍在一定时间内主从数据库的内容是不同的,但是两者的数据会最终同步。具体来说,Redis在主从数据库之间复制数据的过程本身是异步的,这意味着,主数据库执行完客户端请求的命令后会立即将命令在主数据库的执行结果返回给客户端,并异步地将命令同步给从数据库,而不会等待从数据库接收到该命令后再返回给客户端。这一特性保证了启用复制后主数据库的性能不会受到影响,但另一方面也会产生一个主从数据库数据不一致的时间窗口,当主数据库执行了一条写命令后,主数据库的数据已经发生变动,然而在主数据库将该命令传送从数据库之前,如果两个数据库之间的网络连接断开了,此时二者之间的数据就会是不一致的。从这个角度看,主数据库是无法得知某个命令最终同步给了多少个从数据库的,不过Redis提供了两个配置选项来限制只有当数据至少同步给指定数量的从数据库时,主数据库才是可写的:
min-slaves-to-write 3
min-slaves-max-lag 10
上面的配置中,min-slaves-to-write表示只有当3个或3个以上的从数据库连接到主数据库时,主数据库才是可写的,否则会返回错误。
min-slaves-max-lag表示允许从数据库最长失去连接的时间,如果从数据库最后与主数据库联系的时间小于这个值,则认为从数据库还在保持与主数据库的连接。

3、图结构

  从数据库不仅可以接收主数据的同步数据,自己也可以同时作为主数据库存在,形成类似图的结构,如下图所示,数据库A的数据会同步到B和C中,而B中的数据会同步到D和E中。向B中写入数据不会同步到A和C中,只会同步到D和E中。
在这里插入图片描述

4、读写分离与一致性

  通过复制可以实现读写分离,以提高服务器的负载能力。在常见的场景中(如电子商务网站),读的频率大于写,当单机的Redis无法应对大量的读请求时(尤其是较耗资源的请求,如SORT命令等)可以通过复制功能建立多个从数据库节点,主数据库只进行写操作,而从数据库负责读操作。这种一主多从的结构很适合读多写少的场景,而当单个的主数据库不能够满足需求时,就需要使用Redis的集群功能。

5、从数据库持久化

  另一个相对耗时的操作是持久化,为了提高性能,可以通过复制功能建立一个(或若干个)从数据库,并在从数据库中启用持久化,同时在主数据库禁用持久化。当从数据库崩溃重启后主数据库会自动将数据同步过来,所以无需担心数据丢失。
  然而当主数据库崩溃时,情况就稍显复杂了。手工通过从数据库数据恢复主数据库数据时,需要严格按照以下两步进行。
  (1)在从数据库中使用 SLAVEOF NO ONE 命令将从数据库提升为主数据库继续服务;
  (2)启动之前崩溃的主数据库,然后使用 SLAVEOF 命令将其设置成新的主数据库的从数据库,即可将数据同步回来。

注意
当开启复制且主数据库关闭持久化功能时,一定不要使用Supercisor以及类似的进程管理工具令主数据库崩溃后自动重启。同样当主数据库所在的服务器因故关闭时,也要避免直接重新启动。这是因为当主数据库重新启动后,因为没有开启持久化功能,所以数据库中所有数据都被清空,这时从数据库依然会从主数据库中接收数据,使得所有从数据库也被清空,导致从数据库的持久化失去意义。

  无论哪种情况,手工维护从数据库或主数据库的重启以及数据恢复都相对麻烦,还在Redis提供了一种自动化方法哨兵实现这一过程,避免了手工维护的麻烦和容易出错的问题。

6、无硬盘复制

  从Redis2.8.18版本开始,Redis引入了无硬盘复制选项,开启该选项时,Redis在与从数据库进行复制初始化时将不会将快照内存存储到硬盘上,而是直接通过网络发送给从数据库,避免了硬盘的性能瓶颈。
  可以在配置文件中开启该功能:

repl-diskless-sync yes

7、增量复制

  Redis2,8之后版本实现了主从数据库断线重连的增量复制。

  增量复制是基于如下3点实现的:
  (1)从数据库会存储主数据库的运行ID(run id)。每个Redis运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID;
  (2)在复制同步阶段,主数据库将一个命令传送给从数据库时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中存放的命令的偏移量;
  (3)同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量。

  这3点是实现增量复制的基础。2.8版后不再发送sync命令,取而代之的是发送 psync ,格式为“psync 主数据库的运行ID 断开前最新的命令偏移量”。主数据库收到 psync命令后,会执行以下判断来决定此次重连是否可以执行增量复制。
  (1)首先主数据库会判断从数据库传送来的运行ID是否和自己的运行ID相同。这一步骤的意义在于确保从数据库之前确实是和自己同步的,以免从数据库拿到错误的数据(比如主数据库在断线期间重启过,会造成数据的不一致)。
  (2)然后判断从数据库最后同步成功的命令偏移量是都在积压队列中,如果在则可以执行增量复制,并将积压队列中相应的命令发送给从数据库。

  如果此次重连不满足增量复制条件,主数据库会进行一次全部同步。
   积压队列在本质上是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件中repl-backlog-size 选项来调整。积压队列越大,其允许的主从数据库断线的时间就越长。根据主从数据库之间的网络状态,设置一个合理的积压队列很重要。因为积压队列存储的内容是命令本身,如 set foo bar,所以估算积压队列的大小只需要估计主从数据库断线的时间中主数据库可能执行的命令的大小即可。
  与积压队列相关的另一个配置选项时 repl-backlog-ttl ,即当所有从数据库与主数据库断开连接后,经过多久时间可以释放积压队列的内存空间。默认时间是1小时。


author:su1573
鄙人记录生活点滴,学习并分享,请多指教!!!
如需交流,请联系 sph1573@163.com,鄙人看到会及时回复

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ssy03092919

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值