原文作者:陶辉
原文链接: 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. NameError: name ‘sklearn‘ is not defined的解决方法:
  2. Android基础(三) UI开发 Part 1
  3. iOS 因为reason: 'Pushing the same view controller instance more than once is not supported而奔溃(下)...
  4. apache gobblin mysql_gobblin简单使用
  5. Linux: 不用密码直接用ssh 登入到远端电脑(RAS/DSA认证)
  6. [swustoj 771] 奶牛农场
  7. TYVJ 1014 乘法游戏
  8. mysql上机实验报告3_SQL入门随笔(上机实验报告)
  9. 三大运营商将上线 5G 消息;苹果谷歌联手,追踪 30 亿用户;jQuery 3.5.0 发布 | 极客头条...
  10. Linux操作系统下的多线程编程详细解析----条件变量
  11. Build Apache Module
  12. 360兼容模式页面错位_如何处理网站兼容性
  13. 集成学习与随机森林练习题
  14. vecm模型怎么写系数_时变秩和时变系数VECM模型与“费雪效应”机制检验
  15. 排查线上CPU飙高的原因和解决
  16. 矩阵基础 |共轭转置、单位矩阵、矩阵的迹
  17. 解决IDEA : Could not autowire. No beans of ‘xxxx‘ type found
  18. ios sdk 穿山甲_Creator iOS接入穿山甲SDK
  19. uClinux on Blackfin BF533 STAMP - A DSP Linux Port
  20. 短视频询盘获客系统/源码搭建

热门文章

  1. Unity发布IOS游戏 虚拟Home按键隐藏
  2. 深度学习入门极简教程(二)
  3. python四种占位符例子_Python 占位符格式化的简单示例
  4. 初始化list java_如何在Java中初始化List 对象?
  5. Java自定义泛型类注意点
  6. 雅诗兰黛公司和雅诗兰黛品牌宣布与阿曼达·戈尔曼合作
  7. 电脑表格日期怎么修改原有日期_“电脑表格日期格式怎么改“如何把excel中日期格式设置为2001-01-01...
  8. ​GeForce 和 TITAN 产品​对CUDA的支持对照表
  9. 泰拉瑞亚 游戏角色 Later version
  10. 苹果X 安兔兔HTML测试分数,安兔兔公布 iPhone XS Max 跑分成绩 超 37 万分