Redis 集群概述

单台 Redis 服务器可能遇到的问题

  • 单个 Redis 服务器会发生 <mark>单点故障</mark>, 并且一台服务器需要处理所有的请求负载,压力较大 (容错性差)
  • 从容量上,单个 Redis 服务器的内存容量有限,就算一台 Redis 服务器内容容量为 256G,也不能将所有内容作为 Redis 存储内容。
    <mark>一般来说,单台Redis最大使用内容不应该超过 20G</mark>

基本描述

  • 高可用 (High Availability):通常来描述一个系统经过专门的设计,从而<mark>减少停工时间</mark>,而保持其服务的高可用。
  • 高并发(High Concurrency):通过设计<mark>保证系统能够同时并行处理很多请求</mark>

    通常指标有:

    • 响应时间(Response Time)
    • 吞吐量(Throught)
    • 每秒查询率 QPS (Query Per Second)
    • 并发用户数

响应来说,牺牲了 一致性

提升系统的并发能力

提升系统并发能力的方式,主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。

# 垂直扩展(升硬件)

垂直扩展:提升单机处理能力。垂直扩展的方式只有两种

  • 增强单机硬件性能,如:
    添加 CPU 核数如 32 核,升级更好的网卡如万兆,升级根号的硬盘如SSD,扩充硬盘容量如 2T,扩充系统内存如 128G

  • 提升单机架构性能,如:
    使用 Cache 来减少 IO 次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间

如果预算不是问题,单机扩展往往最自接,快捷

<mark>但有一个致命的不足,单机性能总是有限的。</mark>

所以分布式架构设计高并发终极解决方案还是水平扩展

# 水平扩展(分压力)

水平扩展:只要增加服务器数量,就能线性扩充系统性能。

水平扩展对系统架构设计是有要求的,难点在于:如何在架构各层进行可水平扩展 的设计

Redis 主从复制

简介

一个 Redis 服务可以有多个该服务的复制品,这个 Redis 服务成为 Master

其他复制称为 Slaves

如图:我们将一个 Redis 服务器作为主库(master),其他三台作为从库(Slave),<mark>主库只负责写数据</mark>,每次有数据库更新的数据同步到它所有的从库,而<mark>从库只负责读数据</mark>。

两个好处:

  • 读写分离:提高服务器的负载能力,可根据读请求的规模自由增加或减少从库的数量
  • 可用性:数据被复制成好几份,一台机器故障,可以快速从其他机器获取数据并回复。

在 Redis 主从模式中,一台主库可以拥有多个从库 ,但是一个从库只能隶属于一个主库。

工作流程

# 建立连接阶段 slaveof(或 replicaof)


启动从服务器

设置 master 的 地址和端口,保存 master 信息

# --port 6380 指定从库服务的 端口号
# --slaveof 127.0.0.1 6379 指定主服务器

/usr/local/redis/bin/redis-server /usr/local/redis/redis.conf --port 6380 --slaveof 127.0.0.1 6379

加上 slaveof 参数启动另一个 Redis 实例作为从库,并且监听 6380 端口


登录从服务器

/usr/local/redis/bin/redis-cli -p 6380 -a root

主从变换

# 不是任何的从
slaveof no one 

# 指定从属的主服务器
slaveof ip地址 端口号

# 数据同步阶段 psync

数据同步阶段 master 说明

  1. 如果master 数据量大,同步阶段应该避免高峰期,避免master 阻塞,影响业务正常执行
  2. <mark>复制缓冲区大小设定不合理,会导致数据溢出。</mark>
    如果进行全量复制时,<mark>部分复制的缓存区被被填满,会出现指令丢失情况</mark>
    <mark>这种情况下,在进行完成复制后,会进行第二次全量复制(如果部分复制的缓存区一直被填满,一直出现指令丢失,会导致slave陷入死循环状态)</mark>

    避免这种情况,需要修改主服务器配置:repl-backlog-size 1mb ,来调整复制缓存区的大小。
    改多少合理?
    计算机性能决定
    master 单机内存占用主机内存的哔哩不应过大,建议使用50%~70%的内存,留下30%~50%内存用于执行 bgsave命令和创建复制缓冲区

