1.主从哨兵

1.主从哨兵架构图:

springboot 整合redis一主二从三哨兵 redis一主一从两哨兵_redis 集群

此图为最常见的一主两从结构,一个master主机,两个slave主机。每台主机上都运行着两个进程:

  1. redis-server 服务,处理redis正常的数据操作与响应。master服务可读写,slave服务为只读,当master服务接受到数据修改或写入的命令时,会异步将命令发送到slave上,以此保持master与slave上数据的一致性。
  2. 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)的masterhostmasterport属性里,然后回复 OK,再开始执行真正的复制操作;

redis的复制功能的实现,包括 同步(SYNC/PSYNC) 和 命令传播(command propagate) 两个操作:

  1. 在刚开启主从同步功能或者断线重连时,使用同步命令让slave的数据状态跟master保持一致;
  2. 同步后,若master执行写命令,状态又将不一致,通过命令传播让slave执行同样的命令,使状态保持一致;

同步

  1. SYNC命令:redis2.8之前使用,slave向master发送sync命令,master会执行bgsave命令生成rdb文件,然后把rdb文件发送给slave,slave通过rdb恢复数据,达到与master一致的状态;
  2. 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>

作用

  1. 检测网络通信状态;
  2. 辅助实现 min-slave 选项;
  3. 检测命令丢失: 心跳信息会带有slave的offset,master收到心跳后,可以与自己保存的offset对比,大与收到的offset,说明有命令传播失败;

步骤

  1. slave 收到 SLAVEOF <host> <port>命令,设置 master 的地址端口信息,然后回复OK;
  2. 向 master 建立套接字连接;
  3. 发送 PING 命令;
  4. 身份验证,如果 slave 有 masterauth 配置;
  5. slave 发送自身端口信息;
  6. 进行同步;
  7. 命令传播;

注:master 和 slave 需要互为对方的客户端,因为彼此都要向对方发送命令;

2)哨兵 Sentinel

master-slave方案解决了数据的复制问题,但是 当 master 宕机时,slave 并不会自动切换为新的 master,以继续提供服务,于是,Sentinel System 有了用武之地;

由一个或多个 Sentinel 实例组成的 Sentinel System,可以监视任意多个 master 及其 slave,并行使以下职责:

  1. 监视各服务器运行状态;
  2. 发现异常进行通知;
  3. master 下线后从其 slave 中选举新 master,进行故障转移;
  4. 下线的 master 上线后,将其降级成新 master 的 slave;

先进行哨兵功能的总结

  1. 每个 Sentinel 可监视任意多个服务器,每个服务器也可被任意多个 Sentinel 监视;
  2. 多个监视同一主服务器的 Sentinel 视为一个集群,在被监视主服务器下线后,该集群将选举出一个 Sentinel Leader,由该 leader 对其进行故障转移;

故障转移

  1. 选举 Sentinel leader,用于执行故障转移;
  2. 由 Sentinel leader 从故障主服务器的所有从服务器中选一个做新的主服务器;
  3. 向选出的新 master 发送 slaveof no one命令,然后一次次的发送INFO命令查看服务器的role角色,当变成 master 说明升级成功;
  4. 向其它slave服务器发送slaveof命令,让它们复制新的服务器;
  5. 若下线的主服务器上线,则发送slaveof命令,让其降级为从服务器,复制新的主服务器;

Sentinel节点感知

  1. Sentinel 会向被监视服务器发起两条连接,一条命令连接,一条订阅连接;
  2. 建立订阅频道后,会在被监视服务器上订阅 —sentinel—:hello频道,并以 2S 一次的频率,向该频道发送消息,以向其它监视该服务器的Sentinel宣示自己的存在;
  3. Sentinel 通过接收订阅消息并分析,获知与自己监视同一服务器的其它 Sentinel,并在被监视主服务器下线时,与其它 Sentinel 进行选举选出leader,进行故障转移;

判断下线(主观下线、客观下线)

  1. Sentinel 以每秒一次的频率向其它实例(包括主、从、其它Sentinel)发送PING,并根据回复判断服务器是否在线,当一个实例在指定的次数中不能返回有效回复时,会将这个服务器判断为 主观下线
  2. 判断一个主服务器进入主观下线后,向同样监视这个主服务器的其它Sentinel询问,看是否同意这个主服务器进入主观下线状态;
  3. 当足够多的 Sentinel 判断主服务器进入主观下线后,将这个主服务器判断为 客观下线
  4. 发现主服务器进入客观下线状态后,发起一次故障转移操作;

选举哨兵头领 Sentinel Leader

  1. 所有节点都会广播一条设置信息,要求所有节点调自己为局部leader;
  2. 收到广播的节点,若尚未设置自己的局部leader,则按广播设置其为自己的局部leader,并回复OK,否则回复失败;
  3. 广播的节点按收到的回复,比对是否同一纪元,统计回复OK的数量,若超过半数,则成为全局Leader;
  4. 若一纪元没有一个节点获得半数以上,则休眠一个随机时间,纪元加一,再次选举,直到选出全局leader;

选出新的主服务器

  1. 排除下线或者断线的从服务器;
  2. 排除最近5秒没有回复过 leader Sentinel 的 INFO 命令的从服务器;
  3. 排除与已下线服务器连接断开超过 down-after-millinseconds * 10的从服务器;
  4. 根据从服务器优先级,对剩余从服务器排序,选出优先级最高的;
  5. 若优先级最高的有多个,选出其中复制偏移量最大的;
  6. 若优先级最高的、偏移量最大的,仍有多个,则按 runid ,选最小的;

4.集群搭建

1.集群架构图

springboot 整合redis一主二从三哨兵 redis一主一从两哨兵_redis_02

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将首先进行 握手 :

  1. A 为B 创建一个clusterNode结构,并添加到自己的clusterState.nodes字典中;
  2. A 根据 cluster meet 命令指定的地址和端口,向节点B发送meet消息;
  3. B 节点收到 A 的消息,为节点A创建一个clusterNode结构,并添加到自己的clusterState.nodes结构中,然后向 A 回复一条 PONG 消息;
  4. A 收到 PONG消息可知 B 已收到自己的握手消息,再次发一条确认消息 PING
  5. B 收到 A 发送的 PING消息,握手完成;
  6. 完成握手后,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个槽;