高可用及 Nginx reload 原理

本文主要是上期 【Nginx】前端该了解的 Nginx 知识 的扩展延伸。

目前来讲后台系统复杂度主要来源于三个方面,高可用、高性能、扩展性。每个方面都有超级多的指标和方案,本问会介绍一些高可用相关的常见概念,属于高可用扫盲篇。学一些扩展性的知识也能为你的进阶道路做更好的技术分类。也会介绍涉及到 Nginx 的可用性解决方案(reload)。

了解高可用

高可用(缩写为 HA),指系统无中断地执行其功能的能力,代表系统的可用性程度。是进行系统设计时的准则之一。高可用性系统与构成该系统的各个组件相比可以更长时间运行。

高可用性通常通过提高系统的容错能力来实现。可以通过设计减少系统不能提供服务的时间,消除单点故障以确保长时间的连续运行或正常运行。假设系统一直能够提供服务,我们说系统的可用性是100%,一般公司对高可用的追求都是五个 9(99.999%)可用性。

什么时候会发生可用性问题

停机主要由两个方面组成:

  • 计划内停机:一般就是指计划的时间内一定会对系统造成破坏性维护的结果,比如软件系统升级,数据库或者服务器的维护。
  • 计划外停机:一般就是值断电、CPU故障或者其他物理层面的断开连接,比如网络故障,中间件故障等。

将这两个方面具体分析,主要由以下几个问题引起:

  1. 网络问题。网络链接出现问题,网络带宽出现拥塞

  2. 性能问题。数据库慢 SQL、Java Full GC、硬盘 IO 过大、CPU 飙高、内存不足

  3. 安全问题。被网络攻击,如 DDoS 等

  4. 运维问题。系统总是在被更新和修改,架构也在不断地被调整,监控问题

  5. 管理问题。没有梳理出关键服务以及服务的依赖关系,运行信息没有和控制系统同步

  6. 硬件问题。硬盘损坏、网卡出问题

Nginx 怎么减少计划内停机

假如有某一个场景,Nginx 需要更新相关配置然后发布。此时有两种方案

  • nginx -s quit 关闭 Nginx,再启动新的 Nginx 服务

如果这么做的话,那么正在处理的连接就会直接中断,客户端返回错误,即便通过客户端重试,用户仍然有可能收到错误请求。这样就会在停止和启动间存在一个短时服务真空期,如果业务方的高可用要求是五个九,那这段时间已经足够影响你的 KPI 了。这段时间就是上文中提到的计划内停机。

  • nginx -s reload 平滑重载更新配置信息

通过 reload 平滑重载,线上的服务不会被暂停,我们上文知道 Nginx 运行时会开启很多个进程,这时我们让子进程中的请求只出不进,等到子进程中没有请求服务时,Nginx 会更新响应子进程的配置。

Nginx 信号处理

Nginx 命令行指令生效的原理都是通过发送 Linux 信号来传递给 Master 进程指令,nginx -s reload 就是发送 Hub 指令给主进程告知它要重载配置,一些主要的指令配置如下 :

命令行 信号 解释
nginx -s reload HUB 主进程接收 HUB 重载配置文件
nginx -s stop TERM 主进程接收 TERM 立刻停止 nginx 运行
nginx -s quit QUIT 主进程接收 QUIT 优雅停止
nginx -s reopen USR1 主进程日志切割

reload 指令处理逻辑

  • 首先命令行输入 nginx -s reload 之后, Nginx 先通过 -s 参数判断要进行信号处理,他就会去通过 pid 查看主进程的进程号,然后向 Master 主进程发送 reload 对应的 Hub 信号。我们也可以通过 ps -ef | grep nginx 查看 Master 进程进程号。

  • 当 Master 进程接收信号后,它会去解析新的 .conf 配置文件。解析完成后 Master 并不会去直接向子进程发送 QUIT 信号命令其停止进程,而是打开新的监听端口来启动新的 Worker 进程。此时就会出现新旧 Worker 进程共存的情况。老 Worker 进程还是会用老配置处理请求数据。

  • 新 Worker 进程启动后,Master 才会发送 QUIT 进程给旧 Worker 进程,此时旧 Worker 进程就会关闭监听端口,停止新的网络请求进入。等待所有的网络请求完成后,旧的 Worker 就会退出。