数据同步阶段 slave 说明

  • <mark>为了避免 slave 进行全量复制、部分复制时,服务器响应阻塞或数据不同步,建议关闭此期间的对外服务</mark>
    slave-server-stale-data yes|no

  • 数据同步阶段,master发送给slave信息可以理解为 master 是 slave的一个客户端,主动向 slave 发送命令

  • 多个 slave 同时对 master 请求数据同步,master 发送的 RDB 文件增多,会对宽带造成巨大冲击。如果master带宽不足,数据同步需要错峰
    (<mark>后面会讲技术方面的解决方案</mark>)

  • slave 过多时,建议调整拓扑结构时,由一主多从结构变成树状结构,中间的节点即是master,也是slave。

    注意,使用树状结构时,由于层级深度,导致深度越高的 slave 与 最顶层的 master 间数据同步延迟较大,数据一致性差,应谨慎选择

问题解决

  1. PSYNC 失败:-NOMASTERLINK Can’t SYNC while not connected with my master

    127.0.0.1:6380> PSYNC
    Entering replica output mode...  (press Ctrl-C to quit)
    SYNC with master failed: -NOMASTERLINK Can't SYNC while not connected with my master
    

    这个问题,有两个可能性

    • <mark>你的主服务器自定义了密码</mark>
      那么从服务器在连接时要指定主服务器的密码
    1. 主服务器设置成了 slave 模式(从服务器)
      登录客户端,用 slaveof no one 命令改回来

  2. master_link_status:down
    同步不成功,并且在info里面发现,master_link_status:down
    要么是上面的情况,要么是防火墙没开
    打开防火墙端口命令

    # 端口看你自己情况
    firewall-cmd --zone=public --add-port=3679/tcp --permanent
    firewall-cmd --reload
    

# 命令传播阶段

这个阶段就一句话:保证实时数据同步

  • 当 master 数据库状态被修改后,导致主从服务器数据状态不一致,此时需要让主从数据同步到一致的状态,同步的动作成为 <mark>命令传播</mark>
  • master 将接收到的 数据变更命令 发送给 slave,slave 接收 命令 后执行 命令

部分复制(的细节)

  • 命令传播阶段出现了断网现象和处理

    • 网络闪断:忽略
    • 短时间网络中断:部分复制
    • 长时间网络中断:全量复制
  • 部分复制的三个核心要输(下面一个个说)

    1. 服务器的运行 id (run id)
    2. 主服务器的复制积压缓冲区 (也就是前面简称的:复制缓冲区)
    3. 主主从服务器的复制偏移量

