【Redis】Redis Sentinel(哨兵)系统:自动故障恢复与高可用性配置全解

目录


哨兵 (Sentinel)

本章节相关操作不需要记忆!!! 后续⼯作中如果⽤到了能查到即可。

重点理解流程和原理。

Redis 的主从复制模式下,⼀旦主节点由于故障不能提供服务,需要⼈⼯进⾏主从切换,同时⼤量的客⼾端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种⽅案是⽆法接受的,于是 Redis 从 2.8 开始提供了 Redis Sentinel(哨兵)加个来解决这个问题。本章主要内容如下:

  • Redis Sentinel 的概念
  • Redis Sentinel 的部署
  • Redis Sentinel 命令
  • Redis Sentinel 客⼾端
  • Redis Sentinel 实现原理

在复制章节中,我们说了给⼀个⽼师配了⼏个助教。如果⽼师有事请假了⼏天,助教⼜不能上课的话,学⽣们的学习进度就会受到影响。哨兵就是⽤来解决这个问题的。

基本概念

由于对 Redis 的许多概念都有不同的名词解释,所以在介绍 Redis Sentinel 之前,先对⼏个名词概念进⾏必要的说明,如表所⽰。

Redis Sentinel 相关名词解释

redis-sentinel 不负责存储数据,只是对其他的 redis-server 进程起到监控的作用

Redis Sentinel 是 Redis 的⾼可⽤实现⽅案,在实际的⽣产环境中,对提⾼整个系统的⾼可⽤是⾮常有帮助的,本节⾸先整体梳理主从复制模式下故障处理可能产⽣的问题,⽽后引出⾼可⽤的概念,最后重点分析 Redis Sentinel 的基本架构、优势,以及是如何实现⾼可⽤的。

主从复制的问题

Redis 的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作⽤:第⼀,作为主节点的⼀个备份,⼀旦主节点出了故障不可达的情况,从节点可以作为后备 "顶" 上来,并且保证数据尽量不丢失(主从复制表现为最终⼀致性)。第⼆,从节点可以分担主节点上的读压⼒,让主节点只承担写请求的处理,将所有的读请求负载均衡到各个从节点上。但是主从复制模式并不是万能的,它同样遗留下以下⼏个问题:

  1. 主节点发⽣故障时,进⾏主备切换的过程是复杂的,需要完全的⼈⼯参与,导致故障恢复时间⽆法保障。
  2. 主节点可以将读压⼒分散出去,但写压⼒/存储压⼒是⽆法被分担的,还是受到单机的限制。其中第⼀个问题是⾼可⽤问题,即 Redis 哨兵主要解决的问题。第⼆个问题是属于存储分布式的问题,留给 Redis 集群去解决,本章我们集中讨论第⼀个问题。
⼈⼯恢复主节点故障

Redis 主从复制模式下,主节点故障后需要进⾏的⼈⼯工作是⽐较繁琐的,我们在图中⼤致展⽰了整体过程。

Redis 主节点故障后需要进⾏的操作

实际开发中,对于服务器后端开发,监控程序,是非常重要的!

服务器,要求有比较高的可用性,24h运行。但服务器长期运行总有意外发生,也不能全靠人工监控,就用程序盯着服务器的运行状态,这个就是监控程序。这个往往还需要搭配"报警程序"。

  1. 运维⼈员通过监控系统,发现 Redis 主节点故障宕机。

    其实到第二步之前可能看看主节点是否能抢救恢复,不能再下一步

  2. 运维⼈员从所有节点中,选择⼀个(此处选择了 slave 1)执⾏ slaveof no one,使其作为新的主节点。

  3. 运维⼈员让剩余从节点(此处为 slave 2)执⾏ slaveof {newMasterIp} {newMasterPort} 从新主节点开始数据同步。

  4. 更新应⽤⽅(客户端)连接的主节点信息到 {newMasterIp} {newMasterPort}。

  5. 如果原来的主节点恢复,执⾏ slaveof {newMasterIp} {newMasterPort} 让其成为⼀个从节点。上述过程可以看到基本需要⼈⼯介⼊,⽆法被认为架构是⾼可⽤的。⽽这就是 Redis Sentinel 所要做的。

哨兵⾃动恢复主节点故障

当主节点出现故障时,Redis Sentinel 能⾃动完成故障发现和故障转移,并通知应⽤⽅,从⽽实现真正的⾼可⽤。

