即使有AOF和RDB,也依然有服务不可用的问题。为啥嘞?假设你只有一台服务器,它宕机了,它在恢复期间,新来的数据请求是不是就无法被提供服务了。
Redis具有高可靠性是指:数据尽量少丢失,和服务尽量少中断。AOF和RDB保证了前者,那么后者Redis的做法是增加副本冗余量,将一份数据保存在多个实例上,这样一台机器挂了,其他的还能继续服务。
多个服务如何保持数据一致呢?
Redis肯定有解决方法,那就是她提供了主从库模式,然后主从库之间读写分离。保证数据副本一致。
读操作:主从库都可以读 写操作:主库先写,然后同步给从库
为啥要读写分离呢? 假设不读写分离,客户端对一个数据操作了三次,每次都落在不同的实例上,这个时候读取是不是就有问题了,这样就可能取到旧的值。 当然如果非要保证这三个实例数据一致也不是不可能,加锁,实例间协商等等一大堆操作,但是这没有必要呀,开销多大。
如果我们读写分离,那我们就不用担心这个问题了,写操作只在主库,直接同步就行了。
那么我们同步是怎么同步的嘞、是一次性还是分批次?主从库连接断了还能保持一致吗? 听我慢慢道来。
1、主从库如何进行第一次同步
多个Redis实例之间相互是通过replicaof形成主从库的关系,我们在实例2上执行
replicaof 实例一的IP 端口号
这条命令时,实例2就成了实例1的从库,并且从实例一上面复制了数据。
先看张图
主要分为三个阶段:
1、建立主从库连接,协商同步。 这一步成功建立连接了,从库会告诉主库即将同步(发送psync命令,包含了主库的runID和复制进度offset),主库确认回复后就开始同步了。 runID,是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设为“?”。 offset,此时设为-1,表示第一次复制。
主库接受到psync命令后,会用fullresync响应命令带上两个参数,返回给从库,从库也会记录下这两个参数,fullresync第一次复制是全量复制。
2、主库将数据给从库,从库收到数据,在本地完成数据加载。 这个过程需要RDB文件,主库先生存RDB文件,接着将文件发给从库,从库收到文件后,先清空当前数据库,然后加载RDB文件。这是为了避免之前数据的影响。
主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以继续接受正常请求。这些新请求如何保持一致性呢?主库会在内存中用专门的replication buffer,记录这些新请求。
3、主库会把上面的新请求再发送给从库,就是当RDB发生完后,就会把此时的replication buffer的修改操作发给从库。
2、主从级联模式分担全量复制时的主库压力
全量复制时主库会有两个操作,生成RDB和传输RDB文件。
如果从库太多,主库就忙于fork子进程生成RDB文件,进行全量同步,这个fork操作会阻塞主线程,从而导致响应变慢。传输RDB也会占用网络带宽,主库的压力较大。
肯定是有解决方式的,主-从-从。
通过“主-从-从”模式将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上。
简单说就是部署主从集群,手动选择一个从库,用于联级其他的从库,然后再选择一些从库,让他们和刚刚的从库建立主从关系,命令还是replicaof
这样一来,这些从库就不会和主库进行交互,只要与联级的从库交互就行。
一旦主从库完成了全量复制,他们之间一直会维护一个网络连接,主库通过这个连接将后续的收到的命令操作再同步给从库。这个过程就是基于长连接的命令传播,避免频繁的建立连接。
听起来好像挺简单,但是有风险点,最常见的就是网络断链或者阻塞,这样主从之间就无法进行命令传播了,也没法保持数据一致性了。
有问题那就肯定有解决方法。
3、主从库之间的网络断了咋办
Redis2.8之前,断了就再进行一次全量复制,这玩意开销大,之后的版本才有增量复制的方式继续同步。
增量就是只把主从库网络连接断了期间主库收到的命令进行同步。
主要的地方就是repl_backlog_buffer这个缓冲区。 它是一个环形的缓存区,主库会记录自己写到的位置,从库会记录自己读到的位置。听到这里差不多知道怎么办了吧。
我再说说,刚开始的时候,主从位置是一样的,主库不断接受新命令,位置一直在偏移,有个偏移量,也就是master_repl_offset,新操作越大,这个值越大。
从库在复制完写操作命令之后,它的位置也开始偏移,也有个偏移量slave_repl_offset,正常情况下,主从的这两个偏移量相等。
主从库恢复连接后,从库给主库发生给连接命令就是psync,同时还有自己的偏移量也会发生给主库,主库呢就来判断两个之间的差距。
如果有新数据,主库就将这两个偏移量之间的数据命令操作同步给从库。
再看个图看下增量复制
注意个点,repl_backlog_buffer这个缓冲区,它是环形的那就可能发生覆盖,如果缓冲区满了,主库还在写这就覆盖了,这个时候从库读取的慢,那就不能保持数据一致性了。
怎么避免呢? 我们调整repl_backlog_size这个参数,和缓冲区空间大小有关。有个计算公式 缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小 实际中我们考虑突发的请求我们会把这个扩大一倍。
举个例子: 主库每秒写2000个操作,每个操作大小2kb,网络每秒传输1000个,就需要把剩下的1000个操作存起来,至少需要2MB的空间,为了应对突发情况,我们会设置成4MB。
这个是动态的,根据实际情况可以设置4倍,8倍等。
总结:因为全量复制比较耗时也无法避免,所以我们一般一个Redis实例的数据库不要太大,