1. 服务器运行ID(runid)

  • 概念:<mark>服务器运行 id 是每台服务器每次运行的身份识别码</mark>,一台服务器多次运行可以生成多个运行 id
  • 组成:运行 id 由 40 位字符组成,是一个随机的十六进制字符
    例如:
    1b26e30907309452dede588893477e847cfcedd4
    ffffffffffaaaaaaaaaaeeeeeeeeee1111111111 (神选id 🐶)
  • 作用:<mark>运行 id 被用于在服务器间进行传输,识别身份</mark>
    如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别
  • 实现方法
    运行id <mark>在每台服务器启动时自动生成
    master在首次连接slave时,会将自己的 运行id 发送给slave,slave保存此</mark> 运行id
    (<mark>通过 info Server 命令,可以查看节点的</mark>runid

2、 3 . 复制积压缓冲区 + 偏移量

(如图)master 会将 部分复制的命令 放在 复制缓冲区
当 slave 1 断网,缓冲区会等待玩过重新连接后,才把数据发出

主从复制工作流程

复制缓冲区
概念:<mark>是一个先进先出(FIFO)的队列,用于存储服务器执行过程的命令</mark>
由来:每台服务器启动时,如果开启AOF或者被成为master节点,即创建复制缓冲区
(因此,我们通常不会吧AOF关闭)
内部组成:偏移量+字节值
工作原理

  • 就是通过 offset 判断同的 slave 当前数据传输到什么程度了
  • master 记录已发送的信息对应的 offset
  • slave 记录已接收的信息对应的 offset

# 总结:数据同步+命令传播阶段工作流程(详细)

心跳机制

进入命令传播阶段, master 与 slave 间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线

master 心跳:

  • 指令:PING
  • 周期:由 repl-ping-slave-period 决定,默认10s
  • 作用:判断 slave 是否在线
  • 查询:INFO replication(获取slave最后一次连接时间间隔,lag项维持在0或1视为正常)

    slave 心跳任务
  • 指令:REPLCONF ACK [offset]
  • 周期:1秒(比master相对快)
  • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
  • 作用2:判断master是否在线

心跳阶段注意事项

  • 当 slave 多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作
    如:

    min-slaves-to-write 2 
    min-slaves-max-lag 8
    

    slave 数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步。

  • slave 数量由 slave 发送 REPLCONF ACK 命令做确认

  • slave 延迟由 slave 发送 REPLCONF ACK 命令做确认

# 总结:工作全流程(详细)

部分复制、全量复制看上面

# 主从复制常见问题

1、master 重启后的全量复制

一旦master重启,runid发生改变,会导致全部slave的全量复制。

解决方案
redis内部解决了,不需要我们设置(了解)

  • 在 master 关闭时执行指令 shutdown save ,进行 RDB 持久haunted,将 runid 与 offset 保存到 RDB 文件中。
    (下图,可以通过 redis-check-rdb 进行查看)
  • master 重启后加载 RDB 文件恢复数据

2、频繁的全量复制

(前面提及)

  • 问题现象:网络不佳,出现网络中断,slave 不能提供服务
  • 原因:复制缓冲区过小,断网后 slave 的 offset 越界,触发全量复制
  • 最终结果:slave 反复进行全量复制
  • 解决方案:修改缓冲区大小
    repl-backlog-size
  • 建议设置如下:
    1. 测算 master 到 slave 的重连平均时长 second
    2. 获取 master 平均每秒产生写命令数据总量 write_size_per_second
    3. 最优复制缓冲区空间 = 2 * second * write_sieze_per_second

3、频繁的网络中断 (1)

4、频繁的网络中断(2)

5、数据不一致

哨兵模式(主机“宕机”怎么办)


  • 将宕机的 master 下线
  • 找到一个 slave 作为 master
  • 通知所有的 slave 连接新的 master
  • 启动新的 master 与 slave
  • 全量复制 * N + 部分复制 * N

# 哨兵

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行 <mark>监控</mark>,当出现故障时通过 <mark>投票机制</mark> 选择新的 master 并将所有的 slave 连接到新的 master

作用

  • 监控
    不断的检查 master 和 slave 是否正常运行
    master 存活检测、master 与 slave 运行情况检测

  • 通知(提醒)

  • 自动故障转移

# 启动哨兵模式

配置哨兵

  • 配置 一拖二 的主从结构
  • <mark>配置三个哨兵</mark>(配置相同,端口不同)
    参看 sentinel.conf
  • 启动哨兵
    redis-sentinel sentinel-端口号.conf
    

哨兵配置文件(sentinel.conf)的位置


把配置复制到 /usr/local/redis 目录

cp /opt/redis-5.0.8/sentinel.conf /usr/local/redis/sentinel26379.conf

查看 sentinel.conf 配置

# grep -v "#" 去掉注释
# grep -v "^$" 去掉换行
 cat /opt/redis-5.0.8/sentinel.conf | grep -v "#" | grep -v "^$"

# 端口
port 26379
# 守护模式启动 
daemonize no
# pid 文件位置
pidfile /var/run/redis-sentinel.pid
# 日志文件位置
logfile ""
# 数据保存位置
dir /tmp
# (核心) 语法:sentinel monitor (自定义)名字 masterIP master端口 多少个哨兵
# 最后的 2 是指,当有两个哨兵检测到 master 宕机,就认为 master 宕机
# 通常设置为 哨兵数量/2 + 1 (因此,哨兵通常设置单数个)
sentinel monitor mymaster 127.0.0.1 6379 2
# master 多长时间没响应,认为检测到 master 宕机 (名字与上面自定义名字相同)
# 默认 30秒
sentinel down-after-milliseconds mymaster 30000
# 新master上任后,允许的同时同步数据量
# 如果是 1 ,表示同时只能 1 台slave同步新的master数据
sentinel parallel-syncs mymaster 1
# 同步时间超过多少认为是超时(毫秒)
# 默认 3分钟
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes

修改配置

如果 master 没有自定义密码,那么不需要修改配置

如果 自定义了密码,需要添加:

# 语法:sentinel auth-pass 配置中的master名 master密码
sentinel auth-pass mymaster 1

然后,同样的文件复制两份并修改全部端口
(下面可以用 shell 命令快捷完成)

sed 命令解释

# 同时复制并替换出一份26380配置的配置文件
sed 's/26379/26380/g' sentinel26379.conf >sentinel26380.conf
# 同时复制并替换出一份26381配置的配置文件
sed 's/26379/26381/g' sentinel26379.conf >sentinel26381.conf

启动

先起 master (起了就不用再起)
再起 slave

最后起哨兵

# 没有开后台,需要起三个窗口,不喜欢就开后台
/usr/local/redis/bin/redis-sentinel /usr/local/redis/sentinel26379.conf
/usr/local/redis/bin/redis-sentinel /usr/local/redis/sentinel26380.conf
/usr/local/redis/bin/redis-sentinel /usr/local/redis/sentinel26381.conf

哨兵客户端连接

/usr/local/redis/bin/redis-cli -p 26379

连接之后只能执行 哨兵对应的命令

info

这时,我们再看 哨兵的配置文件,可以发现配置文件中自动添加了 salve 的信息

# 问题 :开了多个哨兵,相互不能识别

哨兵是会相互自动识别的,不识别唯一的可能是ID重复了
<mark>把配置里面默认的 id 注释了就好了</mark>

# 模拟master宕机(日志分析,流程分析)

我们把 master kill 了

# 哨兵工作原理

主从切换

哨兵在进行主从切换过程中经历三个阶段

  • 监控
    同步信息
  • 通知
    保持连通
  • 故障转移
    发现问题
    竞选负责人
    选人新的 master
    新 master 上任,其他 slave 切换 master,原master 作为 slave 故障恢复后连接

下面看每一步的细节


阶段一:监控阶段

用于同步各个节点的状态信息

  • 获取各个 <mark>sentinel 的状态</mark>(是否在线)

  • 获取 <mark>master 的状态</mark>
    各个 slave 的详细信息
    master 属性(runid、role:master)

  • 获取<mark>所有 slave 状态</mark>(根据 master 中的 slave 信息)
    slave 属性(runid、role:slave、master_host、master_port、offset、…)

阶段二:通知阶段

<mark>一个 sentinel 间建立长期信息对等的阶段</mark>

sentinel 间形成“朋友圈”网络,<mark>各自</mark> 检测 master、slave 的工作状态,并把检测到的状态在“朋友圈”中发布

阶段三:故障转移阶段

如果 sentinel 发现无法连通 master 会在内部标记 断开记录 S_DOWN(主观下线:猜测挂了)

在 sentinel 的“朋友圈” 发布 master 断开信息

(如下图)

其他 sentinel 看到 信息会去 “围观” master

把围观结果也发上 sentinel 的 专属“朋友圈”

如果有半数(可自定义)的sentinel 在 “朋友圈” 说 master 挂了,就会把 master 状态表示 从 S_DOWN (主观下线:猜测挂)改为 O_DOWN(客观下线:确认挂了)

(如下图)

sentinel 知道 master 挂了,要选取出新的 master

选举出 新 master 之前。<mark>sentinel 间要先选出一个 “主导者”(主导新master的诞生~)</mark>
因此,<mark>会先先投票选 主导的 sentinel</mark>

<mark>投票机制就是简单的 向其他sentinel 要票,最后票数过半的为主导者</mark> (如果没有过半结果会一直重复投票,因此sentinel要单数!)

(如下图)


sentinel 独裁者决定 新 master 的流程(排除法)

  • 排除不在线的
  • 排除响应慢的
  • 断开与原master断开时间久的(性能可能比较差)
  • 看优先原则
    1. 排除优先级低的
    2. 排除 offset 前的
    3. 排除 runid 大的 (到这步,肯定只剩一个 slave 了,那人就当选master)

(下图)

选定 新 master 后,发送指令

  • 向新的 master 发送 slaveof no one
  • 向其他 slave发送 slaveof 新masterIP 端口

slave:你说你是哨兵,原来是罗马教皇锕。。