记一次nginx负载均衡健康检查引起的事故之no live upstreams while connecting to upstream

news2024/11/25 10:52:48

文章目录

    • 概要
    • 一、负载均衡
        • 1.1、常用指令解析
        • 1.2 负载算法配置
        • 1.3、反向代理
    • 二、事故分析
    • 三、小结

概要

Nginx是工作中常用的HTTP服务中间件,除了提供HTTP服务,常用的还有反向代理、限流、负载均衡等功能。
负载均衡支持七层负载均衡(HTTP)和四层负载均衡(TCP),本人项目是基于七层负载的。

这次问题发生在一个阳光明丽的上午,本来正在勤勤恳恳的码字,突然大量用户反馈502,立马查监控,发现服务器资源毫无波动,说明并不还突发流量造成的。继续查错误日志,发现遭到不明攻击,有很多伪造的攻击请求错误,继续看日志,发现Nginx一直在刷no live upstreams while connecting to upstream错误,也就是说此时Nginx负载均衡的上游服务都不可用,那为什么呢?经检测我上游PHP服务运行的好好的,秒级响应呀。

下面我们先了解下Nginx负载均衡,这样才更好的理解问题发生的原因。

一、负载均衡

根据官网可知负载均衡算法有:轮询、加权轮询、hash、ip_hash、最少活跃连接数(least_conn)、随机(random)、最少平均响应时间和最少活跃连接数(least_time)。

另外也有第三方支持的库,常用的如fair(最少响应时间),url_hash(Nginx V1.7.2已支持了,不需要编译第三方库了)。

1.1、常用指令解析

负载均衡配置案例

http{
	#负载均衡配置
    upstream  test_upstreams {
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8084  backup; #备份服务
		server 127.0.0.1:8084  down; #永久性不可用的服务,为啥会有这个指令呢???
		#空闲连接池
		keepalive 30;
		keepalive_timeout 120;
		keepalive_requests 2400;
    }
	
	#限流
	limit_req_zone $binary_remote_addr zone=limit_one:10m rate=100r/s;
	
	#http服务
    server  {
        listen 80;
        server_name www.test.com;
        root  /usr/local/web;
        location  /  {
			limit_req zone=limit_one burst=5 nodelay;#限流
            proxy_pass http://test_upstreams;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;#故障转移
            proxy_next_upstream_timeout 5s;
            proxy_next_upstream_tries 2;
			proxy_connect_timeout 3s;
			proxy_send_timeout 15m;
			proxy_read_timeout 15m;
        }
    }
}

正如案例所现,常用的指令都列出来了,其他不常用的请移步官网。

  • weight 指定当前上游服务器负载权重,值越大,分配的流量就越大,默认值是1;
  • max_fails 在fail_timeout 时间内失败次数达到该值则认为当前上游服务器不可用,默认值是1;
  • fail_timeout 两种作用,一种是同max_fails解释,另一种是判定当前上游服务器不可用时,经过fail_timeout时间会探测是否恢复,默认值是10s;
  • max_conns 指定与上游服务最大TCP连接数,每个worker独立计数,用于限流,默认值是0,表示不限制,注意V1.11.5后免费版才支持。
  • backup 备份服务,当其它上游服务都不可用时才会启用,可以作为容灾备用,不支持hash、 ip_hash、 random三种负载算法
  • keepalive 指定每个worker进程可以缓存的最大空闲TCP连接,所以可以称之为空闲连接池,根据官网描述其并不限制最大连接数,所以值不建议过大。默认不开启。
  • keepalive_timeout 空闲连接池中连接的最大存活时间,默认1h。
  • keepalive_requests 空闲连接池中每个TCP连接处理的最大请求数,到达后就会被释放。默认1000,注意V1.19.10之前是100。

可以看到其实现了健康检查、故障恢复等高可用特性。

1.2 负载算法配置

1、轮询
每个请求依次分配到不同的上游服务。

upstream  test_upstreams {
		server 127.0.0.1:8081  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  max_fails=3  fail_timeout=8s;
}

2、加权轮询
指定轮询权重,请求分配率和weight值成正比,用于上游服务器性能不均的情况。