这里的重载操作也是需要分情况考虑的,假如 Nginx 上配置的是 Socket 或者长连接,通过 reload 重载就会出现大量 Worker 进程堆积的情况。并且一旦 Worker 进程超过了 CPU 数量,就可能触发 CPU 竞争机制造成性能上的下降。

解决这个问题也是可以的,现在我们可以通过 worker_shutdown_timeout 设置过期时间来关闭 reload 过程中仍然打开的连接。

高可用的本质就是冗余 + 故障转移,这两点会不断出现在接下来提出的方法中。

分层次探讨可用性方案:

还记得上文中可用性集群的图片嘛,这里会具体分析每个层级间用什么方法来处理可用性问题。

应用服务层高可用方案:

服务层通常用来处理基础公共服务或者处理具体业务逻辑,在这个层面上大部分都是无状态的。所谓的 无状态 应用是指应用服务器不保存业务的上下文信息,而仅根据每次请求提交的数据进行相应的业务逻辑处理,多个服务实例之间完全对等,请求提交到任意服务器,处理结果都是完全一样的。

那么既然对谁都一样,我们完全就可以通过冗余 + 故障转移的方式来实现高可用。

关于服务层,这里主要可以按照顺序拆成两个部分,第一层级是下游客户端到反向代理,第二是反向代理到具体上游服务器,不过这里首先要说的是下游客户端到反向代理怎么处理,因为这个比较成熟。

反向代理到服务器

Nginx 反向代理层可以对上游服务器做故障转移,这里用加权轮询负载均衡为例,具体的负载均衡算法可以参考前文,我们这次要介绍两个在 upstream 中负责承担高可用业务的两个参数。

  • backup:置顶某个服务为备份服务,当其他非备份服务全都挂掉之后请求就会转发到这个备份服务

  • down:标记当前机器已经下线,请求不会打到这个机器中

可以设置备份服务了,那什么情况算是服务挂掉呢?其实也可以用过人为设置,参数也都很简单↓

  • fail_timeout: 默认10秒,指在fail_timeout 时间内,如果失败次数超过了 max_fails 的值,请求就不会在 fail_timeout 时间内转发的这个服务。

  • max_fails: 设置一段时间(fail_timeout)内的失败次数。

我们首先在代码中开启五个服务,这五个服务的功能都相同(设置冗余),每一个都可以返回相同的图片返回值。

# 开启多个图片服务器 集群链接池

upstream img_server {
    server 127.0.0.1:3001 weight=1;
    server 127.0.0.1:3002 weight=3 max_fails=3 fail_timeout=3s;
    server 127.0.0.1:3003 weight=1;
    server 127.0.0.1:3004 backup;
    server 127.0.0.1:3005 down;
}
server {
    listen 8091 default_server;
    location / {
        proxy_pass http://img_server; # 代理到集群连接池
    }
}
复制代码

通过上面的 Nginx 配置,集群就会按权重打到 3001、3002、3003 服务器中

  1. 当 3002 服务器在 3s 内有超过 3 个错误请求,当前服务器就不可用,请求都会打到 3001、3003 中。

  2. 当 3001、3002、3003 全部不可用,所有的请求都会打到备机 3004 中。

  3. 假如除了 3005 全部不可用,可以人为删除 3005 的 down 字段,通过 nginx -s reload 重载 Nginx 服务。

通过如上操作就可以确保代理服务器到上游业务服务器的高可用,这就是上文提到的冗余 + 故障转移概念。

客户端到反向代理

下游客户端到反向代理层的高可用,是通过反向代理层的冗余来实现故障转移的。

首先我们要知道 Nginx 是用作代理请求的入口使用的,我们为了防止单台 Nginx 宕机造成全部服务宕机,可以将两台 Nginx 服务器都设置成同一个虚拟 IP(Virtual IP Address)。当主 Nginx 服务器挂掉之后,我们再次访问的时候,实际上就是在访问备用 Nginx 机器,再通过备用 Nginx 机器做负载均衡。

