Redis的主从复制
单机的redis情况下,不能满足所有的需求
- 机器故障,当机器发生故障时,可能导致数据丢失,redis将不能提供服务
- 单机性能瓶颈,一台机器的硬件提升是有上限的,不能一直提升,并且一台机器到后面能够提升的空间越来越小,而代价却越来越高
所以,除了纵向提升服务器性能外,可以横向提升服务器规模,部署多个redis服务器,同时也避免单点redis服务器的机器故障,只需要保证这多台redis服务器的数据都是同步的。即使有某台服务器发生故障,也不影响其他服务器提供服务
主节点和从节点都可以进行服务,从节点复制主节点的数据,主节点宕机后,从节点可以提供服务
- 一个master可以用多个slave
- 一个slave只能有一个master
- 数据只能由master流向slave
这种一主多从架构,可以实现高可用,读写分离
建立主从连接
有几种建立连接的方式,分别是主动命令设置和配置文件设置
- slaveof ip port 命令(异步)
- slave of no one 从节点断开连接,但是之前同步的数据不会清零,只是不再同步主节点的数据
从节点向指定ip和端口的主节点发送请求命令,连接建立后就清空从节点数据,开始复制主节点数据。使用命令建立的好处是,不需要重启服务器,一条命令就能马上建立。
配置文件设置
在配置文件中有关于主从复制的相关配置,配置文件设置的优点是适用于集中管理,不过需要重启redis,通常使用配置文件来实现。
- slaveof host port 设置主节点的ip和端口
- slave-read-only yes 从节点只读:yes
slave节点设置为只读,可以防止被修改,避免从库和主库的数据不一致。同时可以实现读写分离。
同步数据
主从建立后,从节点就开始同步主节点数据了,同步数据的方式分为全量复制和部分复制
主从连接第一次建立或者slave节点重启,使用的是全量复制。全量复制会将master节点的所有数据发送给slave节点,是一个重量级的操作。
数据传输过程中,有这几个关键的属性:
- runid:一串40个随机十六进制字符组成的字符串,用于唯一标识当前运行的服务器,是在服务器启动时自动随机生成。主从连接后,master会将自己的runid发给slave存储,用于识别传输身份。
- 复制缓冲区:也叫复制积压缓冲区,是一个FIFO队列,默认大小为1mb,用于存储服务器执行的更新命令。当命令总量超出缓冲容量,会弹出最早的命令。服务器如果开启AOF持久化方式或被设置为主节点,就会创建复制缓冲区。
- offset:是复制缓冲区中的命令偏移量,在缓冲区中,命令以AOF格式的字节存储,通过offset记录了命令的位置。master会记录上次发给所有slave的offset,通过比对slave发过来的offset,找到缓冲区队列中的位置,发送后续的所有命令。
如果master执行shutdown save命令的话,会进行RDB持久化,保存master当前的runid和offset,重启恢复数据后,能够让slave认为还是之前的master
全量复制
全量复制的步骤如下图
上面提到全量复制是一个重量级的操作,主要有这些方面的开销
- master进行bgsave
- 传输RDB文件
- slave清空数据加载RDB
- 可能的AOF
部分复制
redis2.8之后提供了部分复制,在某些情况下,使用更轻量的部分复制,减少开销。不过当部分复制的条件不满足时,仍然会使用全量复制。
比如主从连接因为网络原因丢包,重新建立连接后,判断是否能进行部分复制。
- slave会发送之前存储的master的runid和offset,判断是否是同一台服务器,以及记录的数据位置是否仍在缓冲区队列中
- 如果其中一个不满足,就进行全量复制
- 如果都相同,就忽略
- 如果offset不同,且在缓冲区内有记录,就进行部分复制
心跳机制
在同步命令时的master和slave采取了心跳机制来交换数据。
- **slave心跳,**每隔一秒都会发送REPLCONF ACK {offset},尝试获取最新记录的命令,同时也可以判断master是否在线
- **master心跳,**以一个固定的时间间隔repl-ping-slave-period(默认10秒)去ping从节点,轮询slave是否还在线。master会根据slave发送的命令,判断工作的slave数量和延迟。当数量或延迟不满足要求时,master会强制关闭写功能,暂停数据同步,用来保障数据的稳定性
故障转移
如果服务器发生了故障,我们需要转移故障,让剩下的节点仍然能保证正常工作。
比如slave服务器发生故障,可以将连接故障slave的客户端,转去使用其他的slave,但是需要注意的是:另一个服务器如果突然增加很多客户端,压力也会增大,所以需要合理分配。
如果master服务器发生故障,就需要一个slave服务器转变为master服务器,同时将其他slave服务器连接到这个新的master服务器上,并迁移挂载的客户端。
Redis的哨兵
主从复制如果发生了故障,虽然我们知道了大概的处理方式,但是这个故障转移并不是自动解决的,我们需要监控节点,并且手动转移。或者也可以使用脚本来实现。
但是redis提供了redis sentinel(哨兵)来解决了主从复制中故障转移的功能。sentinel是一个特殊的redis,并不存储数据,主要是去监控节点是否有故障,实现自动转移故障,通知其他哨兵和客户端
一套sentinel可以监控多套master-slave架构
当多个sentinel发现了master有故障,sentinel之间会选举出一个sentinel领导,可以把他看作一个客户端来处理故障。然后从slave中选出一个合适的slave作为new_master,然后通知其他slave和客户端哪个节点是new_master。如果old_master重启后,会将其作为slave节点去连接new_master
哨兵启动:redis-sentinel sentinel-端口.conf
配置文件关键属性:
- port 端口,默认26379
- sentinel monitor 表示该sentinel监视哪一个master,当sentinel认定某个redis节点故障的个数超过quorum,就下线该节点
- sentinel down-after-millseconds 多少时间内master没有响应就认为master故障
- sentinel parallel-syncs 一次允许最多可以有多少同步操作,越多同步速率越快,服务器压力越大
- sentinel failover-timeout 同步超时时间
sentinel不需要配置监控slave节点,只需要通过主节点分析主从关系,就能实现监控slave节点
节点数量应该>=3,且最好是奇数,这是为了保证公平以及能够快速选举出唯一领导者。
监控节点
sentinel最开始会向master发送info命令,再根据当前master的主从关系,去向它的slave发送info,之后每10秒会向master和slave发送一次info指令
- 通过对master进行info命令可以获取到:master信息和状态,slave节点地址
- 然后对slave节点info命令获取到slave状态和属性
新加入的sentinel发现master已经有过sentinel节点监视了,就通过master节点的channel进行发布订阅来加入其他sentinel节点。
每隔2秒每个sentinel节点通过sentinel:hello频道,交互自身信息和其他sentinel节点获取的信息,这样sentinel节点之间可以快速“沟通”,信息同步的速度就很快。
同时每1秒,每个sentinel节点会对其他的sentinel节点和redis节点执行Ping指令,进行心跳检测,判断节点是否在线。
发现故障
根据之前的配置,一个sentinel发现某个master节点响应超时了,就会进行主观下线标记。然后在sentinel之间发送命令:sentinel is-master-down-by-addr询问其他的sentinel节点判断一下是否进行客观下线,并且准备选举。
- 主观下线:S_DOWN,这是一个sentinel节点对该redis节点的“主观”判断,可能由于当前sentinel的网络等原因,和redis节点的连接超时了,所以只有该sentinel个人认为redis节点下线了。
- 客观下线:O_DOWN,当其他sentinel节点对该redis节点达成共识(超过quorum数量的sentinel都认为redis节点下线了)
故障转移
根据之前提到了,我们要从sentinel节点中选举出一个领导者,由领导者来完成故障转移,领导者会从slave节点中选择合适的节点作为master节点,让其他slave节点同步新的master节点
sentinel领导者选举
每个做完主观下线的sentinel节点会发送sentinel is-master-down-by-addr命令,希望成为领导者。
- 此时收到命令的sentinel节点如果没有投票给其他sentinel节点,就会同意这次投票(即每个节点只有一次投票机会,先到先得)。
- 当某个sentinel节点的票数超过半数,并且超过了quorum时,就由该节点作为领导节点来执行故障转移。
- 如果投票中,出现多个领导者就会重新选举,投票轮数+1,保证只有唯一领导者。
选择合适的slave节点
sentinel领导节点会先从在线的slave节点中选择合适的slave节点,将设置为新的master节点,筛选的条件如下:
- 先根据响应速度和最早与master断连的节点中筛选。
- 然后判断优先级,选择slave节点优先级(slave-priority)最高的,通常默认一致,可以选择给硬件配置更好的slave节点设立更高的优先级。存在就返回,不存在就继续筛选。
- 再根据slave节点的offset,选择偏移量最大的节点(复制的数据最完整)。存在就返回,不存在就继续筛选。
- 选择最先启动的slave节点。
故障转移
- 领导者选出最合适的slave节点后,使其执行slave of no one命令成为master
- 让剩下的slave节点执行slaveof new_master ip port,变成new_master的slave
- 从new_master节点同步数据(每次能同步的slave数量和parallel-syncs参数有关)
- 将old_master设置为new_master的slave并监视,再次上线时,就让该节点去同步new_master的数据