0 序言
复制功能是Redis提供的多机功能中最基础的一个,这个功能是通过主从复制(master-slave replication)模式实现的,它允许用户为存储着目标数据库的服务器创建出多个拥有相同数据库副本的服务器,其中存储目标数据库的服务器被称为主服务器(master server),而存储数据库副本的服务器则被称为从服务器(slave server,或者称为replica)。
Redis的复制功能可以从性能、安全性和可用性3个方面提升整个Redis系统:
●首先,在性能方面,Redis的复制功能可以给系统的读性能带来线性级别的提升。从理论上来说,用户每增加一倍数量的从服务器,整个系统的读性能就会提升一倍。
●其次,通过增加从服务器的数量,用户可以降低系统在遭遇灾难故障时丢失数据的可能性。具体来说,如果用户只有一台服务器存储着目标数据库,那么当这个服务器遭遇灾难故障时,目标数据库很有可能会随着服务器故障而丢失。但如果用户为Redis服务器(即主服务器)设置了从服务器,那么即使主服务器遭遇灾难故障,用户也可以通过从服务器访问数据库。从服务器的数量越多,因为主服务器遭遇灾难故障而出现数据库丢失的可能性就越低。
●最后,通过同时使用Redis的复制功能和Sentinel功能,用户可以为整个Redis系统提供高可用特性。具有这一特性的Redis系统在主服务器停机时,将会自动挑选一个从服务器作为新的主服务器,以此来继续为客户提供服务,避免造成整个系统停机。
1 REPLICAOF:将服务器设置为从服务器
Redis 5.0.0之前的版本,使用SLAVEOF命令
SLAVEOF host port
从5.0.0版本开始,Redis正式将SLAVEOF命令改名为REPLICAOF命令
REPLICAOF host port
命令的host参数用于指定主服务器的地址,而port参数则用于指定主服务器的端口号。
在接收到REPLICAOF命令之后,主从服务器将执行数据同步操作:从服务器原有的数据将被清空,取而代之的是主服务器传送过来的数据副本。数据同步完成之后,主从服务器将拥有相同的数据。
1.1 通过配置选项设置从服务器
在从服务器的配置文件中,添加:
replicaof <host> <port>
从服务器启动时,将自动设置。
1.2 取消复制
在使用REPLICAOF命令或者replicaof配置选项将一个服务器设置为从服务器之后,我们可以通过执行以下命令,让从服务器停止复制,重新变回主服务器:
REPLICAOF no one
服务器在停止复制之后不会清空数据库,而是会继续保留复制产生的所有数据。
2 ROLE:查看服务器的角色
可以通过执行ROLE命令来查看服务器当前担任的角色:
ROLE
2.1 主服务器执行ROLE命令
如果执行ROLE命令的是主服务器,那么命令将返回一个由3个元素组成的数组作为结果:
●数组的第1个元素是字符串"master",它表示这个服务器的角色为主服务器。
●数组的第2个元素是这个主服务器的复制偏移量(replication offset),它是一个整数,记录了主服务器目前向复制数据流发送的数据数量。
●数组的第3个元素是一个数组,它记录了这个主服务器属下的所有从服务器。这个数组的每个元素都由3个子元素组成,第1个子元素为从服务器的IP地址,第2个子元素为从服务器的端口号,而第3个子元素则为从服务器的复制偏移量。从服务器的复制偏移量记录了从服务器通过复制数据流接收到的复制数据数量,当从服务器的复制偏移量与主服务器的复制偏移量保持一致时,它们的数据就是一致的。
2.2 从服务器执行ROLE命令
如果执行ROLE命令的是从服务器,那么命令将返回一个由5个元素组成的数组作为结果:
●数组的第1个元素是字符串"slave",它表示这个服务器的角色是从服务器。
●数组的第2个元素和第3个元素记录了这个从服务器正在复制的主服务器的IP地址和端口号。
●数组的第4个元素是主服务器与从服务器当前的连接状态,这个状态的值及其表示的意义如下:○"none":主从服务器尚未建立连接。○"connect":主从服务器正在握手。○"connecting":主从服务器成功建立了连接。○"sync":主从服务器正在进行数据同步。○"connected":主从服务器已经进入在线更新状态。○"unknown":主从服务器连接状态未知。
●数组的第5个元素是从服务器当前的复制偏移量。
3 数据同步
当用户将一个服务器设置为从服务器,让它去复制另一个服务器的时候,主从服务器需要通过数据同步机制来让两个服务器的数据库状态保持一致。
3.1 完整同步
当一个Redis服务器接收到REPLICAOF命令,开始对另一个服务器进行复制的时候,主从服务器会执行以下操作:
1)主服务器执行BGSAVE命令,生成一个RDB文件,并使用缓冲区存储起在BGSAVE命令之后执行的所有写命令。
2)当RDB文件创建完毕,主服务器会通过套接字将RDB文件传送给从服务器。
3)从服务器在接收完主服务器传送过来的RDB文件之后,就会载入这个RDB文件,从而获得主服务器在执行BGSAVE命令时的所有数据。
4)当从服务器完成RDB文件载入操作,并开始上线接受命令请求时,主服务器就会把之前存储在缓存区中的所有写命令发送给从服务器执行。因为主服务器存储的写命令都是在执行BGSAVE命令之后执行的,所以当从服务器载入完RDB文件,并执行完主服务器存储在缓冲区中的所有写命令之后,主从服务器包含的数据库数据将完全相同。
这个通过创建、传送并载入RDB文件来达成数据一致的步骤,我们称之为完整同步操作。每个从服务器在刚开始进行复制的时候,都需要与主服务器进行一次完整同步。
3.2 在线更新
主从服务器在执行完完整同步操作之后,它们的数据就达到了一致状态,但这种一致并不是永久的:每当主服务器执行了新的写命令之后,它的数据库就会被改变,这时主从服务器的数据一致性就会被破坏。
为了让主从服务器的数据一致性可以保持下去,让它们一直拥有相同的数据,Redis会对从服务器进行在线更新:
●每当主服务器执行完一个写命令之后,它就会将相同的写命令或者具有相同效果的写命令发送给从服务器执行。
●因为完整同步之后的主从服务器在执行最新出现的写命令之前,两者的数据库是完全相同的,而导致两者数据库出现不一致的正是最新被执行的写命令,因此从服务器只要接收并执行主服务器发来的写命令,就可以让自己的数据库重新与主服务器数据库保持一致。
只要从服务器一直与主服务器保持连接,在线更新操作就会不断进行,使得从服务器的数据库可以一直被更新,并与主服务器的数据库保持一致。
3.3 部分同步
当因故障下线的从服务器重新上线时,主从服务器的数据通常已经不再一致,因此它们必须重新进行同步,让两者的数据库再次回到一致状态。
●当一个Redis服务器成为另一个服务器的主服务器时,它会把每个被执行的写命令都记录到一个特定长度的先进先出队列中。
●当断线的从服务器尝试重新连接主服务器的时候,主服务器将检查从服务器断线期间,被执行的那些写命令是否仍然保存在队列里面。如果是,那么主服务器就会直接把从服务器缺失的那些写命令发送给从服务器执行,从服务器通过执行这些写命令就可以重新与主服务器保持一致,这样就避免了重新进行完整同步的麻烦。
●如果从服务器缺失的那些写命令已经不存在于队列当中,那么主从服务器将进行一次完整同步。
Redis为这个队列设置的默认大小为1MB,用户也可以根据自己的需要,通过配置选项repl-backlog-size来修改这个队列的大小。
4 无须硬盘的复制
,主服务器在进行完整同步的时候,需要在本地创建RDB文件,然后通过套接字将这个RDB文件传送给从服务器。但是,如果主服务器所在宿主机器的硬盘负载非常大或者性能不佳,那么创建RDB文件引起的大量硬盘写入将对主服务器的性能造成影响,并导致复制进程变慢。
使用无须硬盘的复制特性,我们只需要将repl-diskless-sync配置选项的值设置为yes即可:
repl-diskless-sync <yes|no>
启用了这个特性的主服务器在接收到REPLICAOF命令时将不会再在本地创建RDB文件,而是会派生出一个子进程,然后由子进程通过套接字直接将RDB文件写入从服务器。这样主服务器就可以在不创建RDB文件的情况下,完成与从服务器的数据同步。
无须硬盘的复制特性只是避免了在主服务器上创建RDB文件,但仍然需要在从服务器上创建RDB文件。
5 降低数据不一致情况出现的概率
,因为复制的在线更新操作以异步方式进行,所以当主从服务器之间的连接不稳定,或者从服务器未能收到主服务器发送的更新命令时,主从服务器就会出现数据不一致的情况。
为了尽可能地降低数据不一致的出现概率,以min-replicas开头的配置选项:
min-replicas-max-lag <seconds>//成功通信间隔
min-replicas-to-write <numbers>//从服务器个数
同时不满足上述两个条件的话,会执行在线更新。
6 可写的从服务器
Redis的从服务器在默认状态下只允许执行读命令。Redis之所以将从服务器默认设置为只读服务器,是为了确保从服务器只能通过与主服务器进行数据同步来得到更新,从而保证主从服务器之间的数据一致性。
可以通过将replica-read-only配置选项的值设置为no来打开从服务器的写功能:
replica-read-only <yes|no>
从服务器写的命令,可能会被主服务器覆盖,并且在进行完整同步时,从服务器独特的数据会被清空。
为了避免主从服务器冲突,一个比较简单的方法是在客户端向从服务器写入数据的同时,为数据设置一个比较短的过期时间,使得这些数据可以在使用完毕之后自动被删除。