Redis Sentinel 是⼀个分布式架构,其中包含若⼲个 Sentinel 节点和 Redis 数据节点,每个Sentinel 节点会对数据节点和其余 Sentinel 节点进⾏监控,当它发现节点不可达时,会对节点做下线表⽰。如果下线的是主节点,它还会和其他的 Sentinel 节点进⾏ "协商",当⼤多数 Sentinel 节点对主节点不可达这个结论达成共识之后,它们会在内部 "选举" 出⼀个领导节点来完成⾃动故障转移的⼯作,同时将这个变化实时通知给 Redis 应⽤⽅。整个过程是完全⾃动的,不需要⼈⼯介⼊。整体的架构如图所⽰。

这⾥的分布式架构是指:Redis 数据节点、Sentinel 节点集合、客⼾端分布在多个物理节点上,不要与后边章节介绍的 Redis Cluster 分布式混淆。

Redis Sentinel 架构

这里提供了三个单独的 redis sentinel 进程,并且这三个哨兵进程就会监控现有的 redis master 和 slave。

监控:这些进程之间会建立 tcp 长连接,通过这样的长连接,定期发送心跳包

Redis Sentinel 相⽐于主从复制模式是多了若⼲(建议保持奇数)Sentinel 节点⽤于实现监控数据节点,哨兵节点会定期监控所有节点(包含数据节点和其他哨兵节点)。针对主节点故障的情况,故障转移流程⼤致如下:

  1. 主节点故障,从节点同步连接中断,主从复制停⽌。(从节点挂了无所谓)
  2. 哨兵节点通过定期监控发现主节点出现故障。哨兵节点与其他哨兵节点进⾏协商,达成多数认同主节点故障的共识。这步主要是防⽌该情况:出故障的不是主节点,⽽是发现故障的哨兵节点,该情况经常发⽣于哨兵节点的⽹络被孤⽴的场景下。
  3. 哨兵节点之间使⽤ Raft 算法选举出⼀个领导⻆⾊,由该节点负责后续的故障转移⼯作。
  4. 哨兵领导者开始执⾏故障转移:从节点中选择⼀个作为新主节点;让其他从节点同步新主节点;通知应⽤层(客户端程序)转移到新主节点。

通过上⾯的介绍,可以看出 Redis Sentinel 具有以下⼏个功能:

  • 监控:Sentinel 节点会定期检测 Redis 数据节点、其余哨兵节点是否可达。
  • 自动故障转移:实现从节点晋升(promotion)为主节点并维护后续正确的主从关系。
  • 通知:Sentinel 节点会将故障转移的结果通知给应⽤⽅。

redis 哨兵节点只有一个也是可以的,只是:

  1. 如果只有一个,自身也是很容易出问题的,如果这个哨兵节点挂了,后续 redis 节点也挂了,就无法自动恢复
  2. 出现误判的概率提高了,毕竟网络传输是容易出现抖动、延迟、丢包之类的,这单个哨兵节点出问题,影响就大了

基本原则:在分布式系统中,应该避免使用"单点"。

哨兵节点最好搞奇数个,最少是3个,原因:

如果哨兵节点数目为偶数,可能出现两个相等大小的哨兵组认为自己拥有决策权的情况,从而导致系统不能一致地决定哪个Redis实例应该是主节点。这种情况下,两个哨兵组可能会分别选举出不同的主节点,造成数据不一致和应用错误。

安装部署 (基于 docker)

准备⼯作

1)安装 docker 和 docker-compose

以下部分是独立于这一章节的

以下内容在第58到64节

Docker安装

实战目的

掌握如何安装docker

各版本平台⽀持情况

  • Server版本

  • 桌面版本

Server版本安装
CentOS安装

安装依赖

  1. ⽀持的操作系统
cmd 复制代码
 CentOS 7
 CentOS 8 (stream)
 CentOS 9 (stream)
  1. ⽀持的CPU
cmd 复制代码
 ARM/X86_64

安装Docker

  1. 确认操作系统
cmd 复制代码
CentOS Linux release 7.9.2009 (Core)
Derived from Red Hat Enterprise Linux 7.9 (Source)
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

CentOS Linux release 7.9.2009 (Core)
CentOS Linux release 7.9.2009 (Core)
cpe:/o:centos:centos:7
  1. 确认CPU架构
