在我之前《redis灵魂拷问:怎样搭建一个哨兵主从集群》搭建的集群主从哨兵集群,有1个主节点和2个从节点环境如下表:
服务 |
角色 |
192.168.59.132:6379 |
主节点 |
192.168.59.141:6379 |
从节点1 |
192.168.59.141:6389 |
从节点2 |
主节点启动成功后,我们启动一个从节点。从节点启动成功后,会向主节点发送一个数据同步的命令,格式如下:
psync $master_runid $slave_offset
注意:runid是主库redis实例的一个唯一标识id,是redis实例创建的时候自动生成的。offset是指从节点的复制偏移量。
从库第一次向主节点发送请求时,并不知道主库的runid,也没有偏移量,因为需要全量同步。这时的命令如下:
psync ? -1
主库收到这个请求后,回复一个下面命令,把自己的runid和offset发送给从库:
FULLRESYNC $master_runid $master_offset
之后主库会fork一个bgsave子进程生成一个RDB快照文件发送给从库,由从库完成加载。
上面就是一个全量同步的流程,我用下面的图表示:
上面的流程图我们只是考虑了RDB文件的全量同步,我们没有考虑RDB同步给从库的这段时间内主库接收到的写请求,我们也没考虑全量同步完成后增量同步。
下面我们聊一聊主从同步过程中的2个重要的缓存区。
replication buffer
上面的主从全量同步过程中,主库是不会阻塞的,这个时候还会继续接收新的写请求。但是从库加载RDB文件完成后怎么处理这段时间内的增量数据呢?
这就用到了replication buffer,redis server会为每一个连接到自己的客户端创建一个replication buffer,用来缓存主库执行的命令。等从库加载完成RDB文件后,主库就会把缓存的命令发送给从库,这样就同步完成了。
但是,如果replication buffer写满了怎么办呢?replication buffer是为每个客户端分配的,如果写满了,无论客户端是普通客户端还是从库,只能断开跟这个客户端的连接了。这样从库全量同步失败,只能再次尝试全量同步。
那怎么避免这种情况呢?我们可以在redis.config文件中配置如下参数:
# # client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds> # # A client is immediately disconnected once the hard limit is reached, or if # the soft limit is reached and remains reached for the specified number of # seconds (continuously). # So for instance if the hard limit is 32 megabytes and the soft limit is # 16 megabytes / 10 seconds, the client will get disconnected immediately # if the size of the output buffers reach 32 megabytes, but will also get # disconnected if the client reaches 16 megabytes and continuously overcomes # the limit for 10 seconds. # # By default normal clients are not limited because they don't receive data # without asking (in a push way), but just after a request, so only # asynchronous clients may create a scenario where data is requested faster # than it can read. # # Instead there is a default limit for pubsub and replica clients, since # subscribers and replicas receive data in a push fashion. # # Both the hard or the soft limit can be disabled by setting them to zero. client-output-buffer-limit slave 32mb 8mb 60
上面参数中的slave表示这个缓冲区是主从复制缓存区,32mb表示缓冲区最大是32mb,8mb 60表示如果60s内写入量超过8mb,缓冲就会溢出。
可见,把缓冲区最大值调大,或者把每秒允许的写入量增加,可以减小replication buffer溢出的概率。当然,我们也可以控制主节点的写命令接收速率。
repl_backlog_buffer
redis从库加载完成RDB文件后,就会继续同步从库的增量写命令。但是如果同步增量写命令的过程中,主从连接断开了,怎么办呢?重新连接后需要再次进行全量同步吗?
不一定,这时候就要依赖repl_backlog_buffer这个缓冲区了。主库的写命令,除了传给从库后,还会写入repl_backlog_buffer,当主从断开后,重新建立连接,从库会发送之前的那个命令:
psync $master_runid $slave_offset
这个时候,主库就会在repl_backlog_buffer中找到offset的位置,把之后的写命令写入replication buffer同步给从库。
这里要注意,主库不会给每个从库分配一个repl_backlog_buffer,而是所有从库共享一个。
repl_backlog_buffer的结构是一个环形区域,结构如下图:
如上图,主库按照顺时针方向写命令,从库建立连接后,还有key3和key4这2个命令没有同步,主库只要把这2个命令同步给从库就行了。主库也会记录一个自己的offset,主库offset和从库offset直接的命令就是需要同步的增量命令,这里就是key3和key4。
但是如果环形缓冲区写满了,继续写入就会覆盖之前的命令,如果从库读取慢,从库发送的offset就找不到了,只能进行一次全量同步。怎样避免repl_backlog_buffer写满呢?
我们可以根据主库写入的速率和主从同步传输的速率差值来适当调大repl_backlog_buffer的配置,比如配置这个值为差值的2倍以上。对于高并发的情况,我们也可以设置的更大,或者采用切片集群来分散写请求。
配置也在redis.conf文件,如下:
# Set the replication backlog size. The backlog is a buffer that accumulates # replica data when replicas are disconnected for some time, so that when a # replica wants to reconnect again, often a full resync is not needed, but a # partial resync is enough, just passing the portion of data the replica # missed while disconnected. # # The bigger the replication backlog, the longer the time the replica can be # disconnected and later be able to perform a partial resynchronization. # # The backlog is only allocated once there is at least a replica connected. # # repl-backlog-size 1mb
总结
1.主从第一次全量同步依赖于RDB文件,主库发送RDB文件给从库,从库清空现有数据并加载RDB文件。
2.主库会为每一个从库建立一个replication buffer,用来发送增量写命令,一旦replication buffer写满了,主从就需要断开,重新进行全量同步。
3.增量数据同步时,为了让主从断开后再次连接后不用全量同步,就需要用到offsetrepl_backlog_buffer,这个缓冲区是所有从库共用的,每个从库记录自己的上次断开的offset,再次连接后发给主库就可以继续接收数据了。
4.offsetrepl_backlog_buffer是一个环形的缓冲区,如果从库断开时间太长导致自己的offset被覆盖了,就只能再次全量同步了。