1.主从哨兵
1.主从哨兵架构图:
此图为最常见的一主两从结构,一个master主机,两个slave主机。每台主机上都运行着两个进程:
- redis-server 服务,处理redis正常的数据操作与响应。master服务可读写,slave服务为只读,当master服务接受到数据修改或写入的命令时,会异步将命令发送到slave上,以此保持master与slave上数据的一致性。
- redis-sentinel 哨兵服务,此服务会监控master和slave服务是否正常运行,当超过半数的哨兵认定master服务挂掉时,会进行选举,将slave服务选举设置为master服务,并恢复集群访问,当旧master节点恢复正常后,可以作为新的slave节点重新加入集群。
主从哨兵集群的优缺点:
- 优点:实现了数据备份,读负载均衡,自动化故障恢复,高可用。
- 缺点:没有伸缩性,数据存储的限制受到单机内存大小的限制,没发通过增加主机来增加存储空间。
所以,此主从哨兵在中小系统中广受使用,因为其简单好用的特性,在数据量不大的情况下,可以很好的提供服务,但是随着互联网企业的不断发展,数据量越来越大,单机存储已经无法满足需求,于是redis官方至3.0版本后推出了新一代的集群方案,redis cluster分布式集群,此集群则在高可用的基础上,解决了伸缩性的问题。
2.环境搭建:
由于哨兵的高可用和确保不是因为哨兵故障导致的master状态误判,所以集群的数量为单数最好,最低集群配置则为1主两从,3哨兵。
即需要3台服务器,每台服务器上运行一个redis-server,一个redis-sentinel
1.master配置
# 允许任意用户连接
bind 0.0.0.0
# 关闭保护模式
protected-mode no
# 开启守护进程
daemonize yes
# 如果是作为缓存服务器,需要注释掉三个数据持久化的选项
# save 900 1
# save 300 10
# save 60 10000
# 设置redis密码,如果要设置密码,则主从的密码要统一一致,这样在故障时切换master才能正常连接,如果不设密码则都不设密码
requirepass 123456
# 设置最大内存限制,避免内存过大造成服务器宕机
maxmemory 2gb
2.salve配置
# 允许任意用户连接
bind 0.0.0.0
# 关闭保护模式
protected-mode no
# 开启守护进程
daemonize yes
# 如果是作为缓存服务器,需要注释掉三个数据持久化的选项
# save 900 1
# save 300 10
# save 60 10000
# 设置redis密码,如果要设置密码,则主从的密码要统一一致,这样在故障时切换master才能正常连接,如果不设密码则都不设密码
requirepass 123456
# 设置最大内存限制,避免内存过大造成服务器宕机
maxmemory 2gb
# 指定matser机的IP和端口
slaveof 192.168.0.1 6379
# master上redis的密码,如果没有设置密码则不需要配置
masterauth 123456
3.哨兵配置
修改sentinel.conf
# 设为后台启动
daemonize yes
# 设置log文件路径,方便出故障时进行排查
logfile "/var/log/redis/sentinel.log"
# 关闭保护模式
protected-mode no
# 设置监控master主机,只需要配置上master的ip和端口
# 后面的2,表示有两台或以上哨兵认定master挂掉了,则认为master挂掉,进行选举切换master
sentinel monitor mymaster 192.168.0.1 6379 2
# 指定master和slave的统一密码
sentinel auth-pass mymaster 123456
3.实现原理
1)主从 master-slave
redis主从模式(master-slave,为保政治正确,已改名master-replica),提供了除持久化外另一种数据的热备功能,也为读写分离提供了途径;
redis主从模式通过复制功能实现,redis提供了SLAVEOF(REPLICAOF)
,让一个服务器(slave)去复制另一个服务器(master);
复制功能的开启: 通过客户端向服务器发送指令:slaveof(replicaof) host port
,或者在slave配置文件中配置 replicaof
选项;
注: slaveof(replicaof) host port
是异步命令,当服务器收到该命令后,会先将host/port保存到服务器状态(redisServer)的masterhost
及masterport
属性里,然后回复 OK,再开始执行真正的复制操作;
redis的复制功能的实现,包括 同步(SYNC/PSYNC) 和 命令传播(command propagate) 两个操作:
- 在刚开启主从同步功能或者断线重连时,使用同步命令让slave的数据状态跟master保持一致;
- 同步后,若master执行写命令,状态又将不一致,通过命令传播让slave执行同样的命令,使状态保持一致;
同步:
- SYNC命令:redis2.8之前使用,slave向master发送
sync
命令,master会执行bgsave
命令生成rdb文件,然后把rdb文件发送给slave,slave通过rdb恢复数据,达到与master一致的状态;- PSYNC命令:因为SYNC命令只能执行全量同步,对于slave断线重连后只需要执行部分重同步就可以达到一致的情况,仍使用SYNC命令有很大的浪费,因为在redis2.8中,提供了
PSYNC
命令来取代SYNC
命令
PSYNC
PSYNC提供了全量同步和部分同步两种模式,全量同步跟 SYNC
命令类似
- 为实现数据的部分重同步,redis使用了一些定义
- master 每发送一个字节,将偏移量+1
- slave 每收到一个字节,将偏移量+1
- 复制积压缓冲区
- master维护的一个 固定长度 的FIFO队列,默认1M;存放了一部分最近传播的写命令,并且为每个字节记录了相应的复制偏移量;
- 服务器支行ID: 每个redis服务器,不论主从,都有自己的支行ID,在启动服务时生成,由40个随机的16进制字符串组成
- 有了以上三个定义,就可以实现部分同步功能了:
- slave 向 master 发送命令
PSYNC <runid> <offset>
,其中 runid 表示上次复制的 master 的运行ID,offset 则是slave保存的复制偏移量;如果是第一次同步,则用?
和-1
表示:PSYNC ? -1
;- master 收到命令后,查检是第首次同步、收到的runid是否是自身的runid、根据偏移量检查缺失的数据是否全部在复制积压缓冲区中,然后会回复:
+FULLRESYNC <runid> <offset>
表示将执行全量同步;+CONTINUE
表示将执行增量同步,slave 只需等待 master 继续将增量数据发过来即可;注:当runid相同且缺失和数据都仍在复制积压缓冲区时才会执行部分同步;
-ERR
表示 master 版本低于2.8,不能识别PSYNC
命令;
心跳检测
在命令传播阶段,slave 会定时向 master 发送心跳信息,默认每秒一次,命令格式: REPLCONF ACK <offset>
作用:
- 检测网络通信状态;
- 辅助实现 min-slave 选项;
- 检测命令丢失: 心跳信息会带有slave的offset,master收到心跳后,可以与自己保存的offset对比,大与收到的offset,说明有命令传播失败;
步骤:
- slave 收到
SLAVEOF <host> <port>
命令,设置 master 的地址端口信息,然后回复OK;- 向 master 建立套接字连接;
- 发送 PING 命令;
- 身份验证,如果 slave 有 masterauth 配置;
- slave 发送自身端口信息;
- 进行同步;
- 命令传播;
注:master 和 slave 需要互为对方的客户端,因为彼此都要向对方发送命令;
2)哨兵 Sentinel
master-slave方案解决了数据的复制问题,但是 当 master 宕机时,slave 并不会自动切换为新的 master,以继续提供服务,于是,Sentinel System 有了用武之地;
由一个或多个 Sentinel 实例组成的 Sentinel System,可以监视任意多个 master 及其 slave,并行使以下职责:
- 监视各服务器运行状态;
- 发现异常进行通知;
- master 下线后从其 slave 中选举新 master,进行故障转移;
- 下线的 master 上线后,将其降级成新 master 的 slave;
先进行哨兵功能的总结:
- 每个 Sentinel 可监视任意多个服务器,每个服务器也可被任意多个 Sentinel 监视;
- 多个监视同一主服务器的 Sentinel 视为一个集群,在被监视主服务器下线后,该集群将选举出一个 Sentinel Leader,由该 leader 对其进行故障转移;
故障转移:
- 选举 Sentinel leader,用于执行故障转移;
- 由 Sentinel leader 从故障主服务器的所有从服务器中选一个做新的主服务器;
- 向选出的新 master 发送
slaveof no one
命令,然后一次次的发送INFO
命令查看服务器的role角色,当变成 master 说明升级成功;- 向其它slave服务器发送
slaveof
命令,让它们复制新的服务器;- 若下线的主服务器上线,则发送
slaveof
命令,让其降级为从服务器,复制新的主服务器;
Sentinel节点感知:
- Sentinel 会向被监视服务器发起两条连接,一条命令连接,一条订阅连接;
- 建立订阅频道后,会在被监视服务器上订阅
—sentinel—:hello
频道,并以 2S 一次的频率,向该频道发送消息,以向其它监视该服务器的Sentinel宣示自己的存在;- Sentinel 通过接收订阅消息并分析,获知与自己监视同一服务器的其它 Sentinel,并在被监视主服务器下线时,与其它 Sentinel 进行选举选出leader,进行故障转移;
判断下线(主观下线、客观下线):
- Sentinel 以每秒一次的频率向其它实例(包括主、从、其它Sentinel)发送PING,并根据回复判断服务器是否在线,当一个实例在指定的次数中不能返回有效回复时,会将这个服务器判断为 主观下线
- 判断一个主服务器进入主观下线后,向同样监视这个主服务器的其它Sentinel询问,看是否同意这个主服务器进入主观下线状态;
- 当足够多的 Sentinel 判断主服务器进入主观下线后,将这个主服务器判断为 客观下线
- 发现主服务器进入客观下线状态后,发起一次故障转移操作;
选举哨兵头领 Sentinel Leader:
- 所有节点都会广播一条设置信息,要求所有节点调自己为局部leader;
- 收到广播的节点,若尚未设置自己的局部leader,则按广播设置其为自己的局部leader,并回复OK,否则回复失败;
- 广播的节点按收到的回复,比对是否同一纪元,统计回复OK的数量,若超过半数,则成为全局Leader;
- 若一纪元没有一个节点获得半数以上,则休眠一个随机时间,纪元加一,再次选举,直到选出全局leader;
选出新的主服务器:
- 排除下线或者断线的从服务器;
- 排除最近5秒没有回复过 leader Sentinel 的 INFO 命令的从服务器;
- 排除与已下线服务器连接断开超过
down-after-millinseconds * 10
的从服务器;- 根据从服务器优先级,对剩余从服务器排序,选出优先级最高的;
- 若优先级最高的有多个,选出其中复制偏移量最大的;
- 若优先级最高的、偏移量最大的,仍有多个,则按
runid
,选最小的;
4.集群搭建
1.集群架构图
2.集群配置
# 集群开关,默认是不开启集群模式
# cluster-enabled yes
#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动
配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系
统中配置文件名称不冲突
# cluster-config-file nodes-6379.conf
#节点互连超时的阀值。集群节点超时毫秒数
# cluster-node-timeout 15000
#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间
了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时
间是否过长。判断方法是:
#比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period
#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10
秒,即如果超过310秒slave将不会尝试进行故障转移
# cluster-replica-validity-factor 10
# master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一
个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移
# cluster-migration-barrier 1
#默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全
部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而
造成很长时间数据不一致
# cluster-require-full-coverage yes
3.实现原理
通过主从与哨兵,redis即可实现高可用,然额,仍然存在一个问题,单台服务器的内存是有限的,不够用怎么办?redis有缓存淘汰机制,可以解决一部分问题,但业务需求是无限的,当不能过期与淘汰的数据大到一台主机不够用时,怎么办呢?SO,跟所有其它分布式系统一样,还需要横向扩展能力,幸好,自redis3.0开始,开始提供集群功能;
启动集群: 当redis 服务器以集群模式启动时,即成为一个 节点 ,默认运行在一个只包含自己的集群中,使用cluster meet <host> <port>
命令,可以让服务器把指定的节点加入到自己所在的集群中,假设向服务器 A 发送命令:cluster meet 127.0.0.1 12345
,假设监听端口12345 的服务器为B,那么节点A和节点B将首先进行 握手 :
- A 为B 创建一个clusterNode结构,并添加到自己的clusterState.nodes字典中;
- A 根据
cluster meet
命令指定的地址和端口,向节点B发送meet消息;- B 节点收到 A 的消息,为节点A创建一个clusterNode结构,并添加到自己的clusterState.nodes结构中,然后向 A 回复一条
PONG
消息;- A 收到
PONG
消息可知 B 已收到自己的握手消息,再次发一条确认消息PING
;- B 收到 A 发送的
PING
消息,握手完成;- 完成握手后,A 会通过Gossip协议向集群内的其它节点发送节点B的消息,其它服务器也会与B进行握手,最终,集群内所有服务器都将感知其它服务器,保存有其它服务的clusterNode结构,并与之建立通信;
槽指派: redis 集群内部,通过分片的方式来保存键值数据,每个分片称之为一个槽(slot),共有0-16383共计16384个槽(2048 * 8); 集群建立后,处于未上线状态,需要进行槽指派后,才上线并开始提供服务;
集群命令执行: 集群中因为数据分片存储,执行命令的过程稍微有一点差异:收到命令后,先对key进行hash映射取得该Key的槽号,然后判断该槽是否归自己处理,若是则执行命令,否则取得负责该槽的节点,返回一个MOVED
错误,并把该节点信息返回,引导客户端向正确的节点请求服务;
- 该槽归自己处理,进行处理;
- 该槽不归自己处理,返回一个
MOVED
错误,并把负责处理该槽的节点信息返回,引导客户端向正确的节点请求服务;- 该槽数据目前正在迁移:
- 首先在自己的库中查找该键,若存在,处理;
- 若不存在,返回ASK错误,并给出新节点信息,引导客户端向新节点请求服务;客户端需要先向新节点发送
asking
命令,再发送正式的命令,否则将会收到一个moved
错误
注:正在被导入的槽和数据,不算,只有导入完成后,才会集群内广播
关于集群的内部结构:
- clusterNode: 表示一个节点
- clusterLink: 表示一个节点的连接信息
- clusterState: 每个节点都保存着一个clusterState,记录了以当前节点为视角,集群目前所处的状态,如是否在线、包含多少节点、当前配置纪元等等;其中两个属性记录了所有槽指派信息:
- clusterState.slots: char数组,长度2048(16384/8),用每一个二进制位表示一个槽是否归当前节点处理,为0表示不归我管;
- clusterState.numslots:当前节点负责处理的槽的数量,即slots数组中位的值为1的数量;
- clusterState.nodes:clusterNode结构数组,长度16384
为什么是16384个槽 由记录槽指派信息的结构可知,其实是2048,redis节点维护一个长度2048的char数组,用数组元素中的每一位表示一个槽,而char的长度为8位,于是共可以存储2048*8=16384个槽;