原文作者:陶辉
原文链接: NGINX怎样隐藏上游错误?
转载来源:NGINX开源社区


当上游出错时,作为负载均衡的NGINX可以实时更换Server,在客户端无感知的情况下重新转发HTTP请求。这一功能在NGINX指令中称为next upstream,本文将详细介绍其用法及实现原理。

在OSI网络模型中,传输层的TCP协议通过内核提供的系统调用向NGINX反馈错误,表示层的TLS/SSL协议通过openssl库向NGINX返回错误,而应用层的HTTP协议(或者uwsgi、gRPC、CGI、memcached等协议)通过Response的Decode解码流程返回错误。

当NGINX能够通过重试解决这些错误时,我们可以使用next upstream机制对客户端隐藏个别上游Server由于宕机、网络异常产生的错误,这可以极大的提升整个分布式系统的可用性。

如果我们不清楚它处理协议错误及重试转发的原理,就很容易在实际场景中发现next upstream没有发挥作用,比如:

  • proxy_request_buffering功能关闭后,一旦NGINX转发了请求包体,它就会释放掉内存中缓存的内容,从而失去了next upstream的重试能力。
  • 从上游接收到完整的HTTP头部后NGINX就会向下游客户端转发,由于TCP协议是有序字符流,一经发出就无法更改,此时从HTTP语法层面上也会失去next upstream能力。
  • POST方法属于idempotent非幂等方法,所以从HTTP语义层面上next upstream功能也不会开启(默认配置下)。

等等。可见,next upstream是否能够按预期工作(遵照proxy_next_upstream_tries、proxy_next_upstream_timeout等指令),需要我们对它有深入的理解。

本文将介绍NGINX作为代理服务器转发请求时,next upstream机制检测错误并重新转发给上游的执行流程。

虽然本文例子中的指令属于HTTP/1模块,但在最后我会将官方提供的6个代理模块放在一起做个比较,在对比中你会更深入的了解upstream机制。

同时,本文也是NGINX开源社区基础培训系列课程第二季,即7月23日晚第3次直播课的文字总结。


TCP传输层的错误处理

作为负载均衡,NGINX可以在OSI网络模型的多个层级中检测、处理错误,我们首先来看NGINX在TCP传输层是如何应用next upstram机制的。

TCP层的错误主要体现在三次握手与数据传输中,是否能够及时接收到对方返回的ACK确认帧。

由于TCP协议实现了有序字节流的可靠传输,所以HTTP、gRPC、CGI、memcached等协议都是基于TCP实现的。因此,NGINX向上游转发请求前,需要先通过三次握手建立TCP连接。关于3次握手的流程,你可以参见下图,这里不再详述。

当NGINX作为客户端发起三次握手时,它会向上游Server监听的端口上发送SYN报文。在以下2种情况下,NGINX会认为3次握手建立失败:

  • 接收到对方返回的RST重置报文。通常,这发生在上游对应的应用程序未启动,或者进程没有监听相应的端口;
  • 在proxy_connect_timeout时间内(默认60秒),没有接收到对方返回的SYN+ACK报文。

在以上场景中,NGINX默认会开启next upstream功能。这是因为,XXX_next_upstream指令拥有默认值error和timeout,其中error对应了协议层错误,而timeout则将NGINX指令定义的超时错误单独拎了出来。

所有基于TCP的应用层协议都有这一特性,下面以HTTP/1代理模块为例,探究各指令的用法:

Syntax: proxy_next_upstream error | timeout | invalid_header |
http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
Default: proxy_next_upstream error timeout;
Context: http, server, location

一旦连接建立成功,NGINX就会基于代理模块适配的应用层协议转发请求。

TCP协议要求对于发送的每个字节,接收端都要通过ACK报文中的累计确认序列号进行反馈,一旦在RTO(TCP Retransmission Timeout)时间内未收到ACK确认,操作系统的内核就会重发TCP报文,如下图所示:

内核会为每个socket建立发送、接收缓冲区。如果大量发送报文得不到确认,那么发送缓冲区(它是动态调整的,可通过tcp_wmem修改范围)就没有空闲位置,这样一旦NGINX中的epoll_wait函数在proxy_send_timeout秒内都没有返回写事件,就会触发timeout错误

Default: proxy_send_timeout time;
Context: http, server, location
Context: http, server, location