cmd 复制代码
[root@centos1 ~]# uname -a
[Linux centos1 3.10.0-1160.71.1.el7.x86_64 #1 SMP Tue Jun 28 15:37:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
  1. 卸载旧版本
cmd 复制代码
sudo yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine
  1. 卸载历史版本
cmd 复制代码
# 删除机器上的包
sudo yum remove docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

# 执行卸载
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

# 这个是老师修改后的目录,根据实际情况设置
sudo rm -rf /data/var/lib/docker
sudo rm -rf /etc/docker/daemon.json
  1. 配置仓库
cmd 复制代码
[root@centos1 ~]# ll /etc/yum.repos.d/
total 40
-rw-r--r--. 1 root root 1664 Nov 23 2020 CentOS-Base.repo
-rw-r--r--. 1 root root 1309 Nov 23 2020 CentOS-CR.repo
-rw-r--r--. 1 root root 649 Nov 23 2020 CentOS-Debuginfo.repo
-rw-r--r--. 1 root root 314 Nov 23 2020 CentOS-fasttrack.repo
-rw-r--r--. 1 root root 630 Nov 23 2020 CentOS-Media.repo
-rw-r--r--. 1 root root 1331 Nov 23 2020 CentOS-Sources.repo
-rw-r--r--. 1 root root 8515 Nov 23 2020 CentOS-Vault.repo
-rw-r--r--. 1 root root 616 Nov 23 2020 CentOS-x86_64-kernel.repo

[root@centos1 ~]# sudo yum install -y yum-utils

[root@centos1 ~]# sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Loaded plugins: fastestmirror
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo

[root@centos1 ~]# ll /etc/yum.repos.d/
total 44
-rw-r--r--. 1 root root 1664 Nov 23 2020 CentOS-Base.repo
-rw-r--r--. 1 root root 1309 Nov 23 2020 CentOS-CR.repo
-rw-r--r--. 1 root root 649 Nov 23 2020 CentOS-Debuginfo.repo
-rw-r--r--. 1 root root 314 Nov 23 2020 CentOS-fasttrack.repo
-rw-r--r--. 1 root root 630 Nov 23 2020 CentOS-Media.repo
-rw-r--r--. 1 root root 1331 Nov 23 2020 CentOS-Sources.repo
-rw-r--r--. 1 root root 8515 Nov 23 2020 CentOS-Vault.repo
-rw-r--r--. 1 root root 616 Nov 23 2020 CentOS-x86_64-kernel.repo
-rw-r--r--. 1 root root 1919 Apr 5 07:45 docker-ce.repo

# 配置使⽤国内源
[root@centos1 yum.repos.d]# sed -i 's@//download.docker.com@//mirrors.ustc.edu.cn/docker-ce@g' /etc/yum.repos.d/docker-ce.repo
  1. 安装最新版本
cmd 复制代码
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin 
docker-compose-plugin
  1. 启动docker
cmd 复制代码
#配置加载
sudo systemctl daemon-reload
#启动服务
sudo systemctl start docker
#开启启动
sudo systemctl enable docker
#查看服务状态
sudo systemctl status docker
  1. 检查安装结果查看版本
cmd 复制代码
[root@centos1 ~]# docker version
Client:
  Docker Engine - Community
    Version:           23.0.3
    API version:       1.42
    Go version:        go1.19.7
    Git commit:        3e7cbfd
    Built:             Tue Apr 4 22:04:18 2023
    OS/Arch:           linux/amd64
    Context:           default

Server:
  Docker Engine - Community
    Version:          23.0.3
    API version:      1.42 (minimum version 1.12)
    Go version:       go1.19.7
    Git commit:       59118bf
    Built:            Tue Apr 4 22:02:01 2023
    OS/Arch:          linux/amd64
    Experimental:     false
  containerd:
    Version:          1.6.20
    GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
  runc:
    Version:          1.1.5
    GitCommit:        v1.1.5-0-gf19387a
  docker-init:
    Version:          0.19.0
    GitCommit:        de40ad0
  1. 更详细查看docker 信息
cmd 复制代码
[root@centos1 ~]# docker info
Client:
  Context: default
  Debug Mode: false
  Plugins:
    buildx: Docker Buildx (Docker Inc.)
      Version: v0.10.4
      Path: /usr/libexec/docker/cli-plugins/docker-buildx
    compose: Docker Compose (Docker Inc.)
      Version: v2.17.2
      Path: /usr/libexec/docker/cli-plugins/docker-compose

Server:
  Containers: 0
   Running: 0
   Paused: 0
   Stopped: 0
  Images: 0
  Server Version: 23.0.3
  Storage Driver: overlay2
    Backing Filesystem: xfs
    Supports d_type: true
    Using metacopy: false
    Native Overlay Diff: true
    userxattr: false
  Logging Driver: json-file
  Cgroup Driver: cgroupfs
  Cgroup Version: 1
  Plugins:
    Volume: local
    Network: bridge host ipvlan macvlan null overlay
    Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk s
  Swarm: inactive
  Runtimes: io.containerd.runc.v2 runc
  Default Runtime: runc
  Init Binary: docker-init
  containerd version: 2806fc1057397dbaeefbea0e4e17bddfbd388f38
  runc version: v1.1.5-0-gf19387a
  init version: de40ad0
  Security Options:
    seccomp
      Profile: builtin
  Kernel Version: 3.10.0-1160.71.1.el7.x86_64
  Operating System: CentOS Linux 7 (Core)
  OSType: linux
  Architecture: x86_64
  CPUs: 2
  Total Memory: 1.795GiB
  Name: centos1
  ID: 0b3c79d5-957d-4d04-a856-ac15a2a09db2
  Docker Root Dir: /var/lib/docker
  Debug Mode: false
  Registry: https://index.docker.io/v1/
  Experimental: false
  Insecure Registries:
    127.0.0.0/8
  Live Restore Enabled: false
  1. 执⾏hello-world可以看到Hello from Docker,表⾯docker服务正常
cmd 复制代码
[root@centos1 ~]# sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete 
Digest: sha256:ffb13da98453e0f04d33a6eee5bb8e46ee50d08ebe17735fc0779d0349e889e9
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/
实战经验

Docker镜像源修改

对于使⽤ systemd 的系统(Ubuntu 16.04+、Debian 8+、CentOS 7), 在配置⽂件 /etc/docker/daemon.json 中加⼊:

cmd 复制代码
{
 "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/"]
}

