Redis入门指南笔记--主从复制

转载:    http://blog.csdn.net/gqtcgq/article/details/50273431

    

现实项目中通常需要若干台Redis服务器的支持:

        结构上,单个 Redis 服务器会发生单点故障,而且一台服务器需要承受所有的请求负载。这就需要为数据生成多个副本并分配在不同的服务器上;

        容量上,单个 Redis 服务器的内存非常容易成为存储瓶颈,所以需要进行数据分片。

        同时拥有多个 Redis 服务器后就会面临如何管理集群的问题,包括如何增加节点、故障恢复等操作。

 

一:复制(replication)

        为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此,Redis 提供了“复制”功能,当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

 

1:配置

        在复制的概念中,数据库分为两类,一类是主库,另一类是从库。主库可以进行读写操作,当写操作导致数据变化时,会自动将数据同步给从库。从库一般是只读的,并接受主库同步过来的数据。

        一个主库可以拥有多个从库,而一个从库只能拥有一个主库,如下图所示:

        在 Redis 中使用复制功能非常容易, 只需要在从库的配置文件中加入:”slaveof  masterip masterport”即可,而主库无需进行任何配置。

 

        下面实现一个最简化的复制系统:在一台服务器上启动两个 Redis 实例,监听不同端口,其中一个作为主库,另一个为从库。

        首先不加任何参数来启动一个Redis实例作为主库,该实例默认监听6379端口。然后启动另一个Redis实例,加上”slaveof”参数作为从库,并让其监听6380端口:

[plain]  view plain  copy
  1. # redis-server --port 6380 --slaveof 127.0.0.1 6379  

        此时在主库中的任何数据变化都会自动地同步到从库中。打开终端A执行redis-cli连接到主库:

[plain]  view plain  copy
  1. root@localhost:~# redis-cli  
  2. 127.0.0.1:6379>   

        再打开终端B执行redis-cli连接到从库:

[plain]  view plain  copy
  1. root@localhost:~# redis-cli -p 6380   
  2. 127.0.0.1:6380>   

        使用”info”命令来分别在终端A和终端B中获取Replication节点的相关信息,下面是终端A得到的信息:

[plain]  view plain  copy
  1. 127.0.0.1:6379>info replication  
  2. # Replication  
  3. role:master  
  4. connected_slaves:1  
  5. slave0:ip=127.0.0.1,port=6380,state=online,offset=379,lag=1  
  6. ...  

        可见,得到的角色是master,即主库,同时已连接的从库的个数为1。下面是终端B得到的信息:

[plain]  view plain  copy
  1. 127.0.0.1:6380>info replication  
  2. # Replication  
  3. role:slave  
  4. master_host:127.0.0.1  
  5. master_port:6379  
  6. ...  

        可见,得到的角色是slave,即从库,同时其主库的IP地址为127.0.0.1,端口为 6379。

 

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

[plain]  view plain  copy
  1. 127.0.0.1:6379> set foo heheh  
  2. OK  

        此时在终端B中就可以获得该值了:

[plain]  view plain  copy
  1. 127.0.0.1:6380> get foo  
  2. "heheh"  

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

[plain]  view plain  copy
  1. 127.0.0.1:6380> set foo hi  
  2. (error) READONLY You can't write against a read only slave.  

        可以通过设置从库的配置文件中的“slave-read-only”为”no”,使从库可写,但是对从库的任何更改都不会同步给任何其他数据库,并且一旦主库中更新了对应的数据就会覆盖从库中的改动,所以通常的场景下,不应该设置从库可写。

 

        配置多台从库的方法也一样,在所有从库的配置文件中都加上”slaveof”参数指向同一个主库即可。

        除了通过配置文件或命令行参数设置”slaveof”参数,还可以在运行时使用”slaveof”命令修改:

[plain]  view plain  copy
  1. 127.0.0.1:6380>info replication  
  2. # Replication  
  3. role:master  
  4. connected_slaves:0  
  5. ...  
  6. 127.0.0.1:6380> slaveof 127.0.0.1 6379  
  7. OK  
  8. 127.0.0.1:6380>info replication  
  9. # Replication  
  10. role:slave  
  11. master_host:127.0.0.1  
  12. master_port:6379  
  13. ...  

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

 

2:原理

        当一个从库启动后,会向主库发送”sync”命令。主库收到该命令后,开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从库。从库收到后,会载入快照文件并执行收到的缓存命令。以上过程称为复制初始化。

        复制初始化结束后,主库每当收到写命令时就会将命令同步给从库,从而保证主从库数据一致。

 

        当主从库之间的连接断开重连后,Redis2.6及之前的版本会重新进行复制初始化(即主库重新保存快照并传送给从库),即使从库可能仅有几条命令没有收到,主库也必须要将数据库里的所有数据重新传送给从库。这使得主从库断线重连后的数据恢复过程效率很低下,在网络环境不好的时候这一问题尤其明显。

        Redis 2.8版的一个重要改进就是断线重连后,支持有条件的增量数据传输,当从库重新连接上主库后,主库只需要将断线期间执行的命令传送给从库,从而大大提高Redis复制的实用性。后续会详细介绍增量复制的实现原理以及应用条件。

 

        下面从具体协议角度,详细介绍复制初始化的过程。Redis服务器使用 TCP协议通信,可以使用 telnet 工具伪装成一个从库来与主库通信。首先在命令行中连接主库:

