复制
在Redis中,使用和配置主从复制功能非常简单,它允许Redis从服务器完整复制主服务器的数据。每次主从连接断开时,从机会自动重连主机,并且会尝试复制主机的数据,而不管主机发生了什么。
主从复制主要依赖以下3个机制:
- 当主从服务器连接良好时,主机会通过发送命令流来持续更新从机,以便将发生在主机上的数据变动复制到从机,这些数据变动包括:客户端写入、键过期或被驱逐等等。
- 当主从连接因为网络原因或由于断开时,从机会重连并尝试部分同步主机数据:也就是说从机会尝试只获取在连接断开期间它没有接收到的那部分命令流。
- 当部分同步不可行时,从机会请求全量重新同步。这就会涉及到一个更加复杂的过程,因为此时主机需要创建一份包含它所有数据的快照并且发送给从机,然后再发送后续发生数据变化的命令流。
Redis默认使用高延时和高性能的异步复制机制,在大部分Redis使用场景中,这是一种很自然的复制模式。然而这种复制模式下,Redis从机也只能异步地确认数据大小,这些数据是周期性从主机同步过来的。
客户端通过使用WAIT命令可以同步地复制某些数据。但是WAIT命令只有在其它实例中有指定数量的已确认副本的条件下才能使用。已确认的写入在故障转移的过程中仍然可能丢失,这是由于故障转移过程中的某些原因或是依赖Redis持久化的准确配置。你可以检查Sentinel或者Redis集群文档来获取更多有关高可用和故障转移的信息。接下来本文档将主要描述Redis基本复制模式的基本特征。
以下是一些关于Redis复制的重要事实:
- Redis使用异步复制,
可以异步确认从机从主机复制的数据量。 - 一个主机可以有多个从机。
- 从机可以接受来自其它从机的连接。除了将多个从机连接至一个主机外,从机也可以被其它从机连接,从而形成一个级联结构。从Redis4.0版本开始,所有的从机都可以接收来自主机同样的复制流。
- Redis复制在主机侧是非阻塞的。这意味着当一个或多个从机发起同步初始化或部分同步的时候,主机可以继续处理其它请求。
- 大部分情况下,复制在从机侧也是非阻塞的。当从机在执行初识同步的时候,它可以使用老版本的数据集继续处理查询请求,如果你在redis.conf文件中这么配置了的话。否则你可以配置Redis使从机返回一个错误给客户端。但是,在初识同步完成之后,必须删除老版本的数据并且加载新的数据。在这个剪短的窗口期,从机将会阻塞接入的连接(在数据量很大的情况下,这个时间窗可能会持续好几秒)。从4.0版本开始,我们可以配置Redis,使之可以启动一个新线程来删除旧数据,但是加载新的初始化数据的过程还是会在主线程中执行,并且会阻塞从机。
- 复制可用于可扩展性,以便为只读查询提供多个从机,或者仅仅只是为了数据安全。
- 可以通过复制来避免主机将全量数据写入磁盘带来的消耗:一种典型的技术是配置主机的redis.conf来避免将数据持久化到磁盘,然后连接从机,开启从机的AOF功能。然而进行这种设置必须非常小心,因为一旦主机重启,将会清空数据集,然后从机尝试从主机同步,这样从机的数据集也就会被清空。
当主机的持久化功能关闭时复制的安全性
在使用Redis复制的设置中,强烈建议开启主机和从机的持久化功能。当无法做到这一点时,比如说由于磁盘速度很慢导致延时问题,应该配置实例以避免重启后自动重新启动。
为了更好地理解为什么当主机的持久化功能关闭时,配置成自动重启是很危险的,请检查以下故障模式,在这些故障中,主机和所有从机的数据都被擦除了:
- 我们将节点A配置为主机,并且关闭了它的持久化功能,节点B和节点C从节点A复制数据。
- 节点A崩溃了,但是它有自动重启系统重启了进程。然而由于持久化功能没有打开,节点重启后将没有任何数据。
- 节点B和节点C从节点A复制数据,但是由于此时A没有任何数据,因此它们都会删除之前保存的数据副本。
当Redis哨兵用于高可用时,关闭主机持久化功能的同时开启自动重启功能是很危险的。比如主机可以快速启动以至于哨兵无法探测到故障,从而发生上述描述的故障。
任何时候数据安全都是很重要的,在使用复制切关闭了主机的持久化功能时,应该禁止实例自动启动。
Redis复制是如何运行的
每个Redis主机都有一个复制ID:它是一个很大的伪随机字符串,用来标记一个指定的数据集。每个主机还有一个偏移量,它随着主机每次生成要发送到从机的复制流的字节数递增,以便当数据集发生新的变动时更新从机的状态。即使实际上没有从机连接,这个复制的偏移量也会递增,所以基本上每次给定的一对数据:
Replication ID, offset
就唯一标识了主机数据集的准确版本。
当从机连接至主机时,它们使用PSYNC命令来发送它们旧的主机复制ID和到目前为止已处理的偏移量。这样一来主机就可以只发送增量部分的数据。然而如果主机缓存中没有足够的备份日志,或者从机指向的是一个不再已知历史记录(复制ID),那么就会发生完整的重新同步:在这种情况下,从机将会获得主机的完整副本。
这是更加完整的同步工作原理:
主机开启一个后台保存进程以便产生一个RDB文件。与此同时它还会将客户端新写入的命令缓存起来。当后台保存工作完成时,主机会把RDB文件传送给从机,从机会将它保存在磁盘上,然后加载入内存。随后主机会将所有缓存的命令发送给从机。这一过程是通过命令流的方式来完成的,并且使用了与Redis协议本身相同的格式。
你可以自己通过telnet来尝试一下。当Redis服务器在进行某项工作时连接至Redis端口并发出SYNC命令。你将可以看到批量传输,然后每一个主机接收到的命令都会被重新发送到telnet会话中。实际上SYNC是一个老的协议并且不再被新的Redis实例所使用,它仍然存在只是为了向后兼容:它不支持部分重新同步,所以现在用的是PSYNC。
如前所述,当主从连接因为某种原因断开时,从机可以自动重新连接。如果主机接收到了多个从机的并发同步请求,他将执行单个后台保存进程以便服务所有的从机。
无盘复制
通常情况下一个完整的重新同步过程需要在磁盘上创建一个RDB文件,然后从磁盘上加载这一份RDB文件以便为从机提供数据。
对于传输速率比较慢的磁盘,这对主机而言将会是一个压力很大的操作。Redis从2.8.18版本开始支持无盘复制。在这种设置下,子进程通过传输线直接将RDB文件发送给从站,而无需使用磁盘作为中间存储。
配置
配置基本的Redis复制是很简单的:只需要将下面这一行加到从机配置文件中即可:
slaveof 192.168.1.1 6379
当然你需要将192.168.1.1 6379替换成你自己主机的IP地址(或者主机名)和端口。另外你可以调用SLAVEOF命令,然后主机将会开始与从机同步。
还有一些参数用于调整主机在内存中的复制积压以便执行部分重新同步。更多信息请参考Redis发行版本中的redis.conf示例。
使用repl-diskless-sync配置参数可以开启无盘复制。当第一个从机到达后,为了等待更多从机到达的时间延迟是由参数repl-diskless-sync-delay控制的。更多信息请参考Redis发行版本中的redis.conf示例。
只读从机
从Redis2.6开始,从机开始支持一种只读模式并且默认开启。这种行为是通过redis.conf文件中的slave-read-only选项来控制的,并且它可以在运行期间通过CONFIG SET来开启或关闭。
只读从机会拒绝所有的写命令,因此由于误操作而向从机写入命令是不可能发生的。但这并不意味着这个特性旨在将从机实例暴露到互联网中或是其它存在不可信客户端的网络中,因为此时从机依然开启了DEBUG或是CONFIG这样的管理命令。然而,只读实例的安全性可以通过在redis.conf文件使用rename-command指令禁用命令得到提升。
你可能会奇怪为什么可以通过重置只读设置来让从机可以接收写入操作。如果主从实例重新同步或者从机重启了,这些写入操作仍然会被丢弃,但是还是有少数合法的场景可以在从机中存储一些临时数据。
例如,计算Set或Sorted set这些操作并把结果存入本地键中就是一种被多次观察到的可写入从机的使用场景。
然而需要注意的是,在4.0之前的版本中,可写入的从机并不支持带有存活时间的过期键。这意味着如果你使用EXPIRE或是其它的命令为一个键设置了最大存活时间,该键将会导致内存泄漏,当读命令方位时你并看不到它,但是在统计键数量时你会发现它依然占用着内存。所以通常来说同时使用可写入从机(4.0之前版本)和带有存活时间的键将会引发问题。
Redis 4.0 RC3及其之后的版本已经完全解决了这个问题,现在可写入的从机可以像主机一样删除带有存活时间的键,以DB编号大于63写入的键除外(但实际上默认的Redis实例只有16的数据库)。
同样需要注意的是从4.0版本开始的从机可写入的操作只是本地的,它并不会传播到连接至该实例的子层从机。子层从机将一直接收顶层主机发送到中间从机同样的复制流。例如在这样的设置中:
A ---> B ---> C
即使B是可写入的,C也不会看到B写入的信息,而是会拥有和主机A一样的数据集。
设置从机通过鉴权访问主机
如果主机通过参数requirepass设置了密码,那么很容易配置从机使用这个密码来执行所有同步操作。在一个运行中的实例中可以这样设置:
config set masterauth <password>
为了使设置永久生效,可以在你的配置文件中加上这个命令:
masterauth <password>
存在N个连接从机的时候才允许写入
从Redis2.8开始,我们可以配置一个Redis主机只有在至少存在N个已连接从机的时候才接收写请求。
然而,由于Redis使用异步复制机制,所以并不能确保从机接收到每一条写命令,因此总是会存在一个时间窗,在这个时间窗里会出现数据丢失的情况。
这个功能的工作原理是这样的:
- Redis从机每秒ping一次主机,确认已处理的复制流的数据量。
- Redis主机会记住每个从机最后一次ping的时间。
- 用户可以配置延迟不大于一个最大描述的最小从机的数量。
如果有最少N个从机其延迟小于M秒,那么写命令就会被接受。
你可以把它看成是一种尽力而为的数据安全机制,在这种机制下,数据的一致性并不能得到保证,但是起码这个数据丢失的时间窗内被限制在几秒钟之内。总体而言,In general bound data loss is better than unbound one.
如果条件没有被满足,主机将会返回一个错误并且写命令也不会被接受。
这个功能有两个配置参数:
- min-slaves-to-write
<number of slaves> - min-slaves-max-lag
<number of seconds>
请查看Redis发行版本中的redis.conf样例获取更多信息。
Redis复制怎么处理过期键
Redis的过期功能允许键存活指定的时间。这样一个功能依赖于实例的计时功能。Redis从机可以正确复制键的过期时间,即使键被Lua脚本修改过。
要实现这样一个功能并不能依赖于主机和从机的时间的时间同步能力,因为这个问题并不能得到解决并且会导致竞争条件和数据的不一致性,因此Redis使用3种主要的技术以便可以复制带过期时间的键:
- 从机并不使键过期,相反,他们等待主机来使键过期。当主机标记一个键过期(或者是由于LRU驱逐一个键)时,它会生成一个DEL命令,然后发送给所有的从机。
- 因为这种主机驱动的过期机制,有时候由于主机不能及时提供DEL命令,会导致逻辑上已经过期的键在从机的内存中依然存在。为了处理这种情况,从机会使用它的逻辑时钟来报告一个键已经不存在了,并且只用于不违反数据一致性的读操作(因为还会有来自主机的新命令到达)。通过这种方式从机可以避免报告逻辑上过期的键依然存在的现象发生。实际上,一个使用从机来进行扩展的HTML碎片缓存可以避免返回超过超时时间的数据条目。
- 在Lua脚本执行过程中不会有键被执行过期。在Lua脚本运行期间,概念上来说主机的时间是被凝固了的,所以一个给定的键在脚本运行期间要么一直存在,要么一直不存在。这个特性防止了键在脚本运行过程中过期,并且也可以用于在需要保证数据一致性时将同一份脚本发送给从机。
一旦从机被提升为主机,它将可以独立地使键过期,而不需要老的主机的任何帮助。
在Docker和NAT中配置复制
当使用了Docker、其它使用端口进行转发的容器或者是网络地址翻译的时候,Redis复制就有一些需要额外注意的地方,特别是使用Redis哨兵或者其它类型的系统的时候,这些系统的INFO或者ROLE命令的输出被扫描用来发现从机地址。
问题在于ROLE命令和INFO命令复制部分的输出,当发布到主机实例时,将会显示连接到这台主机的从机的IP地址,而这一点在使用了NAT的环境中可能会有所不同,在这类环境中,将会显示从机的逻辑地址。
与此类似,从机和监听端口警徽在redis.conf文件中配置,在使用端口进行了重新映射的环境中,这里就需要配置成转发端口。
为了修复这这两个问题,从Redis3.2.2开始,将强制从机将IP和端口进行配对。这两个配置的指令如下:
slave-announce-ip 5.5.5.5
slave-announce-port 1234
INFO和ROLE命令
有两个Redis命令提供了很多有关主从机的复制参数信息。其中一个是INFO。如果调用这个命令是使用了replication参数,例如INFO replication,那么将只有与复制相关的信息会被展示出来。另一个更加电脑友好的命令是ROLE,它提供了主从机的复制状态和它们的复制偏移量信息、以及从机列表等等。
重启和故障转移之后的部分重新同步
从Redis4.0开始,当一个从机由于故障转移之后被提升为主机时,它将会和旧主机的从机之间执行部分重新同步。为了做到这一点,从机记住了以前主机旧的复制ID和偏移量,因此它可以为连接的从机提供部分积压信息,即使他们请求了一个旧的复制ID。
然而这个被提升的从机它的新复制ID是不一样的,因为它的数据集的构成历史不同。例如,主机返回可用,并且可以继续接收写命令一段时间,因此在被提升的从机中继续使用相同的复制ID将会违反一对复制ID和便宜量只能标识一个数据集的规则。
此外,当从机慢慢断电并重启后,它们会将必要想信息存入RDB文件以便可以和主机执行重新同步。这一点在升级时时非常有用的。当需要是,最好使用SHUTDOWN命令以便可以在从机中执行save & quit命令。
2017-10-21
原文链接:https://redis.io/topics/replication#configuring-replication-in-docker-and-nat