重新启动 dockerd:

sudo systemctl restart docker

参考:https://mirrors.ustc.edu.cn/help/dockerhub.html

Docker⽬录修改

Docker 默认的安装⽬录为/var/lib/docker,这⾥⾯会存放很多很多镜像,所以我们在安装的时候需要考虑这个⽬录的空间,有三种解决⽅案。

(1)将/var/lib/docker挂载到⼀个⼤的磁盘,这种⼀般我们能控制挂载⽬录,像腾讯云这种云⼚商在安装K8s的节点的时候提供了挂载选项,可以直接挂载这个⽬录过去

(2)安装之前挂载⼀个⼤的磁盘,然后创建⼀个软链接到/var/lib/docker,这样就⾃动安装到我们空间⽐较⼤的磁盘了

(3)安装了docker,然后发现忘了配置这个⽬录,我们需要修改docker的配置⽂件

cmd 复制代码
# 假定我们磁盘的⼤的⽬录为 /data
mkdir -p /data/var/lib/docker
# 编辑配置⽂件
vi /etc/docker/daemon.json
# 输⼊下⾯的 JSON
{
    "data-root": "/data/var/lib/docker"
}
# 加载配置
sudo systemctl daemon-reload
# 重启 Docker
sudo systemctl restart docker
# 查看 Docker 状态
sudo systemctl status docker

配置⽂件信息/etc/docker/daemon.json

修改前在/var/lib/docker下

修改后在/data/var/lib/docker下

GUI版本安装(以windows 11为例)

安装依赖

确定开启虚拟化

  • 输⼊win+i,然后输⼊启⽤或者关闭windows功能
  • 选择windows⼦系统和虚拟机平台
  • 重启电脑,完成系统设置
  • 安装WSL2

    • 以管理员权限运⾏ PowerShell

    • 查看版本,如果不是2需要更新到2

    cmd 复制代码
    wsl --status
    • PowerShell 运⾏命令更新wsl到最新版本
    cmd 复制代码
    PS C:\WINDOWS\system32> wsl --update
    • 设置wsl默认版本
    cmd 复制代码
    PS C:\WINDOWS\system32> wsl --set-default-version 2
  • 通过微软应⽤商店安装 Ubuntu 18.04.5

  • 启动安装好的 Ubunt18.04 ,如图表⽰启动成功
cmd 复制代码
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match y
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: zsc
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

zsc@LAPTOP-DIG6CK4H:~$
安装docker

