在分布式系统中,基本上所有的存储中间件都支持数据同步/复制功能,主要的原因是为实现高可用,单点宕机的故障,必须需要将数据进行共享,而共享的话,就需要将数据进行复制,对于已经学过的MySQL和Kafka来说,都支持复制功能。而Redis作为一个分布式缓存存储中件间,必然也是支持的。
Redis为什么需要复制
通过前面的学习,我们知道Redis有AOF和RDB的持久化方式,通过重新读RDB和回放日志,可以将系统宕机前的数据进行恢复,尽量减少数据的丢失,但是当Redis只有单机的时候,宕机这个过程中是没办法提供服务的,所以需要通过数据冗余的方式,增加副本,即使其中一台机器宕机,也不会系统整体不可用。
数据副本之间如何保证数据一致性?
Redis提供了主从模式,保证数据副本的一致性,主从库之间采用的是读写分离的方式
- 读操作:主库、从库都可以接受并处理
- 写操作:主库接受写,将修改的数据同步给从库。
为什么采用读写分离模式?
如果主从库都可以接受客户端的写操作,那么同时在不同的实例上进行写,因为是对共享数据进行操作,为保证数据一致性,就需要使用锁、以及实例间数据一致性协议进行完成,而这个是一个巨额的开销,所以不能接受。主从模式可以很好的分离,主可以将写操作同步给从库。
第一次主从数据同步流程
启动多个Redis实例后,通过replcaof 形成主从关系,
- 1,建立连接、协商同步
- 当在从库实例上执行slaveof xxx 6379命令后,从库实例会和主库实例建立连接,然后发送psync ? -1命令,psync表示进行数据同步,而每个Redis实例都有一个runID 因为第一次连接,从实例不知道主实例的RunId,所以就是?,-1表示的是进行第一次复制。
- 主库收到命令后,会使用FULLRESYNC {runId}{offet} 返回,具体含义就是FULLRESYNC代表的是第一次复制采用的全量复制,把当前主库的数据都复制到从库中。runId是主库的runId, offet是当前复制的进度。
- 2,第二阶段主库会生成当前全部数据的RDB文件,发送给从库,从库进行将现有数据删除,然后执行RDB文件。
- 3,因为在生成RDB文件之后,主库也会处理读写请求,会将增量的数据写入到RDB文件,发送给从库。这样就完成了整个流程。
主从从模式
上面我们知道在第一次主从连接的时候,对于主库来说比较耗时的操作就是第一次生成RDB文件和传输RDB文件,前者需要fork出线程进行处理,会影响主库的整体处理速度,而后者传输RDB文件,因为针对的是全量的数据,所以这个操作非常耗费网络带宽。那么有没有其他方式可以缓解主库的这种操作,解决方案就是主从从模式,
主从从模式说白了就是,当有较多的从库实例,不需要从主库建立和主库的数据复制连接,使用一个从库进行数据复制。
主从网络间断,数据复制如何处理
由于在分布式系统中,网络是不可靠的,所以当出现主从网络断开的情况下,数据需要保证复制的一致性,如何做,在2.8版本之前是进行全量复制,而之后采用了增量复制。
具体原理是,当主从库断开连接后,主库会将写命令写入到replication buffer 中,同时也写到repl_backing_buff 缓冲区中。
repl_backing_buff 是一个环形缓冲区,主库会记录自己写的位置,从库会记录自己读的位置。
刚开始的时候,主从库位置是一样的,当主库接受的写命令越来越多就会增加偏移量,maser_repl_offset,而从库读取同步的数据也会进行相应的移动,slave_repl_offset。正常情况下,两个基本相当。
repl_backlog_buffer的使用
主从库连接恢复之后,从库首先会给主库发送psync命令,并把自己的slave_repl_offset发送给主库,主库判断和自己的差距,将没有同步的数据进行同步。
注意点:因为repl_backlog_buffer是一个环形缓冲区,当缓冲区写满之后,主库就会覆盖之前写入的数据,而这部分数据如果从库米有来得急进行同步,那就会可能造成数据丢失。一把来说,我们需要将repl_backlog_size 参数设置的比较大一倍。还有一种方式是通过切片集群来解决,具体等后边在详解。
总结
本篇主要介绍了Redis数据复制,具体的方式全量复制、基于长连接的复制、增量复制三种模式。第一次都是使用全量复制,之后就可以基于长连接模式,如果出现网络断开、抖动等情况,就需要增量复制。虽然复制模式的读写分离可以避免数据之间的不一致,但是主库如果出现故障,没有办法提供服务。写一篇我们介绍下主从故障之后,哨兵机制。