[plain]  view plain  copy
  1. root@localhost:~# telnet 127.0.0.1 6379  
  2. Trying 127.0.0.1...  
  3. Connected to 127.0.0.1.  
  4. Escape character is '^]'.  

        作为从库,先要发送”ping”命令确认主库是否可以连接:

[plain]  view plain  copy
  1. ping  
  2. +PONG  

        而后向主库发送”replconf”命令说明自己的端口号:

[plain]  view plain  copy
  1. replconf listening-port 6381  
  2. +OK  

        这时就可以开始同步的过程了,向主库发送”sync”命令开始同步,此时主库发送回快照文件和缓存的命令。目前主库中只有一个foo键,所以收到的内容如下(快照文件是二进制格式,从第三行开始):

[plain]  view plain  copy
  1. sync  
  2. $29   
  3. REDIS0006?foobar?6_?"  

        从库将收到的内容写入到硬盘上的临时文件中,当写入完成后从库会用该临时文件替换RDB快照文件,之后的操作就和RDB持久化时,启动恢复的过程一样了。在同步的过程中从库并不会阻塞,而是可以继续处理客户端发来的命令。

 

        默认情况下,从库会用同步前的数据对命令进行响应。可以配置”slaveserve-stale-data”参数为”no”来使从库在同步完成前对所有命令(除了info和slaveof)都回复错误:

”SYNC  with  master in progress.“

 

        复制初始化阶段结束后,主库执行的任何导致数据变化的命令都会异步地传送给从库,这一过程为“复制同步阶段”。同步的内容和Redis通信协议一样。复制同步阶段会贯穿整个主从同步过程的始终,直到主从关系终止为止。

 

        在复制的过程中,快照无论在主库还是从库中都起了很大的作用,只要执行复制就会进行快照,即使关闭了RDB方式的持久化(通过删除所有save参数)。Redis 2.8.18 之后支持了无硬盘复制,会在下面介绍。

 

        Redis采用了乐观复制(optimistic replication)的复制策略,容忍在一定时间内主从库的内容是不同的,但是两者的数据会最终同步。具体来说,Redis在主从库之间复制数据的过程本身是异步的,这意味着,主库执行完客户端请求的命令后会立即将命令在主库的执行结果返回给客户端,并异步地将命令同步给从库,而不会等待从库接收到该命令后再返回给客户端。

        这一特性保证了启用复制后主库的性能不会受到影响,但另一方面也会产生一个主从库数据不一致的时间窗口,当主库执行了一条写命令后,主库的数据已经发生的变动,然而在主库将该命令传送给从库之前,如果两个数据库之间的网络连接断开了,此时二者之间的数据就会是不一致的。

        从这个角度来看,主库是无法得知某个命令最终同步给了多少个从库的,不过 Redis 提供了两个配置选项,来限制只有当数据至少同步给指定数量的从库时,主库才是可写的:

[plain]  view plain  copy
  1. min-slaves-to-write 3  
  2. min-slaves-max-lag 10  

        “min-slaves-to-write”表示只有当3个(或以上)的从库连接到主库时,主库才是可写的,否则会返回错误:

“NOREPLICAS  Not  enough  good  slaves to  write.”

        “min-slaves-max-lag”表示允许从库最长失去连接的时间,如果从库最后与主库联系(即发送“replconf  ack”命令)的时间小于这个值,则认为从库还在保持与主库的连接。

        举个例子,按上面的配置,假设主库与3个从库相连,其中一个从库上一次与主库联系是 9 秒前,这时主库可以正常接受写入,一旦1秒过后这台从库依旧没有活动,则主库则认为目前连接的从库只有2个,从而拒绝写入。这一特性默认是关闭的,在分布式系统中,打开并合理配置该选项后可以降低主从架构中因为网络分区导致的数据不一致的问题。

 

        从库不仅可以接收主库的同步数据,自己也可以同时作为主库存在,形成类似下图的结构:

        数据库A的数据会同步到B和C中, 而B中的数据会同步到D和E中。向B中写入数据不会同步到A或C中,只会同步到 D和E中。

 