这是个 .exe 应用程序

  • 下载好之后双击 Docker Desktop Installer.exe 开始安装
  • 点击 Docker Desktop 桌⾯快捷⽅式运⾏ Dokcer
  • 确认 Docker 安装成功, 在 PowerShell 中输⼊ docker version 命令确认Client和Server是否启动
cmd 复制代码
PS C:\Users\zsc> docker version 
Client:
 Cloud integration: v1.0.29
 Version: 20.10.21
 API version: 1.41
 Go version: go1.18.7
 Git commit: baeda1f
 Built: Tue Oct 25 18:08:16 2022
 OS/Arch: windows/amd64
 Context: default
 Experimental: true

Server: Docker Desktop 4.15.0 (93002)
 Engine:
  Version: 20.10.21
  API version: 1.41 (minimum version 1.12)
  Go version: go1.18.7
  Git commit: 3056208
  Built: Tue Oct 25 18:00:19 2022
  OS/Arch: linux/amd64
  Experimental: false
 containerd:
  Version: 1.6.10
  GitCommit: 770bd0108c32f3fb5c73ae1264f7e503fe7b2661
 runc:
  Version: 1.1.4
  GitCommit: v1.1.4-0-g5fd4c4d
 docker-init:
  Version: 0.19.0
  GitCommit: de40ad0

以上部分是独立于这一章节的

docker-compose 的安装

cmd 复制代码
# ubuntu
apt install docker-compose
# centos
yum install docker-compose

2)停⽌之前的 redis-server

cmd 复制代码
# 停⽌ redis-server
service redis-server stop

# 停⽌ redis-sentinel 如果已经有的话. 
service redis-sentinel stop

3)使⽤ docker 获取 redis 镜像

cmd 复制代码
docker pull redis:5.0.9

编排 redis 主从节点

  1. 编写 docker-compose.yml

创建 /root/redis/docker-compose.yml , 同时 cd 到 yml 所在⽬录中.

注意:docker 中可以通过容器名字, 作为 ip 地址, 进⾏相互之间的访问

cmd 复制代码
version: '3.7'
services:
  master:
    image: 'redis:5.0.9'
    container_name: redis-master
    restart: always
    command: redis-server --appendonly yes
    ports:
      - 6379:6379
  slave1:
    image: 'redis:5.0.9'
    container_name: redis-slave1
    restart: always
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
      - 6380:6379
  slave2:
    image: 'redis:5.0.9'
    container_name: redis-slave2
    restart: always
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
      - 6381:6379

也可以直接在 windows 上使⽤ vscode 编辑好 yml, 然后在上传到 linux 上.

  1. 启动所有容器
cmd 复制代码
docker-compose up -d

如果启动后发现前⾯的配置有误, 需要重新操作, 使⽤ docker-compose down 即可停⽌并删除刚才创建好的容器

  1. 查看运⾏⽇志
cmd 复制代码
docker-compose logs

上述操作必须保证⼯作⽬录在 yml 的同级⽬录中, 才能⼯作.

  1. 验证

连接主节点

redis-cli -p 6379

cmd 复制代码
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.22.0.3,port=6379,state=online,offset=348,lag=1
slave1:ip=172.22.0.4,port=6379,state=online,offset=348,lag=1
master_replid:a22196b425ab42ddfd222cc5a64d53acffeb3e63
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:348
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:348

连接从节点

redis-cli -p 6380

cmd 复制代码
127.0.0.1:6380> info replication
# Replication
role: slave
master_host: redis-master
master_port: 6379
master_link_status: up
master_last_io_seconds_ago: 10
master_sync_in_progress: 0
slave_repl_offset: 446
slave_priority: 100
slave_read_only: 1
connected_slaves: 0
master_replid: a22196b425ab42ddfd222cc5a64d53acffeb3e63
master_replid2: 0000000000000000000000000000000000000000
master_repl_offset: 446
second_repl_offset: -1
repl_backlog_active: 1
repl_backlog_size: 1048576
repl_backlog_first_byte_offset: 1
repl_backlog_histlen: 446

redis-cli -p 6381