当转发完请求,接收响应的过程中,如果epoll_wait两次返回读事件的间隔超过了proxy_read_timeout秒,也会触发timeout错误:

Syntax: proxy_read_timeout time;
Default: proxy_read_timeout 60s;
Context: http, server, location

当NGINX未完成完整的转发流程时,服务器接收到的RST或者FIN报文会试图关闭TCP连接,此时都会通过epoll_wait函数触发error错误。

只要proxy_next_upstream指令后加入了error和timeout选项,且NGINX还拥有转发完整请求的能力,next upstream机制就会生效,NGINX会基于负载均衡规则,重新挑选1个可用Server转发请求。

NGINX还提供了proxy_next_upstream_tries指令,用于限制重试次数(默认值是0,表示不加限制):

Syntax: proxy_next_upstream_tries number;
Default: proxy_next_upstream_tries 0;
Context: http, server, location

通过proxy_next_upstream_timeout指令还可以限制更换上游Server转发请求的总时长(默认不加限制)。

注意,该时长的起始时间是从首次转发请求算起(而不是每次更换上游Server时重新计算),而截止时间则是最后1次检测next upstream是否允许使用的时刻:

Syntax: proxy_next_upstream_timeout time;
Default: proxy_next_upstream_timeout 0;
Context: http, server, location

任何时候NGINX与下游的TCP连接出错时,next upstream机制都会失效,因为NGINX失去了转发HTTP响应的能力。


TLS表示层的错误处理

再来看NGINX如何处理表示层TLS/SSL协议的错误。TLS会话的建立需要通过握手完成,如下所示:

TLS握手需要完成密钥协商和证书验证工作,通常需要2个RTT的时延(TLS1.3需要1个RTT),这一过程会复用proxy_connect_timeout指令标识的超时时间。一旦TLS握手超时,同样遵循timeout错误的处理方式。

在TLS握手过程中,NGINX还可以核验上游Server返回的证书链,以及SNI(Server Name Indication)插件中的域名(参见RFC6066)。你可以在proxy_ssl_verify指令中打开这一功能:

Syntax: proxy_ssl_verify on | off;
Default: proxy_ssl_verify off;
Context: http, server, location

NGINX会将服务器发来证书中SNI插件中的域名,与proxy_ssl_name指令中的变量做比较:

Syntax: proxy_ssl_name name;
Default: proxy_ssl_name $proxy_host;
Context: http, server, location

proxy_ssl_name的默认值是proxy_host变量,它等于proxy_pass指令后的域名。需要注意,一旦证书链或者SNI域名验证失败,next upstream机制将按error错误处理。


应用层错误处理

一旦应用层在协议层面返回了正确的Response响应,但从语义上却是错误的,NGINX同样可以启用next upstream机制。下图是TCP层、TLS层与应用层结合在一起后,next upstream的工作流程示意图:

我们先以HTTP/1协议为例介绍应用层错误的处理方式,再通过它来对比其他应用层协议。

对于符合REST规范的HTTP消息,响应码应当能够准确地描述应用层错误,比如,2xx错误码通常表示成功,4xx错误码表示请求参数有问题,而5xx错误码表示服务器出现故障。

基于RFC中对各错误码的定义,NGINX允许对以下7种可以进行重试的错误码启用next upstream功能:

响应码 字符串描述 含义
403 Forbidden 服务器理解请求的含义,但没有权限执行此请求
404 Not Found 服务器没有找到对应的资源
429 Too Many Requests 客户端发送请求的速率过快。(NGINX版本 >= 1.11.13时提供)
500 Internal Server Error 服务器内部错误,且不属于其他5xx错误类型
502 Bad Gateway 代理服务器无法获取到合法响应
503 Server Unavailable 服务器资源尚未准备好处理当前请求
504 Gateway Timeout 代理服务器无法及时的从上游获得响应

当然, NGINX默认会将以上错误响应码及包体转发给客户端。有些时候,你可能只是想转换这些错误码,以另一种方式向用户体现业务的处理结果,而不是换一个上游Server重新转发请求。

比如,当上游返回404错误时,改为通过200返回一张找不到资源的图片。此时,可以通过proxy_intercept_errors指令完成这一功能:

