当前位置: 首页 > 科技观察

RedisHighAvailability:你把这个叫做主从架构数据同步原理吗?

时间:2023-03-12 09:39:54 科技观察

高可用有两层含义:一是尽可能避免数据丢失,二是尽可能提供服务。AOF和RDB尽可能保证数据持久化不丢失,而主从复制是增加副本,一份数据保存到多个实例。即使一个实例宕机,其他实例仍然可以提供服务。本文主要带大家了解Redis高可用技术方案之一的主从复制架构。核心知识点开场留言问题=机会。遇到问题的时候,心里其实是快乐的。问题越大,机会就越大。一切都是有代价的。有得必有失,有失必有得,所以很多事情都不用操心。我们只要想清楚自己想做什么,愿意为此付出什么样的代价,然后放手去做吧!一、主从复制概述65师兄:有了RDB和AOF,再也不怕宕机丢失数据了,但是Redis实例宕机时如何实现高可用呢?既然一台机器宕机不能提供服务,那么有多少台?能解决吗?Redis提供了主从模式,通过主从复制,将数据冗余复制到其他Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据复制是单向的,只从主节点到从节点。默认情况下,每个Redis服务器都是一个主节点;一个主节点可以有多个从节点(或者没有从节点),但是一个从节点只能有一个主节点。65师兄:如何保证master和slave之间数据的一致性?为了保证副本数据的一致性,主从架构采用了读写分离的方式。读操作:主从库都可以执行;写操作:主库先执行,然后写操作同步到从库;Redis读写分离65哥:为什么要用读写分离的方式?我们可以假设主从库都可以执行写命令。如果多次修改同一个数据,每次修改都发送到不同的主从实例,会导致实例的副本数据不一致。如果Redis为了保证数据的一致性,需要加锁协调多个实例的修改,Redis自然不会这么做!65哥:主从复制还有其他作用吗?故障恢复:当主节点宕机时,其他节点仍然可以提供服务;负载均衡:Master节点提供写服务,Slave节点提供读服务,分担压力;高可用基石:是哨兵和集群实现的基础,是高可用的基石。2.搭建主从复制主从复制的启动完全是在从节点上发起的,我们不需要在主节点上做任何事情。65师兄:如何搭建主从复制架构?可以使用replicaof(Redis5.0之前的slaveof)命令形成主从库关系。从节点开启主从复制的方式有以下三种:在从服务器的配置文件中添加replicaofstartup命令,在从服务器的启动命令后添加--replicaofclient命令。执行命令:replicaof,则Redis实例成为从节点。例如,假设有实例1(172.16.88.1)、实例2(172.16.88.2)和实例3(172.16.88.3),分别在实例2和实例3上执行以下命令,实例2和实例3变成实例1从库中,实例1成为主实例。副本172.16.88.163793。主从复制原理主从库模式一旦采用读写分离,所有的数据写入操作只会在主库上进行,不协调三个实例。主库有最新数据后,会同步到从库,使主库和从库的数据保持一致。65师兄:主从数据库同步是如何完成的?主库数据是一次性传到从库,还是分批同步?正常运行时如何同步?如果主从数据库之间的网络断开了,重新连接之后数据还能保持一致吗?65哥,你怎么这么多问题啊?同步分为三种情况:主从数据库第一次全量拷贝;主从正常运行时的同步;主从数据库断网重连。主从库的第一个完整副本65哥:我晕了,还是先从主从库之间的第一个同步开始吧。主从库的第一次复制过程大致可以分为三个阶段:连接建立阶段(即准备阶段)、主库同步数据到从库阶段、发送新的write同步到从库阶段的命令;有一种整体观的感知,后面会详细介绍。Redis全量同步连接建立该阶段的主要作用是建立主从节点之间的连接,为全量数据同步做准备。从库会与主库建立连接。从库执行replicaof并发送psync命令,告诉主库即将进行同步。主库确认回复后,主从库开始同步。65师兄:从库如何知道主库的信息并建立连接?在从节点的配置文件中的replicaof配置项中配置好主节点的IP和端口后,从节点就知道自己要向上连接主节点了。从节点内部维护了两个字段,masterhost和masterport,用于存储主节点的IP和端口信息。从库执行replicaof,发送psync命令,表示要进行数据同步。主库收到命令后,根据参数开始复制。该命令包含两个参数,主库的runID和复制进度偏移量。runID:每次Redis实例启动都会自动生成一个唯一的ID。第一次主从复制,master库的runID未知,参数设置为“?”。offset:第一个副本设置为-1,表示第一个副本,记录副本进度的offset。主库收到psync命令后,会使用带有两个参数的FULLRESYNC响应命令:主库runID和主库当前复制进度偏移量,返回给从库。当从库中收到响应时,将记录这两个参数。FULLRESYNC响应表示第一次拷贝使用的全量拷贝,即主库会将当前所有数据拷贝到从库。主库同步数据到从库。第二阶段,master执行bgsave命令生成RDB文件,发送给从库。同时,master库为每个slave创建一个replicationbufferbuffer,用于记录从RDB文件生成开始接收到的所有写入。命令。从库中接收到RDB文件后,保存到磁盘,清除当前数据库中的数据,再将RDB文件数据加载到内存中。向从库发送新的写入命令。第三阶段从从节点加载RDB后,主节点将replicationbufferbuffer中的数据发送给从节点,从节点接收并执行。从节点同步到与主节点相同的状态。65兄:主库在同步数据到从库的过程中能否正常接受请求?主库不会被阻塞。Redis作为唯一一个快而不破的人,动不动就可以挡。RDB文件生成后的写操作,并没有记录到刚才的RDB文件中。为了保证主从库数据的一致性,主库会在内存中使用一个replicationbuffer来记录RDB文件生成后的所有写操作。65兄:为什么从从库接收到RDB文件后需要清空当前数据库?因为从库在开始通过replcaof命令与主库同步之前可能会保存其他数据,防止主从数据相互影响。复制缓冲区到底是什么?在master端创建的一个buffer,存放的数据是master后面三个时期的所有数据写操作。1)master期间的写操作执行bgsave生成RDB;2)master发送rdb到slave网络传输期间的写操作;3)slave加载rdb文件时的写操作,将数据恢复到内存中。Redis无论是与客户端通信还是与从库通信,Redis都会分配一块内存缓冲区用于数据交互。客户端是客户端,从库也是客户端。我们每个客户端连接到Redis后,Redis都会分配一个专有的客户端缓冲区,所有的数据交互都是通过这个缓冲区进行的。Master先把数据写入这个buffer,然后通过网络发送出去,这样就完成了数据交互。不管主从是增量同步还是全量同步,master都会为其分配一个buffer,但是这个buffer是专门用来向从库传播写命令,保证主从数据一致的。我们通常称它为复制缓冲区。replicationbuffer太小导致的问题:replicationbuffer是由client-output-buffer-limitslave设置的。当这个值太小时,主从复制连接会断开。1)当主从复制连接断开时,master会释放连接相关的数据。复制缓冲区中的数据也丢失了,主从之间重新开始复制??过程。2)还有一个更严重的问题。主从复制连接断开,导致在主从上重新执行bgsave和rdb重传操作死循环。当主节点数据量较大,或者主从节点之间的网络延迟较大时,缓冲区的大小可能会超过限制,此时主节点会断开与从节点的连接;这种情况可能会造成Fullcopy->replicationbufferoverflowleadstoconnectioninterrupt->reconnection->fullcopy->replicationbufferbufferoverflowleadstoconnectioninterrupt...循环。具体细节:【topredisheadachesfordevops–replicationbuffer】因此,建议将replicationbuffer的硬/软限制设置为512M。configsetclient-output-buffer-limit"slave5368709125368709120"65哥:主从数据库复制为什么不用AOF?与RDB相比,丢失的数据更少。这是一个很好的问题,原因如下:RDB文件是二进制文件,RDB的网络传输和写入磁盘的IO效率高于AOF。从数据库中恢复数据时,RDB的恢复效率也高于AOF。增量复制65哥:主从库之间网络断了怎么办?断开连接后是否需要完整复制?Redis2.8之前,如果主从库在命令传播过程中出现网络中断,那么从库会再次和主库进行全量拷贝,代价非常大。从Redis2.8开始,断网后,主从库会继续使用增量复制进行同步。增量复制:用于网络中断等情况后的复制。只将中断期间主节点执行的写命令发送给从节点,比全复制效率更高。repl_backlog_buffer断连重连增量复制的秘诀就是repl_backlog_buffer缓冲区。每当master将写命令操作记录在repl_backlog_buffer中,由于内存有限,repl_backlog_buffer是一个定长的循环数组。如果数组已满,它会从头开始覆盖之前的内容。master用master_repl_offset记录自己写入的位置offset,slave用slave_repl_offset记录自己读取的offset。master收到写操作,offset递增。从库继续执行同步写指令后,repl_backlog_buffer中的replicatedoffsetslave_repl_offset也在增加。一般情况下,这两个偏移量基本相等。在断网阶段,主库可能会收到新的写操作命令,所以master_repl_offset会大于slave_repl_offset。repl_backlog_buffer当master和slave断开重连时,slave会先向master发送psync命令,同时将自己的runID和slave_repl_offset发送给master。master只需要将master_repl_offset和slave_repl_offset之间的命令同步到从库即可。增量复制执行流程如下图所示:Redis增量复制65师兄:repl_backlog_buffer太小,还没从库读出就被Master新的写操作覆盖了怎么办?我们必须想办法避免这种情况。将执行完整复制。我们可以调整repl_backlog_size参数来控制缓冲区大小。计算公式:repl_backlog_buffer=second*write_size_per_second秒:从服务器断开连接并重新连接到主服务器所需的平均时间;write_size_per_second:master平均每秒产生的命令数据量(写命令与数据大小之和);例如,如果主服务器平均每秒产生1MB的写入数据,从服务器断开连接后平均需要5秒才能重新连接到主服务器,那么复制积压缓冲区的大小不能小于5MB。为了安全起见,复制积压缓冲区的大小可以设置为2*second*write_size_per_second,这确保可以使用部分重新同步处理大多数断开连接。基于长连接的命令传播65哥:全量同步完成后,如何同步正常的运行过程?当主从库完成全量拷贝后,它们之间会保持一个网络连接,主库会使用这个连接依次接收数据。然后将接收到的命令操作同步到从库。这个过程也称为基于长连接的命令传播。使用长连接的目的是为了避免频繁建立连接带来的开销。在命令传播阶段,主从节点除了发送写命令外,还维护心跳机制:PING和REPLCONFACK。Master->Slave:PING每隔指定的时间,主节点会向从节点发送一个PING命令。这个PING命令的作用主要是让从节点做一个超时判断。Slave->Master:REPLCONFACK在命令传播阶段,从服务器默认每秒向主服务器发送一次命令:REPLCONFACK其中replication_offset是从服务器当前的复制偏移量。发送REPLCONFACK命令对主从服务器有3个作用:检测主从服务器的网络连接状态。min-slaves选项的辅助实现。为了检测命令丢失,从节点发送自己的slave_replication_offset,主节点将其与自己的master_replication_offset进行比较。如果从节点数据丢失,主节点会从repl_backlog_buffer缓冲区中查找并推送丢失的数据。注意offset和repl_backlog_buffer缓冲区不仅可以用于部分复制,还可以用于处理命令丢失等情况;不同的是前者是在断开重连后执行的,而后者是在主从节点未断开时在下面执行的。如何判断是进行全同步还是部分同步?在Redis2.8及之后的版本中,从节点可以发送psync命令来请求数据同步。此时,根据主从节点当前的状态,同步方式可能是全量复制,也可能是部分复制。本文以Redis2.8及之后版本为例。关键是psync的执行:增量和全量复制判断从节点根据当前状态向master发送psync命令:如果从节点没有执行过replicaof,从节点发送psync?-1向主节点发送全量复制请求;如果从节点之前执行过replicaof,就会发送psync,runID是上次复制时保存的主节点的runID,offset是上次复制结束时从节点保存的复制偏移量。主节点根据收到的psync命令和当前服务器状态决定进行全量或部分复制:runID与从节点发送的runID相同,从节点发送的slave_repl_offset后的数据存在于repl_backlog_buffer中buffer,然后回复CONTINUE,表示将进行部分复制,从节点可以等待主节点发送丢失的数据;runID与从节点发送的runID不同,或者从节点发送的slave_repl_offset之后的数据已经不在主节点的repl_backlog_buffer缓冲区中(在队列中被挤出),然后回复从节点FULLRESYNC,表示需要全量复制,其中runID表示主节点当前的runID,offset表示主节点当前的偏移量,从节点保存这两个值以备后用。如果从库与主库断开时间过长,则其在主库repl_backlog_buffer的slave_repl_offset位置的数据已经被覆盖。这时从库和主库之间会进行一次全量拷贝。综上所述,每个从库都会记录自己的slave_repl_offset,每个从库的复制进度不一定相同。与主库重连恢复时,从库会通过psync命令将自己记录的slave_repl_offset发送给主库,主库根据各自的复制情况判断从库是可以进行增量复制还是全量复制从库拷贝的进度。replicationbuffer和repl_backlogreplicationbuffer对应于每个slave,通过configsetclient-output-buffer-limitslave设置。repl_backlog_buffer是一个ringbuffer,整个master进程中只存在一个,所有的slave都是公用的。repl_backlog的大小由repl-backlog-size参数设置,默认大小为1M,其大小可以根据每秒产生的命令,(master执行rdbbgsave)+(master发送rdb给slave)+(slaveloadrdbfile)时间和估计积压缓冲区的大小,repl-backlog-size值不小于这两者的乘积。一般情况下,replicationbuffer是主从库在进行全量复制时,主库上用于连接从库的client的buffer,而repl_backlog_buffer是用来支持从库增量复制的,用来不断的保存写在主库上。用于操作的专用缓冲区。repl_backlog_buffer是专用缓冲区。Redis服务器启动后,开始一直接收写操作命令,所有从库共享。主库和从库会各自记录自己的复制进度,所以不同从库在恢复时,会把自己的复制进度(slave_repl_offset)发送给主库,主库可以独立同步。如图:repl_backlog和repl_buffer的区别4.主从应用问题4.1读写分离问题数据过期问题65师兄:在主从复制的场景下,从节点会删除过期数据吗?这个问题问得好,为了master和slave节点的数据一致性,slave节点不会主动删除数据。我们知道Redis有两种删除策略:懒删除:当客户端查询对应的数据时,Redis判断数据是否过期,过期则删除。定时删除:Redis通过定时任务删除过期数据。65兄:客户端从从节点读取数据会不会读取到过期数据?从Redis3.2开始,从从节点读取数据时,首先判断数据是否过期。如果过期,则不会返回给客户端并删除数据。4.2单机内存大小限制如果Redis单机内存达到10GB,从节点的同步时间在几分钟级别;从节点越多,恢复速度越慢。如果系统的读负载很高,而这期间从节点无法提供服务,会给系统带来很大的压力。如果数据量过大,主节点在全量复制阶段fork+保存RDB文件的时间过长,从节点长时间接收不到数据触发超时。主从节点的数据同步也可能陷入全量复制->超时导致复制中断->重新连接->全量复制->超时导致复制中断……循环。另外,除了master节点的单机内存绝对量不能太大,其占宿主机内存的比例也不能太大:最好只使用50%-65%内存,留出30%~45%的内存给bgsave命令和创建copybuffer等。总结主从复制的作用:AOF和RDB二进制文件保证宕机后数据的快速恢复,尽可能防止数据丢失可能的。但是宕机后就不能提供服务了,所以演化出了主从架构和读写分离。主从复制原理:连接建立阶段、数据同步阶段、命令传播阶段;数据同步阶段分为全量复制和部分复制;在命令传播阶段,主从节点之间有PING和REPLCONFACK命令进行相互心跳检测。主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍然很明显:故障恢复不能自动化;写操作不能负载均衡;存储容量受单机限制;这些问题的解决,需要哨兵和集群的帮助,我会在后面的文章中介绍,欢迎关注。本文转载自微信公众号“代码字节”,可通过以下二维码关注。转载本文请联系码哥字节公众号。