3:读写分离与一致性

        通过复制可以实现读写分离,以提高服务器的负载能力。在常见的场景中(如电子商务网站),读的频率大于写,当单机的Redis无法应付大量的读请求时(尤其是较耗资源的请求,如sort命令等),可以通过复制功能建立多个从库节点,主库只进行写操作,而从库负责读操作。

        这种一主多从的结构很适合读多写少的场景,而当单个主库不能够满足需求时,就需要使用Redis 3.0 推出的集群功能,后续会详细介绍。

 

4:从库持久化

        另一个相对耗时的操作是持久化,为了提高性能,可以通过复制功能建立一个(或若干个)从库,并在从库中启用持久化,同时在主库禁用持久化。当从库崩溃重启后,主库会自动将数据同步过来,所以无需担心数据丢失。然而当主库崩溃时,情况就稍显复杂了。

        手工通过从库数据恢复主库数据时,需要严格按照以下两步进行:

        a:在从库中使用 “slaveof  no  one”命令将从库提升成主库继续服务;

        b:启动之前崩溃的主库,然后使用”slaveof”命令将其设置成新主库的从库,即可将数据同步回来。

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

        无论哪种情况,手工维护从库或主库的重启以及数据恢复都相对麻烦,好在Redis提供了一种自动化方案:“哨兵”来实现这一过程,避免了手工维护的麻烦和容易出错的问题,后续会详细介绍“哨兵”。

 

5:无硬盘复制

        介绍Redis复制的工作原理时,介绍了复制是基于RDB方式的持久化实现的,即主库端在后台保存 RDB 快照,从库端则接收并载入快照文件。这样的实现优点是可以显著地简化逻辑,复用已有的代码,但是缺点也很明显:

        a:当主库禁用RDB快照时(即删除了所有的配置文件中的save语句),如果执行了复制初始化操作,Redis依然会生成RDB快照,所以下次启动后主库会以该快照恢复数据。因为复制发生的时间不能确定,这使得恢复的数据可能是任何时间点的。

        b:因为复制初始化时需要在硬盘中创建RDB快照文件,所以如果硬盘性能很慢,则这一过程会对性能产生影响。

 

        因此从2.8.18版本开始,Redis引入了“无硬盘复制”选项,开启该选项时,Redis 在与从库进行复制初始化时将不会将快照内容存储到硬盘上,而是直接通过网络发送给从库,避免了硬盘的性能瓶颈。

        目前无硬盘复制的功能还在试验阶段,可以在配置文件中使用如下配置来开启该功能:

[plain]  view plain  copy
  1. repl-diskless-sync yes  

6:增量复制

        在介绍复制原理时提到,当主从库连接断开后,从库会发送sync命令来重新进行一次完整复制操作。这样即使断开期间数据库的变化很小(甚至没有),也需要将数据库中的所有数据重新快照并传送一次。这种实现方式显然不太理想。

        Redis 2.8版相对2.6版的最重要的更新之一,就是实现了主从断线重连情况下的增量复制功能。增量复制是基于如下3点实现的:

        a:从库会保存主库的运行ID。每个Redis 运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID。

        b:在复制同步阶段,主库每将一个命令传送给从库时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中,存放的命令的偏移量范围。

        c:同时,从库接收到主库传来的命令时,会记录下该命令的偏移量。

 

        注意,主库和所有从库都记录了命令的偏移量。以上3点是实现增量复制的基础。当主从连接准备就绪后,从库会发送一条sync命令来告诉主库可以开始把所有数据同步过来了。而2.8版之后,不再发送sync命令,取而代之的是发送psync,格式为“psync  主库的运行ID  断开前最新的命令偏移量”。

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

        a:首先主库会判断从库传送来的运行ID是否和自己的运行ID相同。这一步骤的意义在于确保从库之前确实是和自己同步的,以免从库拿到错误的数据(比如主库在断线期间重启过,会造成数据的不一致);

        b:然后判断从库最后同步成功的命令偏移量是否在积压队列中,如果在,则可以执行增量复制,并将积压队列中相应的命令发送给从库。如果此次重连不满足增量复制的条件,主库会进行一次全部同步(即与Redis 2.6的过程相同)。

        大部分情况下,增量复制的过程对开发者来说是完全透明的,开发者不需要关心增量复制的具体细节。2.8 版本的主库也可以正常地和旧版本的从库同步(通过接收sync命令),同样 2.8 版本的从库也可以与旧版本的主库同步(通过发送sync命令)。

 

        唯一需要开发者设置的就是积压队列的大小了。积压队列本质上是一个固定长度的循环队列,默认情况下积压队列的大小为 1 MB,可以通过配置文件的” repl-backlog-size”选项来调整。积压队列越大,其允许的主从库断线的时间就越长。

        根据主从库之间的网络状态,设置一个合理的积压队列很重要。因为积压队列存储的内容是命令本身,如”SET  foo  bar”,所以估算积压队列的大小只需要估计主从库断线的时间中,主库可能执行的命令的大小即可。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值