为了提高redis的高可用性,可以通过集群来容灾,一般有三种集群方案:
1、主从复制
主从复制模式下,redis分为 一个master 主库 和 多个 slave 从库。
master 主库 可以提供读、写操作。slave 从库 只能提供读操作。master不断的将数据同步给slave。
主从复制的两个关键点: 1、runid 当主从复制的时候,没有利用 ip地址+端口号 来标识,而是采用runid。
runid 是一个 40位的随机UUID,当redis重启后,会重新生成runid,runid可以唯一标识redis的一次运行。
如果采用ip地址+端口号的话,如果master重启的话,对应的数据可能会改变,这个时候从库也要跟随主库的变化。如果按照ip地址+端口号的话,不太好判断master是否重启或者改变了。
2、offset
redis采用了全量同步和增量同步的混合模式。
增量同步就是master将收到的写操作数据保存起来,将对应的写操作同步给slave,slave直接执行对应的写操作就可以了,这样比每次都全量同步效率更高。
offset 就是 增量同步中的写操作数据中的偏移量。
master中维护了一个缓冲池,用来存放写操作数据。
缓冲池是循环使用的,当空间不足的时候,会覆盖之前的数据。
master维护了两个指针,一个小offset和一个大的offset。
小的offset对应着当前缓冲池中保存的最旧的写操作偏移量
大的offset对应着当前最新的写操作偏移量
每个从库都会有一个offset变量,代表着当前从库同步的最新的写操作偏移量,当收到主库传来的写操作后,会将offset加上对应的偏移量。
主从同步流程:
首先说一下psync(runid,offset)命令,这是 从库 向 主库发起同步请求的一个命令,runid对应着主库的id,offset对应着从库请求的同步偏移。
1、当slave启动后,还没有master的runid信息,向master发送psync命令,其中offset = -1,runid = ?
2、master接到fsync命令后,发现offset = -1,说明是首次同步,会通过bgsave生成一个全局备份快照rdb,将master的runid和offset(rdb数据对应的offset)传送给对应的slave,并且会利用缓冲区记录快照之后的所有的写操作。在发送完runid和offset后,将对应的rdb发送给从库。
3、slave将master发来的runid和offset保存起来。
4、slave收到rdb后,将rdb加载到内存中,并保存好对应的offset。到此,全量同步完成。
5、当master接到新的写操作的时候,将对应的写操作执行后,将写操作写入到缓冲区中,增加master的offset。接着写操作和新的offset发送给所有的slave,slave在收到写操作后,也会更新内存和对应的offset。
心跳机制:
1、slave心跳 slave每隔1s,就会向主库发送一个请求,请求中携带了自己的offset信息。 master在收到对应的请求后,会维护slave和其对应的offset,通过其offset和自己的offset比对,就可以判断slave是否master的数据是否保持一致了。由于增量同步数据包可能丢失,offset会不一致,master会将对应的写操作数据发送给从库。
2、master心跳 master会每隔10s向slave发送心跳探测,如果在规定时间内收不到回信,就说明slave下线了,后继不会再像slave发送同步信息。
slave断网重连
slave如果断网了,在重新与master建立连接后,会发送fsync命令,会带上对应的runid和offset。master会检验offset,如果offset对应的数据还在缓冲区中,就将对应的数据发送给slave。如果缓冲区重写了,就发送一个rdb和对应的offset发送给slave。
缓冲区会维护一个最小offst和最大offset,相当于一个循环队列,当缓冲区不够用的时候,会覆盖之前的数据。
总结来说,就是首先做一个全量备份,然后在运行期间,就执行增量备份。
实际部署
主从复制只需要在slave中配置相关信息就可以了。
replicaof 127.0.0.1 6379 # master的ip,port
masterauth 123456 # master的密码
replica-serve-stale-data no # 如果slave无法与master同步,设置成slave不可读,方便监控脚本发现问题
主从复制的优缺点:
优点: 1、 slave可以进行读取操作,减轻了master的压力 2、 slave的同步是异步进行的,不会阻塞master处理客户端的读写请求
缺点: 1、 不具有自动容灾功能,当master宕机后,需要手动更换将master的IP更换为一个slave的IP,然后其他slave的master IP也要重新指定。
2、哨兵模式 Sentinel
哨兵模式是在主从复制的基础上改进的一种集群方案。
哨兵模式中会对master和slave进行监控,当master宕机后,立即进行容灾处理。
哨兵是一个进程,通过sentinel.conf来配置哨兵进程监视的相关信息。 哨兵进程可以与redis放在同一个机器中,也可以单独运行。
哨兵进程会监视 master和master对应的slave,当master宕机的时候,会从slave中选出一个slave作为新的master。
为了防止哨兵进程挂掉,导致整个系统瘫痪,会有多个哨兵进程同时监视master和slave。
哨兵模式工作流程: 1、配置sentinel.conf
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <master ip> <master port> <quorum>
sentinel monitor mymaster 192.168.10.133 6379 1
首先在sentinel.conf中配置哨兵监视的master的信息。
2、启动哨兵服务
./redis-sentinel sentinel.conf
3、哨兵的自动发现
细心的同学有可能会发现,我们在sentinel.conf中并没有配置其他哨兵的信息,只配置了master的信息。
哨兵如何与其他哨兵通信呢?
这就依赖于Redis的pub/sub,也就是发布/订阅机制了。
1、 哨兵首先订阅master中的 sentinel:hello 这个key。 2、 哨兵每隔2s就会把自己的ip地址、端口号、runid等信息通过publish发送到 sentinel:hello 中。
我们简单的说下redis的pub/sub机制。
struct redisServer {
...
//用来存放pub/sub信息
dict *pubsub_channels;
...
}
redisServer中 pubsub_channels 是一个dict对象,dict中存储的是若干个channel对象,channel的key就是哨兵订阅的key,也就是 sentinel:hello ,channel的value就是一个列表,存放的是订阅该key的所有客户端。
当一个客户端订阅了redis 的一个key后,会在redisServer中的pubsub_channels中对应的key的列表中加入一个客户端对象,客户端对象中包括了客户端的IP地址、端口等信息。
当哨兵每隔2s 通过publish 向 sentinel:hello 发送自己的ip+端口+runid信息的时候,redis会在 pubsub_channels 找到 sentinel:hello 对应的 客户端列表,然后遍历这个客户端列表,将ip地址+端口+runid信息挨个发送给对应的客户端。
当客户端收到这些信息的时候,就可以得到其他哨兵的信息了。
4、故障探测 每个哨兵每隔1s 会向 master 发送一个ping探测信息。
如果master正常工作,会返回一个响应,里面包括了slave的信息。
如果master在规定时间内没有回应,那么哨兵就认为 master存在 主观宕机。
为什么是主观宕机呢? 因为有可能是当前哨兵掉线了,或者当前哨兵和master之间的网络不太通畅,所以是主观宕机。
如果一个哨兵认为master客观宕机后,回向所有其他的哨兵节点发送一个SENTINEL is-master-down-by-addr 探测信息,里面记录了自己的ip地址、端口号、runid等信息。
当其他哨兵收到SENTINEL is-master-down-by-addr 探测信息后,会返回自己对于master的探测结果在线 or 宕机 给源哨兵。
如果一个哨兵接收到了超过 quorum 的 其他哨兵的关于master的宕机回应,这个时候哨兵就会判定 master 是一个客观宕机
quorum可以在sentinel.conf中配置。
5、选举哨兵leader
当一个哨兵 判定 master 客观宕机了,就开始进入选取哨兵leader,哨兵leader会进行选择新的master的工作。
为什么要选取一个leader呢?直接选择新的master不就好了吗?
因为各个哨兵都是平等的关系,也就是说没有中心,是一种去中心化的集群。
如果一个哨兵直接选取新的master的话,其他哨兵也有可能选取其他的master,出现重复选取的情况。这一切的原因是哨兵集群是去中心化的。
具体的选取流程如下: 首先,选取信息中有一个epoch字段,代表着选取的迭代次数。因为选取称为leader的条件比较严苛,一般需要一半以上的哨兵支持,具体的由参数majority决定。 majority 可以在 sentinel.conf 中配置
所以经过了好几轮还是没选取出leader。
1、哨兵向所有其他哨兵发送一个选取请求,里面携带了自己的ip地址、端口、runid信息。
2、当其他哨兵收到选取请求的时候,就返回一个回应,代表支持对应的哨兵当leader。 值得注意的是,哨兵只会对第一个选取请求回应,后来的请求不会回应,这样才能保证只有一个leader被选取。
3、当哨兵收到的支持票数大于规定的票数的时候,当前哨兵就成了leader。 哨兵称为leader后,会向其他哨兵发送对应的通知。当其他哨兵收到通知后,会停止发送选票请求。
4、如果在规定期间,没有选取出leader,就会开启下一轮投票。
6、选取新的master
哨兵leader会通过以下优先级来选取slave 1、首先根据slave的优先级 2、其次根据slave的offset,offset越大,说明slave的数据越完整 3、最后根据runid,优先选取runid小的slave
接下来还需要改变配置信息: 1、更改被选中的slave的配置信息,将其设置为master,比如开始写模式等操作。
2、向其他slave发送更改信息,将新的master的ip地址、端口号、runid发送给其他slave
3、向其他哨兵发送更改信息,将新的master的ip地址、端口号、runid发送给其他哨兵
3、cluster
明日待续。。。
|