目录
一、哨兵模式
1.1、为何引入哨兵模式
1.2、Redis Sentinel 分布式架构
1.2.1、概述
1.2.2、工作原理(redis 哨兵的核心功能)
1. 监控:
2. 自动故障转移:
3. 通知
1.2.3、问题:哨兵结点只有一个可以么?
1.3、使用 Docker 和 DockerCompose 模拟部署哨兵模式
1.3.1、前言
1.3.2、准备工作
a)首先要安装好 docker 和 docker-compose.
b)停止之前的 redis 服务器
c)使用 docker 获取 redis 镜像
1.2.3、基于 docker 搭建 redis 哨兵环境
a)分配文件目录结构
b)编写 yml 配置文件
c)编写哨兵结点的三个 conf 配置文件
d)使用 docker-compose 启动 数据结点
e)验证
f)使用 docker-compose 启动 redis-sentinel 结点
日志里为什么全都是报错信息呢?都是什么意思?(重!)
解决方案:
1.2.4、展现哨兵机制
1.2.5、哨兵重新选取主节点的流程
1.2.6、缺陷
一、哨兵模式
1.1、为何引入哨兵模式
实际的开发中,对于服务器后端开发,监控程序,是非常重要的!服务器一般要有比较高的可用性,7 * 24 小时运行,服务器长期运行,总会有一些 “意外” ,具体啥时候出现意外,也不能全靠人工来盯着服务器,所以在早期的主从结构中,会写一个程序来盯着服务器的运行状态.
这个程序需要干两件事:
- 监控程序:用来发现服务器运行时出现的异常状态.
- 搭配 “报警程序” :通过 短信/电话/邮件/微信/钉钉... 等形式给程序员报警,告诉程序员说,这个服务器程序出问题了!
Ps:互联网公司的程序员,尤其是大厂,公司都会明确要求,程序员手机要 24 小时开机,并且随时关注,报警不仅仅是给这一个程序员,还会给程序员的领导,还有领导的领导~ 关键时候错过领导的电话,可能就会有比较负面的评价,升职加薪就要往后了~
程序员如何恢复的?
- 先看看主节点还能不能抢救,好不好抢救.
- 如果主节点这边出事的原因很难解决,就需要挑一个从节点,设置为新的主节点
- 具体的,把选中的从节点,通过 slaveof no one,自立山头.
- 其他的从节点,通过修改 slaveof,将 ip 和 port 改为新上任的主节点.
- 告知客户端(修改客户端配置),让客户端能够连接新的主节点,用来完成修改数据的操作.
- 最后,如果之前挂了的主节点修好了,就可以将其作为一个新的从节点,加入到这个机器当中.
Ps:
1.只要是涉及到人工干预,不说繁琐,至少是很烦人的~
2.另外,这个操作过程一旦出错了,就可能让问题更加严重。
3.通过人工干预的做法,就算程序员第一时间看到了报警信息,第一时间处理,也至少需要半个小时以上的恢复时间,也就是说,这半个小时里,整个 redis 就一直不能写?显然不合适。
为了提高 “可用性” 引入以下架构模式~
1.2、Redis Sentinel 分布式架构
1.2.1、概述
Redis Sentinel 是⼀个分布式架构,其中包含若干个 Sentinel 节点(哨兵)和 Redis 数据节点,这两种结点之间的哨兵机制,是通过独立的进程来体现的,和 redis-server 是不同进程!redis-sentinel 不负责存储数据,只是对其他的 redis-server 进程起到监控的效果.
1.2.2、工作原理(redis 哨兵的核心功能)
1. 监控:
a)redis sentinel 进程,会监控现有的主从结点(这里的监控就是指,sentinel 进程会和 主从结构进程 之间建立 tcp 长连接,通过这样的长连接,定期发送心跳包,看主节点是否挂了,如果 心跳包 没有如果如约而至,就说明 redis 主节点挂了).
b)如果是从节点挂了,其实没关系,如果是主节点挂了,哨兵就要发挥作用了,此时一个哨兵发现了主节点挂了,还不够,需要多个哨兵来认同这件事,主要防止出现误判(网络抖动,导致心跳包丢失).
c)如果主节点确实挂了,这些哨兵结点中,就会选举出一个 leader,由这个 leader 负责从现有的从节点中,挑选出一个新的 主节点.
2. 自动故障转移:
d) 挑选出新的主节点之后,哨兵结点就会自动控制被选中的结点,执行 slaveof no one,让他先自立山头,然后再控制其他从节点,修改 slaveof 到新的主节点上.
3. 通知
e)哨兵结点会自动通知客户端程序,告知新的主节点是谁,后续,客户端再进行写操作,就会针对新的主节点进行操作了.
Ps:以上这三大点,也是 redis 哨兵的核心功能.(这里只是粗略的讲了以下 “哨兵重新选取主节点的流程” ,后面会详细讲!这是面试经常会问的问题!)
1.2.3、问题:哨兵结点只有一个可以么?
redis 哨兵结点,只有一个也是可以的.
但值得注意的是,再分布式系统中,应该避免 “单点” 问题(1.一个服务器挂了,整个就服务就瘫痪了;2.并发量有限).
在 redis 中可能会导致以下问题:
- 一个哨兵结点,自身也是容易出现问题的,万一这一个哨兵结点挂了,后续 redis 结点挂了,就无法进行自动恢复了.
- 出现误判的概率也比较高,因为网络传输数据是容易出现抖动、延迟、丢包等问题的.
Ps:哨兵结点,最好搞奇数个,最少也要有 3 个
1.3、使用 Docker 和 DockerCompose 模拟部署哨兵模式
1.3.1、前言
这里我们将以下图为例,通过 Docker 和 DockerCompose 部署和编排 3 个哨兵和 1 个主 2 个从结点.
案例说,这 6 个结点是要部署到 6 个不同的服务器主机上的.
但我这里只有一个云服务器(实际的工作中,上述结点放在一个云服务器上事没有意义的,这么做也是没办法,经济有限), 如果直接部署,就需要小心避开这些冲突:端口号、配置文件、数据文件....
使用 docker 就可以有效解决上述问题~
虚拟机,就是在电脑上通过软件模拟出另外一些硬件(构造了一个台虚拟电脑),也就是说,通过虚拟机这样的软件,就可以使用一个计算机,来模拟出多个电脑的情况.
但是虚拟机有个很大的问题:很吃配置,这个事情对于我的云服务器来说,压力山大~
docker 可以认为是一个 “轻量级” 的虚拟机(现在后端开发很流行这个组件),既不吃很多的硬件资源,又可以起到虚拟机这样的隔离环境的效果(即使你云服务器配置很低,也能构造出好几个这样的虚拟环境).
1.3.2、准备工作
a)首先要安装好 docker 和 docker-compose.
这里我之前有出过专门的文章,可以去翻翻.
b)停止之前的 redis 服务器
- 如果是通过 redis-server 启动服务器,就必须搭配 kill 命令来停止.
- 如果是通过 service redis-server start 启动服务器,必须搭配 service redis-server stop 来停止.
- 如果使用 kill 命令停止 service redis-server start ,这个 redis-server 进程会自动启动.
c)使用 docker 获取 redis 镜像
通过以下命令,就可以从 docker hb 上拉取 redis 5.0.9 的镜像
docker pull redis:5.0.9
1.2.3、基于 docker 搭建 redis 哨兵环境
Ps:此处涉及到多个 redis server 和多个 redis 哨兵结点,每一个 redis server 和每一个 redis 哨兵结点都是作为一个单独的容器(按照案例,这里总共要部署 6 个容器),为了方便部署,使用 docker-compose 进行容器编排.
a)分配文件目录结构
由于 redis 哨兵结点是单独的 redis 服务器进程,因此之后对于哨兵和主从结点的 yml 配置文件要分开!!!
虽然也可以使用一个 yml 文件 直接启动 6 个容器,但是如果 6 个容器同时启动,可能是 哨兵 先启动完成,数据节点后启动完成,哨兵可能就会先认为是数据结点挂了!虽然从大体上来看不影响,但是会影响到我们对执行日志的观察.
b)编写 yml 配置文件
这里分别在 redis-data 目录 和 redis-sentinel 目录下创建一个 yml 配置文件(注意,文件名必须是 docker-compose.yml)
分别用来描述 3 个数据结点和 3 个哨兵结点 , 具体要创建哪些容器,每个容器运行的各种参数,描述清除。
后续通过一个简单的命令,就可以批量进行 启动/停止 这些容器了.
Ps:下来讲到的配置文件不需要记,只需要了解其中一些参数的意思即可,实际工作中不可能让你去写,只会让你去 copy 现有的,对关键参数进行修改.
数据结点配置文件如下:
version: '3.7'
services:master:image: 'redis:5.0.9'container_name: redis-masterrestart: alwayscommand: redis-server --appendonly yesports:- 6379:6379slave1:image: 'redis:5.0.9'container_name: redis-slave1restart: alwayscommand: redis-server --appendonly yes --slaveof redis-master 6379ports:- 6380:6379slave2:image: 'redis:5.0.9'container_name: redis-slave2restart: alwayscommand: redis-server --appendonly yes --slaveof redis-master 6379ports:- 6381:6379
对一些关键参数的解释:
- image:当前容器是基于哪个镜像创建的(镜像就相当于模板,启动镜像就可以构建出容器).
- container_name:自定义容器的名字。docker 中可以通过容器名字, 作为 ip 地址, 进⾏相互之间的访问.
- restart:容器遇到一些异常情况终止了,是否重启(always 表示一旦遇到异常情况,立刻重启 ).
- command:用什么命令来启动服务.
- ports:这里的规则是 [宿主机端口] [容器内部端口]。上述配置文件中,要启动三个容器(master、slave1、slave2),这三个容器的内部端口号都是自成一个小天地,也就是说,容器 1 的 6379 和 容器 2 的 6379 之间是不会有冲突的(两个容器可以视为两个主机);有的时候,希望容器外访问容器内部的端口,需要进行端口映射,把容器内吨端口映射到宿主机上,后续访问宿主机这个端口,就相当于在访问对应容器的对应端口了.
Ps:站在宿主的角度,访问上述几个端口的时候,也不知道这个端口是一个宿主机上的服务,还是来自于容器内部的服务,只要正常使用即可。这里的映射过程很像 NAT.
哨兵结点配置文件如下:
version: '3.7'
services:sentinel1:image: 'redis:5.0.9'container_name: redis-sentinel-1restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel1.conf:/etc/redis/sentinel.confports:- 26379:26379sentinel2:image: 'redis:5.0.9'container_name: redis-sentinel-2restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel2.conf:/etc/redis/sentinel.confports:- 26380:26379sentinel3:image: 'redis:5.0.9'container_name: redis-sentinel-3restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel3.conf:/etc/redis/sentinel.confports:- 26381:26379
对一些关键参数的解释:
volumes:建立映射. 哨兵结点在运行的过程中,会对配置文件进行自动修改,因此就不能只拿 /etc/redis/sentinel.conf 这一个配置文件,给三个容器分别进行映射. 以上 volumes 的内容,就需要我们在当前目录下(redis-sentinel目录下)继续创建三个 conf 文件.
c)编写哨兵结点的三个 conf 配置文件
创建 sentinel1.conf、sentinel2.conf、sentinel2.conf 这三个配置文件,内容可以是一样的,容器启动后,会自动对这三个 conf 文件进行修改.
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000
参数解释:
- bind 0.0.0.0:表示让其他结点进行访问.
- port:容器内部使用的端口.
- sentinel monitor redis-master 6379 2:这里是告诉哨兵结点,需要监控哪个 redis 服务器. redis-master 这里其实是 ip 地址,是使用 docker 自动进行域名解析. 最后的 2 表示法定票数,这里的票数就是为了更稳健的确认,当前 redis-server 是否挂了,不能只听一个哨兵的一面之词.
- sentinel down-after-milliseconds redis-master 1000:这里表示心跳包的超时时间.(此处是 1000ms).
d)使用 docker-compose 启动 数据结点
使用 docker-compose up -d 命令(-d 表示后天运行)启动所有容器(必须和 docker-compose.yml 同文件路径下)
Ps:如果启动后发现前面配置有误,需要重新操作,使用 docker-compose down 即可停止刚创建好的容器.
如果出现以下错误,说明 yml 中配置的 version 不支持,修改成报错信息中支持的版本即可(以下报错中提示,只支持 version 为 2.2 或者 3.3 的版本)
如下信息为启动成功
通过 redis-compose logs 命令查看运行日志信息
e)验证
通过连接 redis 验证主从结构
连接主节点:redis-cli -p 6379
连接第一个从节点:redis-cli -p 6380
连接第二个从节点:redis-cli -p 6381
f)使用 docker-compose 启动 redis-sentinel 结点
在 redis-sentine 目录下,使用 docker-compose up -d 启动所有容器
通过 docker-compose logs 查看日志文件
日志里为什么全都是报错信息呢?都是什么意思?(重!)
docker-compose 一下启动了 N 个容器,此时 N 个容器都是处于同一个 “局域网” 中,可以使这 N个容器之间可以互相访问~
但是!!! 三个 redis-server 结点是一个局域网,三个哨兵结点是另外一个局域网,默认情况下,这两网络是不互通的!!!
解决方案:
可以使用 docker-compose 把此处两组服务放到同一个局域网中.
具体的,使用 docker network ls 列出当前 docker 中的局域网,就可以拿到 redis-data 服务的 docker-compose 文件所在目录名(用来修改配置文件)
修改哨兵结点中配置文件,在之前配置文件的基础上加上如下配置:
networks:default:external:name: redisdata_default
然后再启动哨兵,观察日志如下:
打开 sentinel1.conf 文件,就可以看到 ,哨兵结点启动之后,自动进行修改的内容如下:
1.2.4、展现哨兵机制
哨兵存在的意义,就是在 redis 主从机构出现问题的时候(主节点挂了),此时哨兵结点能够自动帮我我们重新选出一个主节点,来代替之前挂了的结点,保证整个 redis 仍然是可用的状态.
这里我们可以通过 docker stop redis-master 命令手动把主节点干掉,然后观察哨兵日志,可以看到
- sdown 主观下线:本哨兵结点,认为该主节点挂了.
- odown 客观下线:好几个哨兵都认为该主节点挂了,也就是达到了法定票数,此时这个主节点挂了的事情就被实锤了.
此时就需要先选出一个 leader哨兵,由这个 leader 哨兵结点选出一个从节点,作为新的主节点.
接着往下看日志,就可以看出 3 个哨兵的投票过程.
1.2.5、哨兵重新选取主节点的流程
1. 主观下线(sdown):哨兵节点通过心跳包,判定 redis 服务器是否正常工作. 如果心跳包没有如约而至,就说明 redis 服务器挂了.
Ps:此时还不能排除网络波动的影响,因此就只能单方面的认为这个 redis 结点挂了.
比如是否可能出现非常严重的网络波动,导致所有哨兵都联系不上 redis 主节点,误判成挂了呢?
当然可能是有的!如果出现这个情况,怕是用户的客户端也连接不上 redis 主节点,此时这个主节点基本上也就无法正常工作了.
“挂了” 不一定是进程崩了,只要无法正常访问,都可以视为挂了.
2. 客观下线(odown):多个哨兵都认为主节点挂了,(认为挂了的哨兵数目到达 “法定票数”),哨兵们就认为这个主节点是 客观下线.
3. 多个哨兵结点会通过投票的方式,选出一个 leader 哨兵结点,有这个 leader 负责选出一个 从节点 作为新的主节点.
具体的,每个哨兵手里只有一票(接下来的投票过程,就是看谁反应快,网络延迟小)
1 号哨兵 第一个发现当前是客观下线之后,就立即给自己投了一票(推举自己成为 leader),并且告诉 2 3 ,我来负责这个事情(2 3 当他们没有投出这一票的时候,拉票请求,就会投出去,如果有多个拉票请求,就投给最先到达的) ,如下:
2 号哨兵不乐意,认为自己和 1 号同时发现,也给自己投了一票,3 号哨兵慢了半拍,才发现是客观下线,就把这一票投给了 1 号哨兵,如下:
如果总的票数到达了哨兵总数的一半,选举就完成了.(把哨兵个数设置成奇数个结点,就是为了方便投票).
4. 此时 leader 选举完毕,leader 需要挑选出一个从节点,作为新的主节点.
具体的挑选从节点的规则如下:
a)优先级:每个 redis 数据结点,都会在配置文件中有一个优先级的设置(slave-priority)优先级高的从节点,就会胜出(如果没有设置,默认优先级都是一样的). 如果当前优先级一样,就看下一个规则.
b)offset :offset 就是从节点从主节点这边同步数据的进度,数值越大,说明从结点的数据和主节点的数据越接近,因此,最大的胜出. 如果当前优先级一样,就看下一个规则.
c)run id:每个 redis 结点启动的时候随机生成一串数字(大小全随机),这时候就是相当于是随机了.
这就像是,大学的学生如果和辅导员有亲戚关系,那么班长就是钦定的,如果没有关系,那么就看谁更听她的话,如果都是刺头,那就只能看谁名字好听了~
5. 新的主节点指定好了之后,leader 就会控制这个结点,执行 slave no one,让其独立山头,成为 master,然后再控制其他节点,执行 slave of,让其他节点以新的 master 作为主节点.
6. 最后,如果之前挂掉的主节点修复了,就直接让这个节点变为从节点加入到机器中(为什么不在成为主节点?因为成为主节点,还需要通过 slave of 修改正在运行的主节点,这个过程,可能会丢失数据).
使用 docker start redis-master 命令启动恢复之前挂掉的主节点后,会发现,已经变为从节点
1.2.6、缺陷
哨兵 + 主从解决的问题是 “提高可用性”,不能解决极端情况下写丢失的问题~
最关键的一点就是,在数量十分庞大的情况下,这种模式就难以胜任了!
因此,redis 集群,就是解决存储容量问题的有效方案.