Syntax: proxy_intercept_errors on | off;
Default: proxy_intercept_errors off;
Context: http, server, location

当proxy_intercept_errors开启后,对于上游返回的大于等于300响应码的请求,都可以基于error_page指令继续处理:

Syntax: error_page code ... [=[response] uri;
Default: -
Context: http, server, location, if in location

比如,对于上游返回的404错误码,以200的方式返回一个本地文件404_not_found.html,就可以做如下配置:

location /ih {proxy_pass http://ihBackend;proxy_intercept_errors on;error_page 404 = /404.html;
}location = /404.html {alias html/404_not_found.html;
}

如果你希望对这7个错误码启用next upstream机制,可以在proxy_next_upstream指令的选项中添加相应的错误码,比如http_500就表示上游Server返回的500错误码:

Syntax: proxy_next_upstream error | timeout | invalid_header |
http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
Default: proxy_next_upstream error timeout;
Context: http, server, location

由于HTTP协议分为头部和包体两部分,对于头部有特定的格式要求,如果上游返回的HTTP头部不符合规范(在error.log日志中可以看到“upstream sent invalid header”字样的log),你可以通过invalid_header选项开启next upstream功能。

另外,服务器需要在内存中缓存完整的HTTP头部,才能决定包体的处理方式,如果上游返回的HTTP头部体积超过了proxy_buffer_size指令设置的值(在error.log日志中可以看到“upstream sent too big header”字样的log),也会遵照invalid_header选项的处理方式。

对于HTTP请求方法而言,如果严格遵照REST架构,那么如GET/HEAD这样获取资源的方法是具备幂等性idempotent(参见RFC7231)的,即无论执行多少次,都会获得相同的结果。PUT方法会整体覆盖资源,DELETE是删除资源,这两个方法也具有幂等性。

对于在语义上具备幂等性的请求,NGINX默认会启动next upstream功能。

然而,POST方法通过FORM表单修改资源属性,PATCH方法以补丁方式修改资源的部分内容,LOCK方法基于WebDAV规范对资源加锁,这3个方法都不具备幂等性,所以NGINX默认并不会对这3个方法启用next upstream机制。

你可以通过proxy_next_upstream中的non_idempotent选项对非幂等方法启用该功能。

官方提供的七层反向代理除了HTTP/1协议外,还有fastcgi、scgi、uwsgi、memcached、grpc这5个模块,它们都使用了TCP协议,且可以与HTTP协议互相转换,因此它们的next upstream与上述HTTP协议的指令极为相似。

唯一的差别在于_next_upstream指令后的选项,我把它们的差别列在下表中:

这里表示纵轴对应的协议具有proxy_next_upstream选项中的功能,而则因为协议做过转换后,不再提供这一选项。

其中,memcached由于不存在HTTP头部,所以通过invalid_response选项表示invalide_header错误,并以not_found表示了与HTTP_404同样的含义。而fastcgi、scgi、uwsgi通常是与本机进程通讯,所以没有502、504这两种与网络密切相关的错误码。


小结

最后对本文内容做个总结。

当NGINX检测到系统调用返回的传输层错误、openssl返回的表示层错误或者协议解码返回的应用层错误时,在逻辑上允许重试的前提下,可以通过next upstream机制更换上游Server,在客户端无感知的情况下完成请求的转发,大大提升了系统可用性。

HTTP/1、gRPC、scgi、fastcgi、uwsgi、memcached等所有NGINX代理模块,都支持next upstream机制,但由于它们基于不同的通讯协议,所以在语法、语义上有不同的表现,这些会反映在_next_upstream指令的选项上。

当你熟悉了1种协议的next upstream工作原理,可以触类旁通地理解其他协议。


更多资源

想要更及时全面地获取NGINX相关的技术干货、互动问答、系列课程、活动资源?请前往NGINX开源社区:

- 官网:nginx.org.cn

- 微信公众号:NGINX郑重宣布对开源社区的全新承诺

- 微信群:https://www.nginx.org.cn/static/pc/images/homePage/QR-code.png?v=1621313354

- B站:NGINX开源社区的个人空间_哔哩哔哩_Bilibili

NGINX怎样隐藏上游错误?相关推荐

  1. nginx反向代理404错误

    最近在项目中配置nginx遇到了404错误: 配置参照官网配置 1.上游配置 2.代理配置3.host配置 整个流程应该很简单,访问gulimall域名默认80,host解析对应的ip,访问内外,ng ...

  2. nginx openresty content_by_lua_file 404错误

    nginx openresty content_by_lua_file 404错误 2018/08/05 19:18:59 [error] 21231#0: *63 failed to load ex ...

  3. nginx File not found 错误(转)

    当我没初始配在lnmp的时候,用浏览器打开查看php能否解析网页的时出现File not found 不用惊奇让我我们分析一下 使用php-fpm解析PHP,"No input file s ...

  4. linux nginx 安装出错,Linux Nginx安装以及可能出现错误

    Linux Nginx安装以及可能出现错误 转载请标明出处 http://coderknock.com安装过程 从 http://nginx.org/download/nginx-1.9.15.tar ...

  5. Nginx加密与上游服务器的TCP网络通信

    Nginx加密与上游服务器的TCP网络通信 本文介绍了如何保护NGINX和TCP上游服务器或TCP服务器上游组之间的TCP通信. 先决条件 NGINX Plus R6和更高版本或使用--with-st ...

  6. Nginx设置TCP上游服务器的SSL配置

    Nginx设置TCP上游服务器的SSL配置 本文介绍了如何为NGINX Plus和接受TCP连接的负载均衡的服务器组设置SSL 什么是SSL终端 SSL终端意味着NGINX Plus充当与客户端连接的 ...

  7. nginx配置与常见错误解决方法

    nginx配置与常见错误解决方法 参考文章: (1)nginx配置与常见错误解决方法 (2)https://www.cnblogs.com/lechie/archive/2011/11/12/2383 ...

  8. NGINX访问日志和错误日志

    Logs are very useful to monitor activities of any application apart from providing you with valuable ...

  9. 报错——Nginx反向代理400错误

    Nginx反向代理400错误 一.Nginx反向代理400错误原因分析及纠错 一.Nginx反向代理400错误原因分析及纠错 实验环境:一台nginx服务器,两台tomcat服务器,实验是进行ngin ...

最新文章

  1. cisco 访问控制列表ACL笔记
  2. 介绍如何安装project 2016方法
  3. Spring实战(三)Spring中装配Bean的三种方式---XML、JavaConfig、AutoWire
  4. c语言求成绩标准差,C程序计算标准偏差
  5. 蓝桥杯 迷宫与陷阱 BFS
  6. 小端字节序和大端字节序
  7. 小米2s安卓10刷机包_小米10刷机包
  8. 线程池的使用和工作原理
  9. JS-JavaScript打开新页面的两种方式:当前页面打开和新页面打开
  10. Win 10 x64 Visual Studio 2019 编译 Detours 4.0.1
  11. 牛学长周年庆活动:软件大促限时抢,注册码免费送!
  12. 从视频马赛克看编码量化参数对流控的作用
  13. DH坐标系的建立与DH表—机器人学
  14. 免实名的域名有吗?域名实名制认证有哪些要求?
  15. 菜的抠脚团队正式成立
  16. DC(Design Compiler)使用说明
  17. linux+dns的acl,DNS服务器之三:DNS高级用法(DNS转发、ACL、智能DNS及日志系统) | 旺旺知识库...
  18. 手机怎么模拟弱网_手游测试之模拟弱网环境测试
  19. 二级c语言考试系统安卓,无忧考吧二级c语言考试系统下载_无忧考吧二级c语言考试系统官方下载-太平洋下载中心...
  20. 设计求任意两个整数和的web程序,用户通过提交页面(input.jsp)输入两个整数,并提交给一个(sum.jsp)程序

热门文章

  1. 完美世界手游 一直显示服务器爆满,完美世界手游爆满区怎么进[多图]
  2. 50套精美的免费 PSD 网页界面素材资源
  3. echarts 获取geoJson数据
  4. 四级作文万能套用模板
  5. HDOJ 献给杭电五十周年校庆的礼物 1290
  6. 今年双十二值得买的数码好物推荐!双十二数码产品抢购攻略
  7. 试题 算法训练 逗志芃的暴走 java实现
  8. SecureCRT 远程登录 Linux(开启 SSH)
  9. 厦门理工学院c语言实验循环,厦门理工学院C语言 实验4_循环结构..doc
  10. 2023考研时间定了!