Redis深入系列-0x017:Redis同步

概述

基础的Redis同步使用非常简单,配置主从同步可以让从节点完全复制节点。不管主节点发生任何事,从节点会在链接断开之后自动重连。

这个机制工作使用了三台机子:

  • 当主节点和从节点连接的很好的时候,主节点通过发送主节点上接收并执行的命令流到从节点,从而保证从节点更新:客户端写、key过期或者回收等。
  • 当主从节点之间的连接断开,比如网络问题或者主从节点觉得超时,从节点将重新链接并且尝试部分重新同步:这意味着他将重新同步在断开链接之后流中的命令。
  • 当部分重新同步失败以后,从节点将会请求一个完全同步,这降火执行一个更加复杂的操作,主节点需要创建一个当前数据的镜像,发送给从节点,并且在数据集发生改变的时候继续发送命令流。

Redis默认使用高性能高延迟的异步复制,这是大部分Redis使用场景的自然复制模式。从节点定期异步的从主节点获取大量的数据。

可以在客户端使用WAIT命令来请求同步复制确认的数据。然而WATI命令只能确保指定数量的数据被复制到其他实例:数据写入依旧会在故障转移或者因故障转移的导致的原因中丢失,取决于Redis持久化配置。你可以查看Sentinel或者Redis Cluster文档查看更多关于高可用和故障转移的信息。接下来的文档主要概括Redis简单的数据同步的一些基本的特征。

接下来是一些关于Redis同步很重要的事实:

  • Redis使用异步复制,从节点异步的从主节点获取大量的数据。
  • 一个主节点可以有多个从节点
  • 从节点可以接受其他从节点的连接,在连接很多从节点到同一个主节点之外,从节点也可以连接到其他从节点,类似级联的方式。从Redis 4.0以后,所有的子从节点都将从主节点收到相同的复制流。
  • Redis同步在主节点是非堵塞的。这意味着主节点可以继续处理请求,当一个或者多个从节点做初始化数据同步或者增量同步的时候。
  • 同步在从节点也是非堵塞的。如果你在配置文件中配置好了,在从节点初始化同步的时候可以使用旧版本的数据集去响应请求。除此之外,你也可以配置Redis从节点在复制流来的的时候返回一个错误给客户端,在初始化同步的时候,旧的数据集将被删除,行的数据必须被加载。从节点将会在这个短暂时间窗口堵塞正在进来的连接(这将在巨大的数据集上花费长达许多秒的时间)。从Redis 4.0开始,删除就数据将会在另一个线程进行,但是加载新的初始化数据集依旧会在主线程执行,并且堵塞从节点。
  • 为了拥有多个从节点去处理只读请求(比如O(N)的慢查询操作可以由从节点分担),同步是可伸缩的,也是安全可靠的。
  • 避免主节点将所有数据集写入磁盘产生的损耗也是由办法的:一个典型的技术是通过配置不让主节点持久化数据到磁盘,然后配置从节点去做这件事,或者开始AOF。但是这个操作要非常小心,因为重启住节点将清空数据集:如果此时从节点去同步它,将会让从节点也清空。

当主节点的持久化关闭的时候,安全的同步数据

如果使用Redis同步,强烈建议在主节点和从节点都打开持久化。如果这因为担心延迟导致磁盘缓慢,实例应该通过修改配置,避免在重启的时候自动重启。
为了更好的理解在持久化关闭的时候,自动重启是非常危险的,检查下面数据数据从主节点和从节点都消失的模式:

  • 我们设置节点A作为主节点,关闭持久化,节点B和C从A处同步数据。
  • 节点A奔溃,然而他有自动重启系统,他将会自动重启。然而,因为持久化关闭,所以主节点将会以空数据集重启。
  • 节点B和C依旧从节点A复制数据,但是因为A是空的,所以他们也会将自己的数据同步为空。

Redis Sentinel用来做高可用的时候,主节点上将会关闭持久化,同时也关闭自动重启。比如主节点可以快速重启,快到Sentinel都来不及捕捉错误所以上面概括的失败的模式就发生了。

任何时候数据安全都是重要的是,当主节点使用同步并关闭持久化的时候,自动重启也要被关闭

Redis同步是怎么工作的

每一个Redis主节点有一个同步ID,是一个巨大的位随机字符串标记数据集。同时主节点也为发送到从节点的同步流提供一个增长偏移量,为了让从节点更新数据集状态。同步偏移量即使在没有从节点;链接的时候也会增长,所以每一次都将产生一对:

Replication ID, offset