upstream  test_upstreams {
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

3、hash
对某个key做hash 映射后进行请求转发。
如下配置,按请求url做hash,一般配合缓存命中来使用

upstream  test_upstreams {
    	hash $request_uri;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

4、一致性hash

upstream  test_upstreams {
    	hash $remote_addr consistent;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

5、ip_hash
适合需要客户端与服务端有一定粘性的场景,保证客户端每次都命中同一个上游服务。当然了,上游服务发生故障会引起转移的。

upstream  test_upstreams {
    	ip_hash;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

6、least_conn
考虑权重,优先将请求分配到TCP连接数最少的上游服务。一般来说连接数多的上游服务压力就大些,可以合理配请求压力。

upstream  test_upstreams {
    	least_conn;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

7、random
随机选择,不过可以配置可选项two,实现随机选两个上游服务,并从中选一个连接数少的一个上游服务。
least_conn也可以由least_time替换,但是least_time商业版才支持

upstream  test_upstreams {
    	random two least_conn;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

8、least_time
考虑最少平均响应时间和最少活跃连接数作为上游服务,更合理,奈何只有商业版才支持。

upstream  test_upstreams {
    	least_time;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

9、fair(第三方)
需要编译第三方代码。
看了下源码,不支持 max_conns等其他参数。
看很多文章说是按最短响应时间的优先分配,看了源码,并没有真的用到上游服务响应时间来作为分配指标,所以保留意见
核心逻辑如下:

假设来了有两个可用的上游服务A和B。现在依次来了1、2、3、4、5、6、7、8、9共九个请求。
A 处理了 1、4、5、7 四个请求,B处理了2、3、6、8、9五个请求。那么第10个请求来的时候有:
A 有已处理请求个数nreq=4,间隔请求个数req_delta=10 - 7=3;
B 有已处理请求个数nreq=5,间隔请求个数req_delta=10 - 9=1;
有一个评分函数score_func,优先分配给score_func(nreqA,req_deltaA)/weight和score_func(nreqB,req_deltaB)/weight中最小的一方。
这种算法稳定性完全取决于评分函数,但线上网络环境复杂,请慎用

upstream  test_upstreams {
    	fair;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}
1.3、反向代理

负载均衡是要配合反向代理使用的,如1.1小节中的案例所示。我们要特别注意 proxy_next_upstream 参数,本次线上事故就与它有关
官网解释如下:

Specifies in which cases a request should be passed to the next server

即指定在什么情况下将请求转移给下一个上游服务。其默认值error timeout。

其参数有:

  • error 与上游服务建立TCP连接、向上游服务传递请求或读取响应标头时出错;
  • timeout 与上游服务建立TCP连接、向上游服务传递请求或读取响应标头超时;
  • invalid_header 上游服务响应为空或无效响应;
  • http_500 上游服务响应500;
  • http_502 上游服务响应502;
  • http_503 上游服务响应503;
  • http_504 上游服务响应504;
  • http_403 上游服务响应403;
  • http_404 上游服务响应404;
  • http_429 上游服务响应429;

支持以上10种错误情况进行请求转移到下一个上游服务,另外:

  • non_idempotent 正常情况下当HTTP请求是POST, LOCK, PATCH方法时,上游失败是不会请求下一个上游服务的,需要配置上该参数就可以转移了,所以配置时要注意接口的幂等性,重试是否会造成重复提交引起业务异常,一般来说GET、OPTIONS之类的是幂等的
  • off 关闭自动转移。

所以proxy_next_upstream指令实现了高可用的另一个特性,故障转移。即如果正常请求失败了,会依次向剩余的可用上游服务进行重试,直至有一个成功或全部失败。
不过故障转移也容易造成故障扩散,本次线上故障就是这样的

proxy_next_upstream_timeout
该参数限制了请求的总时间,默认0,表示会依次向所有可用上游服务请求一次,直至有一个成功或全部失败。

以案例所示,假设8081、8082、8083三个上游服务两秒后返回http_500。如果设置proxy_next_upstream_timeout值为3s,那么只能请求两个上游服务,即除了本来正常的请求失败后还能重试一次。

proxy_next_upstream_tries
该参数限制了请求的总次数,默认0,表示会依次向所有可用上游服务请求一次,直至有一个成功或全部失败。

以案例所示,假设8081、8082、8083三个上游服务均返回http_500。如果设置proxy_next_upstream_tries值为2,那么只会请求两个上游服务,即除了本来正常的请求失败后还能重试一次。

二、事故分析

线上事故其实也有点乌龙了,虽说是因为受到恶意攻击引起的,但主要还是前人的Nginx配置有漏洞造成的,与业务无关。
大概转发路径如下:
Nginx转发示意图
经过日志追查,发现上游的两个Nginx 有这样的配置:

error_page 405 = @405
location @405 {
    proxy_pass http://localhost:80;
}

这本来是为了美化 405 错误页面的,结果不知道为什么废弃了,但Nginx配置并没有干掉,这次线上恶意攻击出大了405错误,然后转发到本机80端口,结果并没有80这个服务,就返回给入口Nginx 502了。

入口Nginx收到502,首先触发了故障转移,请求转移到另一个上游Nginx,依旧收到502,线上恶意攻击比较高频,
接着触发健康检测阈值,导致入口Nginx认为所有上游服务都故障了,就会报 no live upstreams while connecting to upstream错误,并返回客户端502,
线上恶意攻击持久且高频,导致入口Nginx的健康检测一直认为上游服务故障,无法恢复,进而正常用户也无法使用。

三、小结

经过第一章的分析,可知Nginx的负载均衡除了负载外,还有故障转移、健康检测、故障自动恢复、限流等特性。

对于健康检测,可以说其是被动检测,即需要先发请求,看从发出到收到响应整个过程的反应作为检测手段。
当然商业版ngx_http_upstream_hc_module模块也提供了主动检测的指令health_check,不差钱的可以用上。
另外淘宝技术团队也开源了一个主动检测模块,源码,有需要的可以用起来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1036665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KVCache原理简述

在GPT的推理过程中,它根据完整的提问和回答的已生成部分,来生测下一个词(的概率)。 例如,我们的提问是【天王盖地虎,】,回答是【宝塔镇河妖。】。 那么第一次,GPT根据【天王盖地虎…

Windows安装Docker Desktop并配置镜像、修改内存占用大小

启用Hyper-V Win S 搜索控制面板 安装WSL2 第一种方法(推荐) 以管理员运行命令提示符,然后重启Docker Desktop wsl --updatewsl --set-default-version 2第2种方法去微软官网下载WSL2并安装 《微软官网下载WSL2》 配置WSL2最大内…

类和对象:运算符重载

本篇文章来介绍一下C中的运算符重载,以及与运算符重载有关的三个默认默认成员函数:赋值运算符重载,普通对象取地址与const对象取地址操作符重载,也就是下面图片中6个默认成员函数的后三个,前三个默认成员函数在之前文章…

Makerbase SimpleFOC MINI 基本测试

第1部分 硬件介绍 1.1 硬件清单 序号品名数量1SimpleFOC MINI V1.0 主板12ARDUINO UNO主板13MKS SF2804电机14杜邦线45DC12V电源16USB 线1 1.2 硬件连接 1.SimpleFOC MINI V1.0 主板主板与Arduino UNO主板叠接。如下图所示: 2.USB 线一端连接 Arduino UNO 主板…

RabbitMQ工作模式——Routing路由模式

1.Routing路由模式 Routing生产者代码 public class Producer_Routing {public static void main(String[] args) throws IOException, TimeoutException {//1.创建连接工厂ConnectionFactory factory new ConnectionFactory();//2.设置参数factory.setHost("172.16.98.…

一文彻底搞懂PN结及其单向导电性(图解说明)

前置知识 首先我们要知道纯净的本征半导体 硅 的导电性是非常差的,所以我们一般都会向纯净硅中添加杂质,也就是P型半导体和N型半导体。P型半导体和N型半导体都是呈电中性的,对于N型半导体而言,它又多数载流子电子和带正电荷的N离子…

【LeetCode-中等题】113. 路径总和 II

文章目录 题目方法一&#xff1a;DFS回溯 题目 方法一&#xff1a;DFS回溯 解题核心 就是要知道递归在哪里结束 &#xff0c;收货结果在哪里收获&#xff0c;哪些变量需要回溯&#xff0c;哪些不需要回溯 class Solution {List<List<Integer>> res new ArrayLis…

BI技巧丨Window应用之累计求和

Window函数除了可以用来计算同环比、移动平均之外&#xff0c;还可以用来处理累计求和问题。 核心在于Window的from和to参数的设定&#xff0c;可以将其设置为绝对位置和相对位置。 先来看看本期的案例数据&#xff1a; 案例数据比较简单&#xff0c;一张销售事实表。 将其导…

C++核心编程——P45-52继承

继承 继承是面向对象三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中: 我们发现&#xff0c;定义这些类的时候&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这时候我们就可以考虑利用继承的技术&#xff0c;减少重复代码量…

PPPoE配置

实验需求 配置IP地址使用PPPOE拨号上网设置路由让直播业务部和营销部都可以访问外网 实验拓扑 实验步骤 配置 R1地址池 电信链路&#xff1a; [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]sysname r1 [r1]ip pool zhibo  //配置…

多进程编程- POSIX命名信号量(named semaphore)

POSIX命名信号量是POSIX标准下的一个进程间同步原语&#xff0c;允许多个进程共享同一个信号量&#xff0c;从而实现进程间的同步和通信。这与无名信号量不同&#xff0c;无名信号量主要用于线程之间的同步&#xff0c;而不是进程之间。 命名信号量是“命名”的&#xff0c;因…

基于AVR128单片机抢答器控制系统

一、系统方案 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 uchar set_time 0 ; DDRA0XFF; PORTA0xff; DDRB0XFF; PORTB0x00; DDRC0X00; PORTC0xff; DDRD0XFc; PORTD0XFF; DDRE0XFF; PORTE0XFF; DDRF0XFF; PORTF0XF0; beer1(); timer1_i…

Linux-多路转接-select/poll

select/poll 五种IO模型对IO的正确理解何为高效的IO阻塞IO非阻塞IO设置文件描述符为非阻塞模式非阻塞IO例子 信号驱动IO异步IO多路转接 selct认识接口select返回值 基本使用select使用特点缺点 poll认识接口对select的改善缺点 五种IO模型 对IO的正确理解 &#x1f680;IO不仅…

MySQL学习笔记9

MySQL数据表中的数据类型&#xff1a; 在考虑数据类型、长度、标度和精度时&#xff0c;一定要仔细地进行短期和长远的规划&#xff0c;另外&#xff0c;公司制度和希望用户用什么方式访问数据也是要考虑的因素。开发人员应该了解数据的本质&#xff0c;以及数据在数据库里是如…

Sentinel故障转移及实现原理

Sentinel故障转移及实现原理 一、哨兵模式的基本工作流程二、判断实例下线三、选举新主库四、哨兵模式弊端五、哨兵集群判断实例下线六、哨兵集群判断实例下线详细工作过程七、哨兵集群的通信八、哨兵和客户端的通信九、总结 一、哨兵模式的基本工作流程 redis在运行时会开启一…

vue基础知识十五:说说你对slot的理解?slot使用场景有哪些?

一、slot是什么 在HTML中 slot 元素 &#xff0c;作为 Web Components 技术套件的一部分&#xff0c;是Web组件内的一个占位符 该占位符可以在后期使用自己的标记语言填充 举个栗子 <template id"element-details-template"><slot name"element-na…

(2022|ECCV,图像分割,VQ-SEG,AR Transformer)Make-A-Scene:利用人类先验进行基于场景的文本到图像生成

Make-A-Scene: Scene-Based Text-to-Image Generation with Human Priors 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2.相关工作 2.1. 图像生成 2.2. 图像标…

2023年汉字小达人区级自由报名明天开赛,3个新问题和往年真题练一练

明天9月25日&#xff0c;备受关注的2023年第十届上海小学生汉字小达人区级自由报名的比赛就要开始了&#xff0c;最近还是有几个“小迷糊”家长刚听说这个活动&#xff0c;问了几个问题&#xff0c;我觉得挺有普遍性的&#xff0c;所以再次给大家回答一下&#xff0c;希望能够帮…

redis漏洞修复:CVE-2022-35977、CVE-2023-22458、CVE-2023-28856

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、漏洞内容二、现状三、更新redis下载镜像停止已有的容器启动新的容器 四、更新后的版本1. 查看日志2. 查看版本 总结 前言 漏扫发现机器上的redis版本有点低…

图像识别-YOLO V8安装部署-window-CPU-Pycharm

前言 安装过程中发现&#xff0c;YOLO V8一直在更新&#xff0c;现在是2023-9-20的版本&#xff0c;已经和1月份刚发布的不一样了。 eg: 目录已经变了&#xff0c;旧版预测:在ultralytics/yolo/v8/下detect 新版&#xff1a;ultralytics/models/yolo/detect/predict.py 1.安…