分布式系统中的几种Redis部署方式
为了解决一个程序只部署在一个服务器上的单点问题:
-
可用性问题,如果这个机器挂了,就意味着服务就中断了
-
一个程序只部署在一台机器上,它的性能/支持的并发量也是有限的
所以,就引入了分布式系统。在分布式系统中,往往希望有多个服务器来部署Redis服务,从而构成一个Redis集群,这样就可以让这个集群给整个分布式系统中的其他服务,提供更稳定/更高效的数据存储功能。
主从模式
在一个集群中,有的是主节点,有的是从节点。从节点的数据要跟随主节点变化,从节点的数据要和主节点保持一致。
本来,在主节点上保存的所有数据,在引入从节点之后,就要把主节点上的数据复制出来,放在从节点中,后续,主节点对于数据有任何的修改,都会把这样的修改同步到从节点上。
从节点就是主节点的副本。
Redis主从模式中,从节点上的数据,不允许修改,只能读取!
对于客户端发来的读取数据的操作,可以在主从节点中的任意一个节点中获取数据;对于写操作,则需要从主节点操作,或者从节点告知主节点进行操作,这样也就提高了性能和并发量。
对于之前的单个Redis服务器节点,如果机器挂了,整个Redis就挂了。但现在可以有多个节点,也可以把这些节点放在不同的机房中,这样就不会出现所有节点同时挂掉的情况。如果挂掉的是某一个从节点,不影响读写操作,如果挂掉的是主节点,则只能进行读操作。一定程度上提高了可用性。
更准确的说,主从模式,主要是针对读操作进行并发量和可用性的提高;对于写操作,无论是可用性还是并发,都非常依赖主节点,而且主节点只能有一个。好在实际业务场景中,读操作往往比写操作更频繁。
主节点上会收到源源不断的修改数据的请求,从节点需要从主节点这里同步这些修改请求。但是,从节点和主节点之间的数据同步,不是瞬间完成的。
每一个从节点中都会有一个offset字段,用来记录同步数据的进度
主从复制中的拓扑结构(若干节点之间,按照什么样的方式进行组织连接):
一主一从
一个主节点连接一个从节点,主节点和从节点都可以处理读数据请求,只有主节点可以处理写数据请求。
如果写数据请求太多,也会给主节点造成压力,这时可以关闭主节点的AOF,只在从节点上开启AOF,让主节点只操作内存中的数据,减少IO操作。
缺点在于,这种模式下,主节点一旦挂了,就不能让他自动重启,因为没有AOF文件,如果自动重启,就会丢失数据,进一步的主从同步,会把从节点的数据也删除掉。
改进办法:主节点挂了之后,让其从从节点获取到AOF文件,再启动。
一主多从
因为实际的开发中,读请求往往远远超过写请求,所以应该适量增加从节点的数量以提高效率
主节点上的数据发生改变,会把改变的数据同时同步给所有从节点。
但是,随着从节点的数量增多,主节点要同步一条数据就需要传输多次,所以往往需要增加主节点的网络带宽,增大了成本。
树形结构
这种结构,不要求主节点有太高的网络带宽,从节点也可以帮助数据同步,但是进行数据修改时,同步的延时更长了。
主从复制流程
数据同步的方法
Redis提供了psync命令,可以完成数据同步的过程。
这个命令不需要手动执行,Redis服务器会在建立好主从同步关系之后,自动执行psync(从节点执行psync,从节点从主节点拉取数据)。
进行数据复制,需要从节点在与主节点建立好复制关系之后,从主节点获取到replication id(复制ID)。这个ID是主节点启动时自动生成的,每个主节点每次重启得到的ID都是不同的。根据这个ID就可以知道当前从节点是从哪个主节点复制的数据(在集群中会有多个主节点)。
每一个节点中都还会有一个offset值,对于主节点,每次有新的修改数据的操作时,都会在这个值原来的基础上增加;对于从节点,每同步一部分主节点中的数据,这个值也会增加,当从节点中的offset值跟主节点中的offset值相等时,就证明数据同步完成。
replication id 和 offset 共同描述了一个“数据集合”
如果有两个机器,replication id 一样,offset 也一样,就可以认为这两个 redis 机器上存储的数据是完全一样的。
psync运行流程:
-
从节点发送 psync 命令给主节点,replid 和 offset 的默认值分别是?和-1.
-
主节点根据 psync 参数和自身数据情况决定响应结果:
-
如果回复 +FULLRESYNC replid offset,则从节点需要进行全量复制流程
-
如果回复 +CONTINUE,从节点进行部分复制流程
-
如果回复 -ERR,说明Redis主节点版本过低,不支持 psync 命令,从节点可以使用 sync 命令进行全量复制
psync 可以从主节点获取全量数据,也可以获取一部分数据,具体区别于 offset 的进度。
如果 offset 为-1,则获取全量数据;如果是具体的整数,则从当前偏移量位置进行获取部分数据。
复制全量数据会有比较大的消耗,所以一般还是部分数据复制更好,但是如果主节点本身并不适合部分复制,就会仍然给从节点进行全量复制。
较老的版本的Redis不支持psync,可以用sync代替,不过后者会阻塞其他操作,所以不建议使用。
一般什么时候会进行部分复制?
从节点之前已经从主节点上复制过数据了,但因为网络抖动导致从节点重启了,这时需要重新行主节点同步数据,在大部分数据已经同步的情况下,希望只同步一小部分未复制的数据。
全量复制流程
-
从节点发送 psync 命令给主节点进行数据同步,由于是第一次进行复制,从节点没有主节点的运行 ID 和复制偏移量,所以发送 psync ? -1。
-
主节点根据命令,解析出要进行全量复制,回复 +FULLRESYNC 响应。
-
从节点接收主节点的运行信息进行保存。
-
主节点执行 bgsave 进行 RDB 文件的持久化。
-
主节点发送 RDB 文件给从节点,从节点保存 RDB 数据到本地硬盘。
-
主节点将从生成 RDB 到接收完成期间执行的写命令,写入缓冲区中,等从节点保存完 RDB 文件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 RDB 的二进制格式追加写入到收到的 RDB 文件中,保持主从一致性。
-
从节点清空自身原有数据。
-
从节点加载 RDB 文件得到与主节点一致的数据。
-
如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进行 bgrewrite 操作,得到最近的 AOF 文件。
第四步中,使用 RDB 是因为二进制节省空间,不能使用已有的 RDB 文件,而要重新生成,是因为已有的文件和当前的最新数据可能存在较大差异。
上述由主节点向从节点传送数据使用的是 RDB 文件,要写入文件就要有IO操作,效率就会降低。所以,为了保证效率,Redis 主节点进行全量复制的时候,也支持“无硬盘模式”(diskless),也就是主节点生成的 RDB 的二进制数据,不再直接保存到文件中,而是直接进行网络传输。同样的,从节点也不需要先把接收到的数据写入硬盘,然后再加载,而是直接把收到的数据写入内存。
但是,即使引入了无硬盘模式,整个操作仍然是比较重量级的,比较耗时的,网络传输是无法省去的,所以效率也并没有提升很多。
关于 replication id 和 runid
具有主从关系的节点之间的 replication id 是相同的,但是每个节点的 runid 都是不同的。
runid 主要用于支撑实现 Redis 哨兵的功能,和主从复制没什么关系。而 replication id 在上文中也表明,它主要是用于主从复制。
部分复制流程
-
当主从节点之间出现网络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
-
主从连接中断期间主节点依然响应命令,但这些复制命令都因网络中断无法及时发送给从节点,所以暂时将这些命令滞留在复制积压缓冲区(一个基于数组实现的环形队列)中。
-
当主从节点网络恢复后,从节点再次连接上主节点。
-
从节点将之前保存的 replication 和 offset 作为 psync 的参数发送给主节点,请求进行部分复制。
-
主节点接到 psync 请求后,进行必要的验证,随后根据 offset 去复制积压缓冲区查找合适的数据,并响应 +CONTINUE 给从节点。
-
主节点将需要从节点同步的数据发送给从节点,最终完成一致性。
上文中提到的“主节点有时候会判定适不适合进行部分复制,不适合的话就进行全量复制”,其实指的是,从节点虽然之前进行过数据复制,但是后续需要复制的数据已经超出积压缓冲区中的数据了,这时只把积压缓冲区中的数据发送给从节点是不够的,所以只能进行全量复制。
实时复制
-
全量复制:从节点刚连上主节点之后,进行的数据初始化工作。
-
部分复制:全量复制的特殊情况,优化手段,目的和全量复制一样。
-
实时复制:从节点已经和主节点同步好了数据,但是之后主节点会源源不断地收到新的修改数据的请求,主节点上的数据就会随之改变,这些数据也同样需要同步给从节点。
实时复制需要主从节点之间建立 TCP 长连接,随后主节点把自己收到的修改数据的请求发送给从节点,从节点再根据这些修改请求修改内存中的数据。
在进行实时复制时,需要保证连接处于可用状态,所采用的是“心跳包”机制。
-
主节点:默认每隔10秒给从节点发送一个 ping 命令,从节点收到就返回 pong
-
从节点:默认每隔1秒给主节点发起一个特定的请求,上报当前从节点复制数据的进度(offset)