标记主机数据集精确的版本
当从节点连接到主节点的时候,他们使用PSYNC命令发送他们目前为止执行的旧的同步ID和偏移量。这种方式下,主节点可以值发送增长部分,然而,如果主节点没有足够的日志备份,或者从节点使用了一个过去的同步ID,主节点无法找到他,就会发生全量同步:这种情况下,从节点将会得到一个完成的数据复制集,从新开始。
更多全量同步工作的方式:
主节点开始一个后台进程保存数据,产生一个RDB文件。同时开始缓存从客户端来的所有写命令。当后台保存进程完成,主节点将会把数据文件发送给从节点,他保存在磁盘中,然后加载到内存。主节点也会马上发送缓存命令到从节点。这将会以Redis协议本身相同的命令流格式完成。
你可以足迹通过telnet尝试。在服务店正在执行一些工作的时候连接Redis端口并发送SYNC命令。你可以看到大部分的传输并且主节点收到的所有命令都会telnet中打印出来。事实上SYNC是一个旧的协议,不在新的Redis实例上使用,但是依旧向后兼容的:他不允许重新增量同步,所以PSYNC用来代替他。

  • 就像已经说过的,从节点在主从节点因为某些断开的时候自动重新连接。如果主节点同时接到多个从节点同步请求,他只会启动一个单独的线程去服务他们。

无盘同步

正常情况下,一个全量同步需要创建一个RDB文件,然后从磁盘中读取这个文件,再发送给从节点。

然而磁盘读取是非常慢的,这将给主节点造成非常大的压力。Redis 2.8.18以后支持无盘同步。在这种设置下,子进程可以直接通过网络发送RDB给从节点,从而避免使用磁盘当做中间存储。

配置

配置Redis同步是很简单的,添加以下命令到配置文件就可以了:

slaveof 192.168.1.1 6379

当然你必须使用你主节点的IP和端口覆盖192.168.1.1 6379参数。当你使用SLAVEOF命令的时候,主节点就会开始同步数据到从节点

当然还有一些参数用来开启同步日志,让主节点加载到内存中去执行增量同步。可以查看Redis发行版中的redis.conf文件查看更新信息。

无盘同步可以使用repl-diskless-sync配置参数开启。延迟传输从而等待更多子节点链接主节点可以使用repl-diskless-sync-delay配置参数控制。更多栗子请查阅发行版中的redis.conf文件。

只读的从节点

Read-only slave
Redis 2.6开始,从节点支持只读模式,并且这是默认开启的。这个行为可以用slave-read-only来控制,可以在redis.conf修改,也可以使用CONFIG SET来设置。

只读的自己诶单拒绝所有的写命令,所以不可能写入从节点,因为会发生错误。这并不以为这这个特性是为了在不被信任的网络和客户端中暴露自己,因为管理命令,比如像DEBUGCONFIG之类的依旧可以使用。当然,只读实例的安全性可以通过在redus.conf中的rename-command禁用命令来提升。

你可能会想为什么要允许只读的从节点实例可以被改为可写的。这些写入的数据将会被主节点再次同步数据的时候删除,这里有几个合理的用户场景来说明为什么要短暂的在可写从节点存储数据。

比如统计慢Set或者Sorted Set操作,并存储他们到本地,这是一个可写从节点很常见的用户场景

然而,要记住,4.0以前的可写从节点没办法做到key的过期时间设置,这意味着,如果你使用EXPIRE或者其他命令去设置一个key最大的TTL,这个key将会泄露,你将无法使用读命令找到他,你将会在key的统计中看到他,并且他依旧占据着内存。所以,在通常情况下可写从节点使用TTL将会导致问题(4.0版本以前)。

Redis 4.0 RC3和更新的版本完全解决了这个发生在key写入超过63个数据库的问题(但是默认情况下Redis实例只有16个数据库),并且现在可写从节点可以像主节点一样驱逐过期的key

同样需要记住的是从Redis4.0以后,从节点写入的数据只存在本地,不会传播到连接到该子节点的子从节点,子从节点接收到的同步流将会和最高的主节点发送给中间节点一模一样。下面是栗子:

A ---> B ---> C

即使B是可写的,C也不会看到B写入的数据,只会看到A中的数据

设置从节点连接主节点时候的验证

如果你的主节点通过requirepass配置属性设置了密码,设置从节点使用密码同步也是非常简单的:
使用redis-cli连接从节点,输入:

config set masterauth <password>

为了永远记住他,添加到配置文件中:

masterauth <password>

只有N个从节点链接的时候才允许写入

Redis 2.8以后,可以设置主节点只有在有N台从节点链接的时候可以写入请求。