cmd 复制代码
127.0.0.1:6381> info replication
# Replication
role: slave
master_host: redis-master
master_port: 6379
master_link_status: up
master_last_io_seconds_ago: 7
master_sync_in_progress: 0
slave_repl_offset: 516
slave_priority: 100
slave_read_only: 1
connected_slaves: 0
master_replid: a22196b425ab42ddfd222cc5a64d53acffeb3e63
master_replid2: 0000000000000000000000000000000000000000
master_repl_offset: 516
second_repl_offset: -1
repl_backlog_active: 1
repl_backlog_size: 1048576
repl_backlog_first_byte_offset: 1
repl_backlog_histlen: 516

编排 redis-sentinel 节点

也可以把 redis-sentinel 放到和上⾯的 redis 的同⼀个 yml 中进⾏容器编排. 此处分成两组, 主要是为了两⽅⾯:

  • 观察⽇志⽅便
  • 确保 redis 主从节点启动之后才启动 redis-sentinel. 如果先启动 redis-sentinel 的话, 可能触发额外的选举过程, 混淆视听. (不是说先启动哨兵不⾏, ⽽是观察的结果可能存在⼀定随机性)
  1. 编写 docker-compose.yml

创建 /root/redis-sentinel/docker-compose.yml , 同时 cd 到 yml 所在⽬录中.

注意: 每个⽬录中只能存在⼀个 docker-compose.yml ⽂件.

cmd 复制代码
version: '3.7'
services:
  sentinel1:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-1
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel1.conf:/etc/redis/sentinel.conf
    ports:
      - 26379:26379
  sentinel2:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-2
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel2.conf:/etc/redis/sentinel.conf
    ports:
      - 26380:26379
  sentinel3:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-3
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel3.conf:/etc/redis/sentinel.conf
    ports:
      - 26381:26379
networks:
  default:
    external:
      name: redis-data_default

也可以直接在 windows 上使⽤ vscode 编辑好 yml, 然后在上传到 linux 上.

  1. 创建配置⽂件

创建 sentinel1.conf sentinel2.conf sentinel3.conf . 三份⽂件的内容是完全相同的.

都放到 /root/redis-sentinel/ ⽬录中.

cmd 复制代码
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000

理解 sentinel monitor

cmd 复制代码
sentinel monitor 主节点名 主节点ip 主节点端⼝ 法定票数
  • 主节点名, 这个是哨兵内部⾃⼰起的名字.
  • 主节点 ip, 部署 redis-master 的设备 ip. 此处由于是使⽤ docker, 可以直接写 docker 的容器名, 会被⾃动 DNS 成对应的容器 ip
  • 主节点端⼝, 不解释.
  • 法定票数, 哨兵需要判定主节点是否挂了. 但是有的时候可能因为特殊情况, ⽐如主节点仍然⼯作正常, 但是哨兵节点⾃⼰⽹络出问题了, ⽆法访问到主节点了. 此时就可能会使该哨兵节点认为主节点下线, 出现误判. 使⽤投票的⽅式来确定主节点是否真的挂了是更稳妥的做法. 需要多个哨兵都认为主节点挂了, 票数 >= 法定票数 之后, 才会真的认为主节点是挂了.

理解 sentinel down-after-milliseconds

  • 主节点和哨兵之间通过⼼跳包来进⾏沟通. 如果⼼跳包在指定的时间内还没回来, 就视为是节点出现故障.
  1. 启动所有容器
cmd 复制代码
docker-compose up -d

如果启动后发现前⾯的配置有误, 需要重新操作, 使⽤ docker-compose down 即可停⽌并删除刚才创建好的容器.

  1. 查看运⾏⽇志
cmd 复制代码
 docker-compose logs

上述操作必须保证⼯作⽬录在 yml 的同级⽬录中, 才能⼯作

可以看到, 哨兵节点已经通过主节点, 认识到了对应的从节点.

  1. 观察 redis-sentinel 的配置 rewrite

再次打开哨兵的配置⽂件, 发现⽂件内容已经被⾃动修改了

cmd 复制代码
bind 0.0.0.0
port 26379
sentinel myid 4d2d562860b4cdd478e56494a01e5c787246b6aa
sentinel deny-scripts-reconfig yes
# Generated by CONFIG REWRITE
dir "/data"
sentinel monitor redis-master 172.22.0.4 6379 2
sentinel down-after-milliseconds redis-master 1000
sentinel config-epoch redis-master 1
sentinel leader-epoch redis-master 1
sentinel known-replica redis-master 172.22.0.2 6379
sentinel known-replica redis-master 172.22.0.3 6379
sentinel known-sentinel redis-master 172.22.0.7 26379 f718caed536d178f5ea6d1316d
sentinel known-sentinel redis-master 172.22.0.5 26379 2ab6de82279bb77f8397c309d3
sentinel current-epoch 1

