复制
复制功能是Redis提供的多机功能中最基础的一个,这个功能是通过主从复制(master-slave replication)模式实现的,它允许用户为存储着目标数据库的服务器创建出多个拥有相同数据库副本的服务器,其中存储目标数据库的服务器被称为主服务器(master server),而存储数据库副本的服务器则被称为从服务器(slave server)。如下图所示:
对于Redis来说,一个主服务器可以拥有任意多个从服务器,而从服务器本身也可以用作其他服务器的主服务器,并以此构建出一个树状的服务器结构。
在默认情况下,处于复制模式的主服务器既可以执行写操作也可以执行读操作,而从服务器只能执行读操作。
对于开启了复制功能的主从服务器,主服务器在每次执行写操作之后,都会与所有从服务器进行数据同步,以此来将写操作产生的改动反应到各个从服务器上。
主从复制的优点
Redis的复制功能可以从性能、安全性和可用性3个方面提示整个Redis系统:
- 首先,在性能方面,Redis的复制功能可以给系统的读性能带来线性级别的提升。理论上每增加一倍数量的从服务器,整个系统的读性能就会提升一倍。
- 其次,通过增加从服务器的数量,可以降低系统在遭遇故障时丢失数据的可能性。
- 最后,通过同时使用Redis的复制功能和Sentinel 功能,可以为整个Redis系统提供高可用性。
REPLICAOF:将服务器设置为从服务器
在很长一段时间,Redis一直使用 SLAVEOF作为复制命令,但是从 Redis 5.0.0 版本开始,Redis正式将 SLAVEOF命令改名为 REPLICAOF命令并逐渐废弃原来的SLAVEOF命令。本文是基于 Redis 5.0版本而写的,因此在下面的内容中,将使用 REPLICAOF 命令作为复制命令。
通过执行 REPLICAOF 命令,将接收这个命令的Redis服务器设置为另一个Redis服务器的从服务器:
REPLICAOF host port
其中命令的 host 参数用于指定主服务器的IP地址,而 port 参数用于指定主服务器的端口号。因为Redis的复制操作是以异步方式进行的,所以收到 REPLICAOF 命令的服务器在记录在主服务器的地址和端口号之后就会向客户端返回OK,至于实际的复制操作则会在后台进行。
在接收到 REPLICAOF 命令之后,主从服务器将执行数据同步操作:从服务器原有的数据将被清空,取而代之的是主服务器传送过来的数据副本。数据同步完成之后,主从服务器将拥有相同的数据。
通过配置选项设置从服务器
除了可以使用 REPLICAOF命令设置从服务器之外,还可以通过设置 replicaof 配置选项,在启动Redis服务器的时候将它设置为从服务器:
replicaof host port
redis-server --port 6380 --replicaof 127.0.0.1 6379
取消复制
在使用 REPLICAOF 命令或 replicaof 配置选项将一个服务器设置为从服务器后,可以通过以下命令,让从服务器停止复制,重新变回主服务器:
REPLICAOF no one
服务器在停止复制后不会清空数据库,而是会继续保留复制产生的所有数据。
复杂度:REPLICAOF 命令本身的复杂度为 ,但它引起的异步复制操作的复杂度为 ,其中 为主服务器包含的键值对的总数量。REPLICAOF no one命令的复杂度为
版本要求:REPLICAOF 命令从 Redis 5.0.0版本开始可用。SLAVEOF命令从 Redis 1.0.0版本开始可用,但从 Redis 5.0.0后逐渐废弃。
ROLE:查看服务器的角色
用户可以通过执行 ROLE 命令来查看服务器当前担任的角色:
ROLE
ROLE命令在主服务器或者从服务器上执行产生的效果不同。
主服务器执行ROLE命令
如果执行ROLE命令的是主服务器,那么命令将返回一个由3个元素组成的数组作为结果:
- 数组的第1个元素是字符串 “master”,它表示这个服务器的角色是主服务器。
- 数组的第2个元素是这个服务器的复制偏移量(replication offset),它是一个整数,记录了主服务器目前向复制数据量发送的数据数量。
- 数组的第3个元素是一个数组,它记录了这个主服务器属下的所有从服务器。这个数组的每个元素都由3个元素组成,第1个子元素为从服务器的IP地址,第2个子元素是从服务器的端口号,第3个子元素则是从服务器的复制偏移量。从服务器的复制偏移量记录了从服务器通过复制数据流接收到的复制数据数量,当从服务器的复制偏移量与主服务器的复制偏移量保持一致时,它们的数据就是一致的。
从服务器执行ROLE命令
如果执行ROLE命令的是从服务器,那么命令将返回一个由5个元素组成的数组作为结果:
- 数组第1个元素是字符串 “slave” ,它表示这是一个从服务器。
- 数组的第2个元素和第3个元素记录了从服务器正在复制的主服务器的IP地址和端口号。
- 数组的第4个元素是主服务器与从服务器当前的连接状态,这个状态的值及其含义是:
○ “none:” 主从服务器尚未建立连接。
○ “connect:” 主从服务器正在握手。
○ “connecting:” 主从服务器成功建立了连接。
○ “sync:” 主从服务器正在进行数据同步。
○ “connected:” 主从服务器已经进入了在线更新状态。
○ “unknown:” 主从服务器连接状态未知。 - 数组第5个元素是从服务器当前的复制偏移量。
复杂度:
版本要求:ROLE命令从Redis 2.8.12版本开始可用。
数据同步
当用户将一个服务器设置为从服务器,让它去复制另一个服务器的数据时,主从服务器需要通过数据同步机制让两个服务器的数据库状态保持一致。
完整同步
当一个Redis服务器接收到 REPLICAOF 命令,开始对另一个服务器进行复制的时候,主从服务器会执行以下操作:
- 主服务器执行 BGSAVE 命令,生成一个RDB文件,并使用缓冲区存储起在 BGSAVE 命令之后执行的所有写命令。
- 当RDB文件创建完毕,主服务器会通过套接字将RDB文件传送给从服务器。
- 从服务器在接受完主服务器传送过来的RDB文件之后,就会载入这个RDB文件,从而获得主服务器在执行 BGSAVE 命令时的所有数据。
- 当从服务器完成RDB文件载入操作,并开始上线接受命令请求时,主服务器就会把之前存储在缓冲区的所有写命令发送给从服务器执行。
因为主服务存储的写命令都是在执行 BGSAVE 命令之后执行的,所以当从服务器载入完RDB文件,并执行完主服务器存储在缓冲区中的所有写命令之后,主从服务器的数据将保持一致。
这个通过创建、传送并载入RDB文件来达成数据一致的步骤,我们称之为完整同步操作。每个从服务器在刚开始进行复制的时候,都需要与主服务器进行一次完整同步。
在线更新
主从服务器在执行完整同步操作之后,它们的数据就达到了一致,但这种一致并不是永久的:每当主服务器执行了新的写命令之后,它的数据库就会被改变,这时主从服务器数据一致性就会被破坏。为了让主从服务器数据保持一致,Redis会对从服务器进行在线更新:
- 每当主服务器执行完一个写命令之后,它就会将相同的写命令或具有相同效果的写命令发送给从服务器执行。
- 因为完整同步之后的主从服务器在执行最新出现的写命令之前,两者的数据库完全相同,而导致两者数据库不同的是最新被执行的写命令,因此从服务器只要接收并执行主服务器发来的写命令,就可以让自己的数据库重新与主服务器数据库保持一致。
需要注意的是,因为在线更新是异步进行的,所以在主服务器执行写命令之后,直到从服务器也执行相同的写命令的这段时间里,主从服务器的数据库会出现短暂的不一致,因此要求强一致性的程序可能需要直接读取主服务器而不是从服务器。
部分同步
当因故障下线的从服务器重新上线,主从服务器的数据通常已经不一致,因此它们必须重新进行同步,让两者的数据再次回到一致。
在Redis 2.8版本之前,重同步操作是通过直接进行完整同步来实现的,但是这种重同步方式在从服务器只是短暂下线的情况下非常浪费资源,因此,Redis从 2.8 版本开始使用新的重同步功能去代替原来的重同步功能:
- 当一个Redis服务器成为另一个服务器的主服务器时,它会把每个被执行的写命令都记录到一个特定长度的先进先出的队列中。
- 当断线的从服务器尝试重新连接主服务器时候,主服务器将检查从服务器断线期间,被执行的那些写命令是否仍然保存在队列中。如果是,那么主服务器就会直接把从服务器缺失的那些写命令发送给从服务器,从服务器通过执行这些写命令就可以重新与主服务器保持一致。
- 如果从服务器缺失的那些写命令已经不存在与队列中,那么主从服务器就进行一次完整同步。
因为新的重同步功能需要使用先进先出的队列来记录主服务器执行过的写命令,所以这个队列的体积越大,它能够记录的命令就越多,从服务器断线能够快速恢复的机会也越大。Redis为这个队列设置的默认大小为 1MB,用户也可以根据需要,通过配置选项 repl-blacklog-size 来修改这个队列的大小。
无须硬盘的复制
Redis从2.8.18版本开始引入了无须硬盘的复制特性:启用了这个特性的主服务器在接收到REPLICAOF命令时,将不再在本地创建RDB文件,而是会派生一个子进程,然后由子进程通过套接字直接将RDB文件写入从服务器。这样主服务器就可以在不创建RDB文件的情况下,完成与从服务器的数据同步。
要使用无须硬盘的复制特性,只需要将 repl-diskless-sync 配置选项的值设置为 yes 即可:
repl-diskless-sync <yes|no>
redis-server --repl-diskless-sync yes
该特性只是避免了在主服务器上创建RDB文件,但仍然需要在从服务器上创建RDB文件。
降低数据不一致的概率
为了尽可能的降低数据不一致出现的概率,Redis从2.8版本开始引入了两个以 min-replicas开头的配置选项:
min-replicas-max-lag <seconds>
min-replicas-to-write <numbers>
用户设置了这两个配置选项之后,主服务器只会在从服务器的数量大于等于 min-replicas-to-write 选项的值,并且这些从服务器与主服务器最后一次成功通信间隔不超过 min-replicas-max-lag 选项的值时才会执行写命令。
因为在线更新的异步性质,min-replicas-max-lag 和 min-replicas-to-write 并没有办法完全杜绝数据不一致的情况出现,但它们可以有效的减少主从服务器连接不稳定而导致的数据不一致,并降低因为没有从服务器可用而导致数据丢失的可能性。
可写的从服务器
从Redis 2.6版本开始,Redis的从服务器在默认情况下只允许执行读命令。如果用户尝试对一个只读的从服务器执行写命令,那么从服务器将返回以下错误信息:
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
OK
127.0.0.1:6380> SET msg "hello world"
(error) READONLY You can't write against a read only replica .
Redis之所以将从服务器设置为只读的,是为了确保从服务器只能通过与主服务器进行数据同步来得到更新,从而保证主从服务器之间的数据一致性。
如果想要让从服务器支持读操作,可以通过以下配置来完成:
replica-read-only <yes|no>
只需要将 replica-read-only
设置为 no
即可。
使用可写从服务器的注意事项:
- 在主从服务器都可写的情况下,程序必须将写命令发送到正确的服务器上,不能把需要在主服务器上执行的写命令发送到从服务器上执行,也不能把需要在从服务器上执行的写命令发送到主服务器上执行。
- 从服务器执行写命令得到的数据,可能会被主服务器发送的写命令覆盖。
- 当从服务器与主服务器进行完成同步时,从服务器数据库包含的所有数据都将被清空,其中包括客户端写入的数据。
- 为了减少内存占用,降低键冲突发生,并确保主从服务器的数据同步操作可以顺利进行,客户端写入从服务器的数据应该在使用完毕之后尽快删除。
脚本复制
Redis服务器拥有两种不同的脚本复制模式,第一种是从 Redis 2.6版本开始支持的脚本传播模式,另一种则是从 Redis 3.2版本开始的命令传播模式。
脚本传播模式
处于脚本传播模式的主服务器会将被执行的脚本及其参数(也就是EVAL命令本身)复制到AOF文件以及从服务器中。因为带有副作用的函数在不同服务器上运行时可能会产生不同的结果,从而导致主从服务器不一致,所以在这一模式下执行的脚本必须是纯函数,换句话说,对于相同的数据集,相同的脚本以及参数必须产生相同的效果。
为了保证脚本的纯函数性质,Redis对处于脚本传播模式的Lua脚本设置了以下限制:
- 脚本不能访问Lua的时间模块,内部状态或者除给定参数之外的其他外部信息。
- 在Redis的命令中,存在着一部分带有随机性的命令,这些命令对于相同的数据集以及相同的参数可能会返回不同的结果。例如:SPOP、SRANDMEMBER、SCAN、SSCAN、ZSCAN、HSCAN、RANDOMKEY、LASTSAVE、PUBSUB、TIME。
- 当用户在脚本中调用 SINTER、SUNINON、SDIFF、SMEMBERS、HKEYS、HVALS、KEYS这7个会随机顺序返回结果的元素的命令时,为了消除随机性,Lua环境在返回这些命令的结果之前会先对结果中包含的元素进行排序,以此来确保命令返回的元素总是有序的。
- Redis会确保每个被执行的脚本都拥有相同的随机数生成器种子,这意味着如果用户不主动修改这一种子,那么所有脚本在默认情况下产生的伪随机数都将是相同的。
脚本模式是Redis复制脚本时默认使用的模式。如果用户在执行脚本之前没有修改过相关的配置选项,那么Redis将使用脚本传播模式来复制脚本。
命令传播模式
处于命令传播模式的主服务器会将执行脚本产生的所有写命令用事务包裹起来,然后将事务复制到AOF文件以及从服务器中。因为命令传播模式复制的是写命令而不是脚本本身,所以即使脚本本身包含副作用,主服务器给所有从服务器复制的写命令仍然是相同的,因此处于命令传播模式的主服务器能够执行带有副作用的非纯函数脚本。
除了脚本可以不是纯函数之外,与脚本传播模式相比,命令传播模式对Lua环境还有以下放松:
- 用户可以在执行 RANDOMKEY、SRANDMEMBER等带有随机性的命令之后继续执行写命令。
- 脚本的伪随机数生成器在每次调用之前,都会随机地设置种子。
除了以上两点之外,命令传播模式与脚本传播模式的Lua环境限制都一样。比如,即使在命令传播模式下,脚本还是无法访问Lua的时间模块以及内部状态。
开启命令传播模式,用户在使用脚本执行任何写操作之前,需要先在脚本中调用以下函数:
redis.replicate_commands()
redis.replicate_commands()
只对调用该函数的脚本有效:在使用命令传播模式执行完当前脚本之后,服务器将自动切换会默认的脚本传播模式。
选择性命令传播
为了进一步提升命令传播模式的作用,Redis允许用户在脚本中选择性的打开或关闭命令传播功能,这一点可以通过在脚本中调用redis.set_repl()
函数并向它传入以下4个值来完成:
- redis.REPL_ALL —— 默认值,将写命令传播至AOF文件以及所有的从服务器
- redis.REPL_AOF —— 只将写命令传播至AOF文件;
- redis.REPL_SLAVE —— 只将写命令传播至所有从服务器;
- redis.REPL_NONE —— 不传播写命令;
与redis.replicate_commands() 函数一样,**redis.set_repl()**函数也只对执行该函数的脚本有效。用户可以通过这一功能来定制被传播的命令序列,以此来确保只有真正需要的命令才会被传播至AOF文件以及从服务器。
-- 打开命令传播模式
-- 以便在执行SRANDMEMBER之后继续执行DEL
redis.replicate_commands()
-- 因为这个脚本即使不向从服务器传播SUNIONSTORE命令和DEL命令
-- 也不会导致主从服务器数据不一致,所以我们可以把命令传播功能关掉
redis.set_repl(redis.REPL_NONE)
-- 集合键
local set_a = KEYS[1]
local set_b = KEYS[2]
local result_key = KEYS[3]
-- 随机元素的数量
local count = tonumber[ARGV[1])
-- 计算并集,随机选出指定数量的并集元素,然后删除元素
redis.call('SUNIONSTORE',result_key,set_a,set_b)
local elements = redis.call('SRANDMEMBER',result_key,count)
redis.call('DEL',result_key)
-- 返回随机选出的并集元素
return elements;
模式的选择
既然存在着两种不同的脚本复制模式,那么如何选择正确的模式来复制脚本就显得至关重要了。一般来说,用户可以根据以下情况来判断应该使用哪种复制模式:
- 如果脚本的体积不大,执行的计算也不多,但是会产生大量的命令调用,那么使用脚本传播模式可以有效的节约网络资源。
- 相反,如果一个脚本的体积非常大,执行的计算非常多,但是只会产生少量的命令调用,那么使用命令传播模式可以通过重用已有的计算结果来节约计算资源以及网络资源。
转载:https://blog.csdn.net/lingxi0726/article/details/106753307