什么是 VIP(Virtual IP Address

我们通常说的 VIP 就是虚拟 IP 地址(Virtual IP Address)。通常一个 IP 只能绑定在一个网卡上,一旦这台机器故障,客户端根据 IP 找到这台机器的请求就会得不到响应。

而 VIP 虽然也会配置在一台机器上,但当这台机器故障时,备机就会把这个 IP 抢占过去,这个抢占的过程也叫作漂移。通过漂移这就做到了 IP 不变的情况下,故障转移到了备机身上。

那 VIP 怎么在 Nginx 上应用呢,这里就出现了 Keepalived 解决方案。

什么是 Keepalived && 怎么用 Keepalived 实现 VIP 故障转移

Keepalived本身是一款为 LVS 负载均衡设计的状态检测和切换工具,他会监控集群中各个节点的状态,不过之后他又加入了虚拟路由器冗余协议功能(VRRP),通过 VRRP 就可以实现 VIP 的故障转移。

话说回来,目前 Keepalived 的高可用解决方案除了 LVS 中可以使用,在 Nginx 和各类数据库中都会见到它的身影。作为前端er,我们只需要了解以下几点就可以了,再多就是抢饭碗了。

VRRP 将每个设备都设置成 Master 和 Backup 两个角色,每台设备都有一个权重,一般权重最高的正常设备会被选举为 Master。Master 会持有 VIP。

如果 Master 超过设置的时间没有回应,则在 Backup 中重新选举,这样就做到了故障转移。其实可以说 Keepalived 就是一个中介,通过中介来判断主备机器何时启用。

  • Master-> Backup 模式 : 主机宕机时,虚拟 IP 会自动漂移到备机,当 Keepalived 监听到主机修复后,还会把 VIP 移动过来。

  • Backup-> Backup 模式:主机宕机时,虚拟 IP 会自动漂移到备机,但是即使主机修复, VIP 也不会改变,而是一直用原来的 Backup 备机作为主机。

不过高并发场景下,一个 Nginx 承受不住,无论有多少从节点,还是会挂掉。所以我们又使用了 LVS 四层代理来实现高可用,把 Keepalived 移动到 LVS 层来解决并发场景。

数据存储层高可用方案:

数据库层面的高可用大部分都是通过主从模式、读写分离来实现的,不过这里还是要知道一些具体的细节和其他的解决方案。

首先,存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,但是这里就有与服务器冗余不同的问题,数据库存储和读取的写入对同步的要求非常高,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。

假如每个服务器之间的线路传输是几毫秒,但是如果是跨地区的机房可能就要几十毫秒,在这几十毫秒之中如果发生了数据的读写操作,那两个机房就会出现数据不同步问题,所以处理存储层的可用性相对来说比较复杂。接下来会具体讨论主备模式和主从模式两个方案。

主备模式

主备模式是比较久远的模式了,听名字都知道就是一个主机,多个备用机器来实现高可用,读取和写入全部交给主机负责,主机定期把复制的数据交给备用机器。一旦主机宕机就将某一个备用机器作为主节点。

主备模式其实就是通过协商规则来解决数据一致性问题,当服务器启动的时候两台服务器都可以作为备用机器,然后让两台机器建立连接,其中一台机器用来作为决策主机,另一台作为备用机,

这样做的好处就是客户端是无感知的,当主机出现故障时我们可以通过备机来继续服务,而且主机和备机之间只需要进行数据复制,不需要考虑分布读写的问题。

但是这里也会有一个糟糕的决策问题,比如当两台机器互相的连接时,可能会发生以下几种状况。

  • 如果备机在连接中断的情况下认为主机故障,那么备机需要升级为主机,但实际上此时主机并没有故障,那么系统就出现了两个主机,这与设计初衷不符合。

  • 如果备机在连接中断的情况下不认为主机故障,则此时如果主机真的发生故障,那么系统就没有主机了,这同样与设计初衷不符合。

  • 如果为了规避连接中断对状态决策带来的影响,可以增加更多的备机连接。这样虽然能够降低连接中断对状态带来的影响,但同时又引入了这几条连接之间信息取舍的问题,即如果不同连接传递的信息不同,应该以哪个连接为准?

怎么确定两台机器的状态连接

产生这个问题的主要原因就是:当主机宕机时,备机如何判断到底是主机宕机还是主备之间的连接问题。假如主机没有宕机而是连接问题,服务器到底要那台机器执行读写操作?

针对主机通信及状态传递,这里有引入了以下几个方向:

  • 状态传递的渠道:是主备相互间互相连接,还是通过第三方仲裁?

  • 状态检测的内容:主备通信到底要检查什么?机器是否掉电、进程是否存在、响应是否缓慢等。

  • 切换时机:什么情况下备机应该升级为主机?是机器掉电后备机才升级,还是主机上的进程不存在就升级。

  • 切换策略:主机故障恢复之后,我们需要恢复原来的主机做主机,还是已经成为主机的备机做主机。

  • 自动程度:切换是完全自动的,还是需要人工验证的?

  • 数据冲突:当原有故障的主机恢复后,新旧主机之间可能存在数据冲突。

中介模式解决主备冲突

这其中,解决主备状态关系最简单的方式就是中介模式,我们在主备机器中间再加入一个第三方中介,通过中介完成双方的状态管理,实在感兴趣可以去看看 Apach Zookeeper 的切换架构。这里就不深入了探讨具体实现原理了,只是介绍它的解决方案。

  • 所有的机器一开始都是备机!并且只要与中介断开连接的就自动变成备机!

  • 主机与中介断开连接后,主机自动成为备机,并且由中介告知某个备机,备机将自己升级为主机。

  • 如果是网络中断导致主机与中介断开连接,主机自己会降级为备机,网络恢复后,旧的主机以新的备机身份向中介上报自己的状态。

主从模式

对比主备复制中备机只在主机出现宕机时启用,主从模式中的从机就是需要真正承担读取操作的机器。主机会将数据复制给从机来实现数据统一。

  • 首先,主从模式通过从机读取数据来保证数据可读性。

  • 其次,客户端读取操作一般都占数据流 80% 以上,所以多个机器读取也能发挥硬件性能。

主从模式中我们也可以通过 Keepalived 来做故障转移,当然了,我们维持了这么多机器一起执行读取写入操作,这对于网络延迟的要求极高,而且不同的操作或者说读取操作怎么分配也是个比较麻烦的问题。

集群/分区

集群:

当单台机器性能不足以满足业务需求的时候就会出现集群这种概念,上文中的主备、主从都有多个备机和从机,这就算是一个集群了,这里要说的集群主要是指多台可以执行读写操作的主机组成的分散集群,每台服务器都会负责存储一部分数据。

当然集群中也会有一个特殊主机负责做数据分配。比较常见的集群就是 Hadoop 集群了。感兴趣可以看看自己公司后台文档,现在基本都是这个模式。

分区:

分区这个概念其实也是因为以上的高可用适用性范围还是不够大,万一整个机房停电主备都挂了怎么办?

这时候就不能把鸡蛋放在一个篮子里,常见的分区中比如地理位置分区的概念就可以非常简单的处理这种问题。

每个分区存储一部分数据。当某地发生事故后,受影响的也只是一部分数据和一部分服务器,而不是全部数据都不可用;故障恢复后,其他地区的备份数据会快速恢复故障区域的业务。有关分区也有好多种思路,可以参考如下↓

  1. 独立式分区,每一个分区都将自己的备份单独存储,各个分区之间没有什么关系,这种比较土豪,比如三个分区就对应三个备份中心。

  2. 集中式分区,这种方案就相对省钱,所有的分区都将备份存储到一地,而且设计简单。

  3. 互备式分区,这种就抠到家了,分区之间互相备份其他分区的文件,不过这么搞设计复杂度就上来了,每个分区到底改备份多少个分区的数据?新建分区后数据存储到哪里?都是问题。

当然了,在做到了集群和分区的方案之后,我们又多了一种可以满足高可用的方式:DNS 域名划分。

有关 DNS 域名服务器可以做的文章其实很多,DNS 除了做高可用,在高性能场景中也是很重要的解决方案,因为当业务范围特别大, 客户里服务器非常远的时候,网络影响就会逐渐上升,网络通信延迟、丢包造成的页面加载慢就非常需要由 DNS 和 CDN 服务器配合来承载业务。感兴趣的话之后可以具体讲讲各种 CDN 原理和机制。

DNS 服务器会检查集群服务的健康状态,再分发给用户它觉得最合适的 IP 地址来满足用户体验,并且当灾难发生时,通过 DNS 的健康检查也可以做到主备之前 IP 的容灾切换。

结语

系统高可用全部都是围绕着冗余和故障转移下功夫。

除了上文讨论的可用方案之外,我们还需要通过一定的过载保护策略来保证服务器的可用性,常见的策略比如限流、熔断、降级都是应对故障的方法。

但是有这些保护策略还不够,我们还需要故障监控系统,这就又需要引入监控管理或者数据采集。Apache 这些服务器都内置了用户日志收集、服务器性能监控报告等功能。

每个公司对此的策略可能都不太相同,通过了解这些基本思路,可以提升我们的工程化视野,希望本文对看到这里的同学能够有所帮助。

おすすめ

転載: juejin.im/post/7075860879295643678