04-原理
8.1.2 原理
Redis采用了乐观复制(optimistic replication)的复制策略,容忍在一定时间内主从数据库的内容是不同的,但是两者的数据会最终同步。具体来说,Redis在主从数据库之间复制数据的过程本身是异步的,这意味着,主数据库执行完客户端请求的命令后会立即将命令在主数据库的执行结果返回给客户端,并异步地将命令同步给从数据库,而不会等待从数据库接收到该命令后再返回给客户端。这一特性保证了启用复制后主数据库的性能不会受到影响,但另一方面也会产生一个主从数据库数据不一致的时间窗口,当主数据库执行了一条写命令后,主数据库的数据已经发生的变动, 然而在主数据库将该命令传送给从数据库之前,如果两个数据库之间的网络连接断开了,此时二者之间的数据就会是不一致的。从这个角度来看,主数据库是无法得知某个命令最终同步给了多少个从数据库的,不过Redis提供了两个配置选项来限制只有当数据至少同步给指定数量的从数据库时,主数据库才是可写的: 上面的配置中,min-slaves-to-write表示只有当3个或3个以上的从数据库连接到主数据库时,主数据库才是可写的,否则会返回错误,例如: min-slaves-max-lag表示允许从数据库最长失去连接的时间,如果从数据库最后与主数据库联系(即发送REPLCONF ACK命令)的时间小于这个值,则认为从数据库还在保持与主数据库的连接。举个例子,按上面的配置,主数据库假设与3个从数据库相连,其中一个从数据库上一次与主数据库联系是9秒前,这时主数据库可以正常接受写入,一旦1秒过后这台从数据库依旧没有活动,则主数据库则认为目前连接的从数据库只有2个,从而拒绝写入。这一特性默认是关闭的,在分布式系统中,打开并合理配置该选项后可以降低主从架构中因为网络分区导致的数据不一致的问题。具体8.2节还会介绍。
了解Redis复制的原理对日后运维有很大的帮助,包括如何规划节点,如何处理节点故障等。下面将详细介绍Redis实现复制的过程。
当一个从数据库启动后,会向主数据库发送 SYNC 命令。同时主数据库接收到 SYNC 命令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到的缓存的命令。以上过程称为复制初始化。复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数据库,从而保证主从数据库数据一致。
min-slaves-to-write 3
min-slaves-max-lag 10
当主从数据库之间的连接断开重连后,Redis 2.6以及之前的版本会重新进行复制初始化(即主数据库重新保存快照并传送给从数据库),即使从数据库可以仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使得主从数据库断线重连后的数据恢复过程效率很低下,在网络环境不好的时候这一问题尤其明显。Redis 2.8版的一个重要改进就是断线重连能够支持有条件的增量数据传输,当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从数据库,从而大大提高Redis复制的实用性。8.1.7节会详细介绍增量复制的实现原理以及应用条件。
下面将从具体协议角度详细介绍复制初始化的过程。由于Redis服务器使用TCP协议通信,所以我们可以使用telnet工具伪装成一个从数据库来与主数据库通信。首先在命令行中连接主数据库(默认端口为6379,假设目前没有任何从数据库连接):
redis> SET foo bar
(error) NOREPLICAS Not enough good slaves to write.
$ telnet 127.0.0.1 6379 Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
然后作为从数据库,我们先要发送 PING 命令确认主数据库是否可以连接:
PING +PONG
主数据库会回复 +PONG 。如果没有收到主数据库的回复,则向用户提示错误。如果主数据库需要密码才能连接,我们还要发送 AUTH 命令进行验证(关于Redis的安全设置会在 9.1 节介绍)。而后向主数据库发送 REPLCONF 命令说明自己的端口号(这里随便选择了一个):
REPLCONFlistening-port 6381 +OK
这时就可以开始同步的过程了:向主数据库发送 SYNC 2命令开始同步,此时主数据库发送回快照文件和缓存的命令。目前主数据库中只有一个 foo 键,所以收到的内容如下(快照文件是二进制格式,从第三行开始):
2 Redis 2.8版之后从数据库会向主数据库发送PSYNC命令来代替SYNC以实现增量复制,具体请参考8.1.7节。
SYNC $29
REDIS0006?foobar?6_?"
从数据库会将收到的内容写入到硬盘上的临时文件中,当写入完成后从数据库会用该临时文件替换RDB快照文件(RDB 快照文件的位置就是持久化时配置的位置,由 dir 和 dbfilename 两个参数确定),之后的操作就和RDB持久化时启动恢复的过程一样了。需要注意的是在同步的过程中从数据库并不会阻塞,而是可以继续处理客户端发来的命令。默认情况下,从数据库会用同步前的数据对命令进行响应。可以配置 slave-serve-stale- data 参数为 no 来使从数据库在同步完成前对所有命令(除了 INFO 和 SLAVEOF )都回复错误:“ SYNC with master in progress. ”
复制初始化阶段结束后,主数据库执行的任何会导致数据变化的命令都会异步地传送给从数据库,这一过程为复制同步阶段。同步的内容和Redis通信协议(会在9.2节介绍)一样,比如我们在主数据库中执行 SET foo hi ,通过telnet我们收到了:
*3
$3
set
$3
foo
$2
hi
复制同步阶段会贯穿整个主从同步过程的始终,直到主从关系终止为止。
在复制的过程中,快照无论在主数据库还是从数据库中都起了很大的作用,只要执行复制就会进行快照,即使我们关闭了RDB方式的持久化(通过删除所有 save 参数)。Redis 2.8.18 之后支持了无硬盘复制,会在 8.1.6 节介绍。
乐观复制