# Generated by CONFIG REWRITE 这⾥的内容就是⾃动修改的.

对⽐这三份⽂件, 可以看到配置内容是存在差异的.

重新选举

redis-master 宕机之后

⼿动把 redis-master ⼲掉

cmd 复制代码
docker stop redis-master

观察哨兵的⽇志

可以看到哨兵发现了主节点 sdown, 进⼀步的由于主节点宕机得票达到 3/2 , 达到法定得票, 于是 master 被判定为 odown.

  • 主观下线 (Subjectively Down, SDown):哨兵感知到主节点没⼼跳了。判定为主观下线
  • 客观下线 (Objectively Down, ODown):多个哨兵达成⼀致意⻅,才能认为 master 确实下线了

接下来, 哨兵们挑选出了⼀个新的 master. 在上图中, 是 172.22.0.4:6379 这个节点.

此时, 对于 Redis 来说仍然是可以正常使⽤的.

redis-master 重启之后

⼿动把 redis-master 启动起来

cmd 复制代码
docker start redis-master

观察哨兵⽇志

可以看到刚才新启动的 redis-master 被当成了 slave,也就是失去了最开始主节点的身份,成为了从节点

使⽤ redis-cli 也可以进⼀步的验证这⼀点

cmd 复制代码
127.0.0.1:6379> info replication
# Replication
role: slave
master_host: 172.22.0.4
master_port: 6379
master_link_status: up
master_last_io_seconds_ago: 0
master_sync_in_progress: 0
slave_repl_offset: 324475
slave_priority: 100
slave_read_only: 1
connected_slaves: 0
master_replid: ececc285a2892fba157318c77ebe1409f9c2254e
master_replid2: 0000000000000000000000000000000000000000
master_repl_offset: 324475
second_repl_offset: -1
repl_backlog_active: 1
repl_backlog_size: 1048576
repl_backlog_first_byte_offset: 318295
repl_backlog_histlen: 6181

结论

  • Redis 主节点如果宕机,哨兵会把其中的⼀个从节点,提拔成主节点
  • 当之前的 Redis 主节点重启之后,这个主节点被加⼊到哨兵的监控中,但是只会被作为从节点使⽤

选举原理

假定当前环境如上⽅介绍, 三个哨兵(sentenal1, sentenal2, sentenal3), ⼀个主节点(redis-master), 两个从节点(redis-slave1, redis-slave2).

当主节点出现故障, 就会触发重新⼀系列过程.

主观下线

当 redis-master 宕机,此时 redis-master 和三个哨兵之间的⼼跳包就没有了。此时,站在三个哨兵的⻆度来看,redis-master 出现严重故障。因此三个哨兵均会把 redis-master 判定为主观下线 (SDown)

客观下线

此时,哨兵 sentenal1,sentenal2,sentenal3 均会对主节点故障这件事情进⾏投票。当故障得票数 >= 配置的法定票数之后,

cmd 复制代码
sentinel monitor redis-master 172.22.0.4 6379 2

在这个地⽅配置的 2 , 即为法定票数

此时意味着 redis-master 故障这个事情被做实了。此时触发客观下线 (ODown)

选举出哨兵的 leader

接下来需要哨兵把剩余的 slave 中挑选出⼀个新的 master。这个⼯作不需要所有的哨兵都参与。只需要选出个代表 (称为 leader),由 leader 负责进⾏ slave 升级到 master 的提拔过程

这个选举的过程涉及到 Raft 算法

假定⼀共三个哨兵节点,S1,S2,S3

  1. 每个哨兵节点都给其他所有哨兵节点,发起⼀个 "拉票请求"。(S1 -> S2, S1 -> S3, S2 -> S1, S2 -> S3, S3 -> S1, S3 -> S2)
  2. 收到拉票请求的节点,会回复⼀个 "投票响应"。响应的结果有两种可能,投 or 不投。

⽐如 S1 给 S2 发了个投票请求,S2 就会给 S1 返回投票响应。

到底 S2 是否要投 S1 呢?取决于 S2 是否给别⼈投过票了。(每个哨兵只有⼀票)

如果 S2 没有给别⼈投过票,换⽽⾔之,S1 是第⼀个向 S2 拉票的,那么 S2 就会投 S1,否则则不投。

  1. ⼀轮投票完成之后,发现得票超过半数的节点,⾃动成为 leader