然而,因为Redis使用的是异步复制,所以没有办法保证从节点确实收到的给定的写入请求,纵游一个窗口期的数据丢失。
接下来是解释这个特性是如何工作的:
This is how the feature works:

  • 从节点每秒钟都会ping主节点,告知它所有的复制流工作了。
  • 主节点会记住从每个从节点收到的最新的ping
  • 用户可以给从节点配置一个在等于从节点的数量的最低值和不超过最高值之间的延迟

如果至少有N个从节点,如果少于延迟M秒,则写入将被接受。
你可能觉得经最大努力保证数据安全的机制,虽然数据一致性无法保证,但是至少只是几秒的时间窗口内丢失数据。通常情况下范围数据丢失可比无范围数据丢失好多了。
如果条件不满足,主节点将会返回一个错误,并且写入请求将不被接受
下面是这个特性的两个配置参数:

min-slaves-to-write <number of slaves>
min-slaves-max-lag <number of seconds>

查看更多信息,可以查阅打包在Redis发行版中的redis.config

How Redis replication deals with expires on keys

Redis同步是如何解决key的过期的

Redis过期允许key有一个有限的生存时间。这种特性依赖于实例多时间的控制,从节点正确的复制key和过期,就是这些keylua修改过。

为了实现这种特性,Redis不能信任主节点和从节点时钟同步的能力,因为这无法解决这个问题并且还会导致静态条件和数据集分离,所以Redis使用三个主要技术来保证过期key的同步:

  • 从节点不让key过期,而是等待主节点让key过期。当主节点让一个key过期(或者因为LRU被移除),它同步发往所有从节点的DEL命令。
  • 然而,因为只有主节点才执行过期操作,有些时候,从节点在内存中还保存这逻辑上已经过期的key,因为这时候主节点无法提供一个DEL命令。为了解决这个问题,从节点只用它自己的逻辑时钟报告说这个key不存在,并且只供读操作,并不去影响数据集的一致性(然后新的命令就会从主节点来)。这种方式下,从节点就避免了逻辑上过期的key还存在的问题。在真实的团推中,一个用从节点抓取的HTML片段将会避免返回已经比期望存在时间多的缓存。
  • Lua脚本执行的时候,key将不会过期,当Lua脚本执行的时候,概念上来讲,主节点的时间是停止的。所以一个key在脚本执行期间,要么存在,要么不存在。这防止key在脚本运行中间的时候过期,这也是为了发送相同的脚本到所有的从节点,保证了对数据集的影响是一致的。

当一个从节点晋升为主节点,他将会开始独立的过期key,不需要旧的主节点的任何帮助。

DockerNAT中配置同步

当使用Docker或者其他容器技术做端口转发或网络地址转换的时候,Redis同步有一些需要额外注意,特别是使用Redis Sentinel或者其他系统时,主节点INFOROLE命令需要输出并扫描,用来发现从节点地址。

问题在ROLE命令,和同步章节的INFO输出,当发送请求到主节点实例的时候,使用NAT的环境可能和从节点实例逻辑地址不同(可以用来连接从节点的那个地址)。

同样的,从节点将会被以配置文件中配置的监听的端口列出,这可能会和转发的端口不同,要注意端口可能被重映射。

为了解决这个问题,从Redid 3.2.2开始,强制要求从节点声明一个任意的IP和端口给主节点。这两个配置指定是:

slave-announce-ip 5.5.5.5
slave-announce-port 1234

这被记录在Redis发行版的redis.conf文件中。

INFOROLE命令

有两个命令提供了很多关于主节点和从节点当前的同步参数。一个是INFO。如果这个命令被以INFO replication的方式调用,则只显示和同步紧密相关的信息。另一个更加计算机友好的命令是ROLE,提供了主节点和从节点同步的状态,同步偏移,列出已连接的从节点等。

Redis 4.0以后支持重启和故障转移后的增量重新同步,当一个实例在故障转以后提升为主节点,他依旧可以从旧的主节点那边执行增量重新同步。为了做到这一点,从节点记住了从前的主节点的的同步ID和偏移,所以,可以提供部分的记录去连接从节点,尽管他们请求的是旧的同步ID。

然而,晋升的从及诶单提供的新的同步ID将会不同,因为他构建了一个不同的数据集历史。比如,有时候,主节点可以返回可以使用并且可以继续接受写入,所以在晋升的从节点使用相同的同步ID将会破坏规则,即一个同步ID和偏移对只标记一个数据集。
此外,当优雅关闭或者重启的时候,从节点可以存储从主节点同步必须的信息到RDB文件中。这对升级很有用,当必须的时候,最好在从节点使用SHUTDOWN命令去执行一个保存-退出操作。

阅读 2.2k

推荐阅读

哎,好像不能申请多个专栏呢,原本这个专栏只放前端文章,现在看来不行了!就都放吧!

22 人关注
111 篇文章
专栏主页