一、Redis主从复制
主从复制:主节点负责写数据,从节点负责读数据,主节点定期把数据同步到从节点保证数据的一致性
1.1. 主从复制的相关操作
a,配置主从复制方式一、新增redis6380.conf, 加入 slaveof 192.168.152.128 6379, 在6379启动完后再启6380,完成配置;
b,配置主从复制方式二、redis-server --slaveof 192.168.152.128 6379 临时生效
c,查看状态:info replication
d,断开主从复制:在slave节点,执行6380:>slaveof no one
e,断开后再变成主从复制:6380:> slaveof 192.168.152.128 6379
f,数据较重要的节点,主从复制时使用密码验证: requirepass
e,从节点建议用只读模式slave-read-only=yes, 若从节点修改数据,主从数据不一致
h,传输延迟:主从一般部署在不同机器上,复制时存在网络延时问题,redis提供repl-disable-tcp-nodelay参数决定是否关闭TCP_NODELAY,默认为关闭。
参数关闭时:无论大小都会及时发布到从节点,占带宽,适用于主从网络好的场景,
参数启用时:主节点合并所有数据成TCP包节省带宽,默认为40毫秒发一次,取决于内核,主从的同步延迟40毫秒,适用于网络环境复杂或带宽紧张,如跨机房
1.2. Redis主从拓扑
a)一主一从:用于主节点故障转移从节点,当主节点的“写”命令并发高且需要持久化,可以只在从节点开启AOF(主节点不需要),这样即保证了数据的安全性,也避免持久化对主节点的影响
b)一主多从:针对“读”较多的场景,“读”由多个从节点来分担,但节点越多,主节点同步到多节点的次数也越多,影响带宽,也加重主节点的稳定
c)树状主从:一主多从的缺点(主节点推送次数多压力大)可用些方案解决,主节点只推送一次数据到从节点B,再由从节点B推送到C,减轻主节点推送的压力。
1.3. 主从复制原理
了解redis复制原理对日后运维有很大帮助,包括如何规划节点,如何处理节点故障,redis复制过程可分为三个阶段:
- 复制初始化阶段
- 数据同步阶段
- 命令传播阶段
1.3.1.复制初始化阶段
当执行完slaveof masterip port 命令时候,从库根据指明的master节点ip和port向主库发起socket连接,主库收到socket连接之后将连接信息保存,此时连接建立;
当socket连接建立完成以后,从库向主库发送ping命令,以确认主库是否可用,此时的结果返回如果是PONG则代表主库可以用,否则可能出现超时或者主库此时在处理其他任务阻塞那么此时从库将断开socket连接,然后进行重试;
如果主库连接设置了密码,则从库需要设置masterauth参数,此时从库会发送auth命令,命令格式为“auth + 密码”进行密码验证,其中密码为masterauth参数配置的密码,需要注意的是如果主库设置了密码验证,从库未配置masterauth参数则报错,socket连接断开。
当身份验证完成以后,从节点发送自己的监听端口,主库保存其端口信息,此时进入下一个阶段:数据同步阶段。
1.3.2.数据同步阶段
主库和从库都确认对方信息以后,便可开始数据同步,此时从库向主库发送psync命令(需要注意的是redis4.0版本对2.8版本的psync做了优化,后续会进行说明),主库收到该命令后判断是进行增量复制还是全量复制,然后根据策略进行数据的同步,当主库有新的写操作时候,此时进入复制第三阶段:命令传播阶段。
1.3.3.命令传播阶段
当数据同步完成以后,在此后的时间里主从维护着心跳检查来确认对方是否在线,每隔一段时间(默认10秒,通过repl-ping-slave-period参数指定)主节点向从节点发送PING命令判断从节点是否在线,而从节点每秒1次向主节点发送REPLCONF ACK命令,命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量,作用一是汇报自己复制偏移量,主节点会对比复制偏移量向从节点发送未同步的命令,作用二在于判断主节点是否在线,从库接送命令并执行,最终实现与主库数据相同。
1.3.4.乐观复制
redis采用量乐观复制策略,容忍在一定时间内主从数据内容是不同的,但是两者的数据最终会同步。
1.4. redis复制演进
1.4.1. sync&psync1&psync2
从redis2.6到4.0开发人员对其复制流程进行逐步的优化,以下是演进过程:
- redis版本<=2.6<2.8,复制采用sync命令,无论是第一次主从复制还是断线重连进行复制都采用全量复制;
- 2.8<=redis版本<4.0,复制采用psync,从redis2.8开始,redis复制从sync过渡到psync,这一特性主要添加了redis在断线重新时候可使用部分复制;
- redis版本>=4.0,也采用psync,相比与2.8版本的psync优化了增量复制,这里我们称为psync2,2.8版本的psync称为psync1。
以下将分别说明各个版本的复制演进。
1.4.2. sync
在redis2.6以及以前的版本,复制采用sync命令,当一个从库启动后,会向主库发送sync命令,主库收到sync命令后执行bgsave后台保存RDB快照(该过程在上一篇已经详细介绍),同时将保存快照的将快照保存期间接受的写命令保存到缓冲队列。当快照完成以后,主库将快照文件已经缓存的所有命令发送给从库,从库接受到快照文件并载入,再将执行主库发送的命令,也就是上面我们介绍的复制初始化阶段和数据同步阶段,其后就是命令增量同步,最终主库与从库保持数据一直。
当从库在某些情况断线重连(如从库重启、由于网络原因主从连接超时),重复上述过程,进行数据同步。由此可见,redis2.6版本以及2.6以前复制过程全部采用全量复制。
sync虽然解决了数据同步问题,但是在数据量比较大情况下,从库断线从来依然采用全量复制机制,无论是从数据恢复、宽带占用来说,sync所带来的问题还是很多的。于是redis从2.8开始,引入新的命令psync。
1.4.2.psync1
在redis2.8版本,redis引入psync命令来进行主从的数据同步,这里我们称该命令为psync1。psync1实现依赖以下三个关键点:
- offset(复制偏移量)
主库和从库分别各自维护一个复制偏移量(可以使用info replication查看),用于标识自己复制的情况,在主库中代表主节点向从节点传递的字节数,在从库中代表从库同步的字节数。每当主库向从节点发送N个字节数据时,主节点的offset增加N,从库每收到主节点传来的N个字节数据时,从库的offset增加N。因此offset总是不断增大,这也是判断主从数据是否同步的标志,若主从的offset相同则表示数据同步量,不通则表示数据不同步。以下图示分别代表某个时刻两个主从的同步情况(以下是4.0版本截图):
- replication backlog buffer(复制积压缓冲区)
复制积压缓冲区是一个固定长度的FIFO队列,大小由配置参数repl-backlog-size指定,默认大小1MB。需要注意的是该缓冲区由master维护并且有且只有一个,所有slave共享此缓冲区,其作用在于备份最近主库发送给从库的数据。
在主从命令传播阶段,主节点除了将写命令发送给从节点外,还会发送一份到复制积压缓冲区,作为写命令的备份。除了存储最近的写命令,复制积压缓冲区中还存储了每个字节相应的复制偏移量(如下图),由于复制积压缓冲区固定大小先进先出的队列,所以它总是保存的是最近redis执行的命令。
- run_id(服务器运行的唯一ID)
每个redis实例在启动时候,都会随机生成一个长度为40的唯一字符串来标识当前运行的redis节点,查看此id可通过命令info server查看。
当主从复制在初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来,当断线重连时,从节点会将这个runid发送给主节点。主节点根据runid判断能否进行部分复制:
a. 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会更具offset偏移量之后的数据判断是否执行部分复制,如果offset偏移量之后的数据仍然都在复制积压缓冲区里,则执行部分复制,否则执行全量复制;
b.如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的redis节点并不是当前的主节点,只能进行全量复制;
介绍完三个概念以后,接下来就可以介绍redis2.8提供的psync命令实现过程,如下图:
图文说明:
- 如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步);
- 相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC <runid> <offset>命令:其中runid是上一次复制的主服务器的运行ID,而offset则是从服务器当前的复制偏移量,接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作,如何判断已经在介绍runid时进行详细说明。
根据情况,接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种:
- 如果主服务器返回+FULLRESYNC <runid> <offset>回复,那么表示主服务器将与从服务器执行完整重同步操作:其中runid是这个主服务器的运行ID,从服务器会将这个ID保存起来,在下一次发送PSYNC命令时使用;而offset则是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量;
- 如果主服务器返回+CONTINUE回复,那么表示主服务器将与从服务器执行部分同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了;
- 如果主服务器返回-ERR回复,那么表示主服务器的版本低于Redis 2.8,它识别不了PSYNC命令,从服务器将向主服务器发送SYNC命令,并与主服务器执行完整同步操作。
由此可见psync也有不足之处,当从库重启以后runid发生变化,也就意味者从库还是会进行全量复制,而在实际的生产中进行从库的维护很多时候会进行重启,而正是有由于全量同步需要主库执行快照,以及数据传输会带不小的影响。因此在4.0版本,psync命令做了改进,以下说明。
1.4.3.psync2
redis4.0新版本除了增加混合持久化,还优化了psync(以下称psync2)并实现即使redis实例重启的情况下也能实现部分同步,下面主要介绍psync2实现过程。psync2在psync1基础上新增两个复制id(可使用info replication 查看如下图):
master_replid: 复制id1(后文简称:replid1),一个长度为41个字节(40个随机串+’0’)的字符串,每个redis实例都有,和runid没有直接关联,但和runid生成规则相同。当实例变为从实例后,自己的replid1会被主实例的replid1覆盖。
master_replid2:复制id2(后文简称:replid2),默认初始化为全0,用于存储上次主实例的replid1。
在4.0之前的版本,redis复制信息完全丢失,所以每个实例重启后只能进行全量复制,到了4.0版本,任然可以使用部分同步,其实现过程:
第一步:存储复制信息
redis在关闭时,通过shutdown save,都会调用rdbSaveInfoAuxFields函数,把当前实例的repl-id和repl-offset保存到RDB文件中,当前的RDB存储的数据内容和复制信息是一致性的可通过redis-check-rdb命令查看。
第二步:重启后加载RDB文件中的复制信息
redis加载RDB文件,会专门处理文件中辅助字段(AUX fields)信息,把其中repl_id和repl_offset加载到实例中,分别赋给master_replid和master_repl_offset两个变量值,特别注意当从库开启了AOF持久化,redis加载顺序发生变化优先加载AOF文件,但是由于aof文件中没有复制信息,所以导致重启后从实例依旧使用全量复制!
第三步:向主库上报复制信息,判断是否进行部分同步
从实例向主库上报master_replid和master_repl_offset+1;从实例同时满足以下两条件,就可以部分重新同步,否则执行全量同步:
- 从实例上报master_replid串,与主实例的master_replid1或replid2有一个相等,用于判断主从未发生改变;
- 从实例上报的master_repl_offset+1字节,还存在于主实例的复制积压缓冲区中,用于判断从库丢失部分是否在复制缓冲区中;
psync2除了解决redis重启使用部分同步外,还为解决在主库故障时候从库切换为主库时候使用部分同步机制。redis从库默认开启复制积压缓冲区功能,以便从库故障切换变化master后,其他落后该从库可以从缓冲区中获取缺少的命令。该过程的实现通过两组replid、offset替换原来的master runid和offset变量实现:
第一组:master_replid和master_repl_offset:如果redis是主实例,则表示为自己的replid和复制偏移量; 如果redis是从实例,则表示为自己主实例的replid1和同步主实例的复制偏移量。
第二组:master_replid2和second_repl_offset:无论主从,都表示自己上次主实例repid1和复制偏移量;用于兄弟实例或级联复制,主库故障切换psync。
判断是否使用部分复制条件:如果从库提供的master_replid与master的replid不同,且与master的replid2不同,或同步速度快于master; 就必须进行全量复制,否则执行部分复制。
以下常见的主从切换都可以使用部分复制:
- 一主一从发生切换,A->B 切换变成 B->A ;
- 一主多从发生切换,兄弟节点变成父子节点时;
- 级别复制发生切换, A->B->C 切换变成 B->C->A;
用一句redis开发者话来说psync2,尽管它不是非常完美,但是已经非常适用。
1.5. 主从的缺点
a)主从复制,若主节点出现问题,则不能提供服务,需要人工修改配置将从变主
b)主从复制主节点的写能力单机,能力有限
c)单机节点的存储能力也有限
1.6.主从故障如何故障转移
a)主节点(master)故障,从节点slave-1端执行 slaveof no one后变成新主节点;
b)其它的节点成为新主节点的从节点,并从新节点复制数据;
c)需要人工干预,无法实现高可用。
二、Redis哨兵机制(Sentinel)
2.1. 为什么要有哨兵机制?
哨兵机制的出现是为了解决主从复制的缺点的
2.2. 哨兵机制(sentinel)的高可用
原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
其实整个过程只需要一个哨兵节点来完成,首先使用Raft算法(选举算法https://www.jianshu.com/p/8ec1967d1de3)实现选举机制,选出一个哨兵节点来完成转移和通知。
2.3. 哨兵的定时监控任务
任务1:每个哨兵节点每10秒会向主节点和从节点发送info命令获取最拓扑结构图,哨兵配置时只要配置对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有新的从节点加入时可以马上感知到
任务2:每个哨兵节点每隔2秒会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其它哨兵节点的信息及对主节点的判断,其实就是通过消息publish和subscribe来完成的
任务3:每隔1秒每个哨兵会向主节点、从节点及其余哨兵节点发送一次ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据
客观下线:当主观下线的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,当超过quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线
2.4. 领导者哨兵选举流程
a)每个在线的哨兵节点都可以成为领导者,当它确认(比如哨兵3)主节点下线时,会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移;
b)当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;
c)如果哨兵3发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举…………
2.5. 故障转移机制
a)由Sentinel节点定期监控发现主节点是否出现了故障
sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了
b) 当主节点出现故障,此时3个Sentinel节点共同选举了Sentinel3节点为领导,负载处理主节点的故障转移 。
选举的过程使用了Raft算法,过程如下:
- 发现主数据库客观下线的哨兵节点(下面称之为A),会向其他哨兵节点发送命令,要求对方选自己为领头哨兵
- 若目标哨兵节点没有投过票,则会同意A节点为领头哨兵
- 若A节点发现超过半数,且超过quorum参数值的哨兵节点选举自己为领头哨兵,则A就成功成为领头哨兵
- 当多个哨兵同时参与竞选,有可能出现没有任何节点当选的可能,这时每个参选节点都将等待一个随机事件重新发起竞选请求,进行下一轮选举,直到选举出领头哨兵为止。
c) 由Sentinel3领导者节点执行故障转移,过程和主从复制一样,但是自动执行
流程:
- 将slave-1脱离原从节点,升级主节点
选主的依据如下:
过滤掉不健康的(下线或断线),没有回复过哨兵ping响应的从节点
a.选择一个优先级最高的从数据库作为新的主数据,优先级可以通过slave-priority选项设置
b.在优先级相同的情况下,复制的命令偏移量越大(数据完整性越好),越优先
c.如果以上条件都一样,则选择运行ID最小的从数据库
将从节点slave-2指向新的主节点
领头哨兵向该从数据库发送SLAVEOF NO ONE命令,将其升格为主数据库。通知客户端主节点已更换
领头哨兵向其他从数据库发送SLAVEOF命令,使得它们作为新主数据库的从数据库。将原主节点(oldMaster)变成从节点,指向新的主节点
领头哨兵更新内部记录,将停止服务的旧主数据库作为新主数据库的从数据库,以便其恢复后能够按照新的主从关系继续提供服务。
d) 故障转移后的redis sentinel的拓扑结构图