如果出现平票的情况 (S1 投 S2, S2 投 S3, S3 投 S1, 每⼈⼀票), 就重新再投⼀次即可.

这也是为啥建议哨兵节点设置成奇数个的原因. 如果是偶数个, 则增⼤了平票的概率, 带来不必要的开销.

  1. leader 节点负责挑选⼀个 slave 成为新的 master。当其他的 sentenal 发现新的 master 出现了,就说明选举结束了。

简⽽⾔之,Raft 算法的核⼼就是 "先下⼿为强"。谁率先发出了拉票请求,谁就有更⼤的概率成为 leader。

这⾥的决定因素成了 "⽹络延时",⽹络延时本⾝就带有⼀定随机性。

具体选出的哪个节点是 leader,这个不重要,重要的是能选出⼀个节点即可。

假定 鹏哥,杭哥,蛋哥,是三个哨兵节点

汤⽼湿是主节点,若⼲助教⽼师是从节点

有⼀天,汤⽼湿请假了,需要安排⼀个助教⽼师,代替汤⽼湿上课。那么谁负责去执⾏ "提拔助教" 这个事情呢?显然这个事情不需要 鹏哥,杭哥,蛋哥 同时⼲预。

此时⽐如蛋哥先说,"要不我去负责安排这个事情?",鹏哥和杭哥⼀致说 "好",此时蛋哥得票超过半数,蛋哥就成为这次操作的 leader 节点。

leader 挑选出合适的 slave 成为新的 master

挑选规则:

  1. ⽐较优先级。优先级⾼(数值⼩的)的上位。优先级是配置⽂件中的配置项( slave-priority 或者 replica-priority )

  2. ⽐较 replication offset 谁复制的数据多,⾼的上位

  3. ⽐较 run id,谁的 id ⼩,谁上位。(这个是 redis 节点启动的时候随机生成的一串数字,大小是随机的)

蛋哥按照什么标准来提拔助教呢?三个标准~

  1. ⽐较优先级:某个助教是杨校⻓钦定的,那么钦定的⼈直接上位

  2. ⽐较 replication offset:某个助教备课备的最快,⼀共 20 个课件已经备完 18 个了,别的助教才备了 5, 6 个. 那么这个备课快的就直接上位.

  3. ⽐较 run id:⼤家没有钦定,备课速度也⼀样,这个时候就看哪个助教的名字起的好听~⽐如蛋哥觉得,嗯,这个名字我喜欢,就你了~

当某个 slave 节点被指定为 master 之后,

  1. leader 指定该节点执⾏ slave no one,成为 master
  2. leader 指定剩余的 slave 节点,都依附于这个新 master

小结

上述过程,都是 "⽆⼈值守",Redis ⾃动完成的。这样做就解决了主节点宕机之后需要⼈⼯⼲预的问题,提⾼了系统的稳定性和可⽤性。

⼀些注意事项:

  • 哨兵节点不能只有⼀个。否则哨兵节点挂了也会影响系统可⽤性。
  • 哨兵节点最好是奇数个(一般三个够了)。⽅便选举 leader,得票更容易超过半数。
  • 哨兵节点不负责存储数据(哨兵节点就可以使用一些配置不高的机器来配置,但不能搞一个机器部署三个哨兵,这样没有意义,一崩就是三个都崩了)。仍然是 redis 主从节点负责存储。
  • 哨兵 + 主从复制解决的问题是 "提⾼可⽤性",不能解决 "数据极端情况下写丢失" 的问题。
  • 哨兵 + 主从复制不能提⾼数据的存储容量。当我们需要存的数据接近或者超过机器的物理内存,这样的结构就难以胜任了。

为了能存储更多的数据,就引⼊了集群

相关推荐
ketil272 小时前
Ubuntu 安装 redis
redis
王佑辉3 小时前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
Karoku0664 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
gorgor在码农4 小时前
Redis 热key总结
java·redis·热key
想进大厂的小王4 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情4 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
月落.9 小时前
WPF的<ContentControl>控件
wpf
就是有点傻9 小时前
WPF中的依赖属性
开发语言·wpf
wangnaisheng9 小时前
【WPF】把一个Window放在左上角/右上角顶格显示
wpf
WineMonk9 小时前
.NET WPF CommunityToolkit.Mvvm框架
.net·wpf·mvvm