10分钟快速认识Nginx
点赞 8
3 条评论
672 次浏览
发表于 : 2020-06-08 21:27

Nginx是当下最流行的Web服务器,通过官方以及第三方C模块,以及在Nginx上构建出的Openresty,或者在Openresty上构建出的Kong,你可以使用Nginx生态满足任何复杂Web场景下的需求。Nginx的性能也极其优秀,它可以轻松支持百万、千万级的并发连接,也可以高效的处理磁盘IO,因而通过静态资源或者缓存,能够为Tomcat、Django等性能不佳的Web应用扛住绝大部分外部流量。

 

但是,很多刚接触Nginx的同学,对它的理解往往失之偏颇,不太清楚Nginx的能力范围。比如:

  • 你可能清楚Nginx对上游应用支持Google的gRPC协议,但对下游的客户端是否支持gRPC协议呢?
  • Openresty中的Nginx版本是单号的,而Nginx官网中的stable稳定版本则是双号的,我们到底该选择哪个版本的Nginx呢?
  • 安装Nginx时,下载Nginx docker镜像,或者用yum/apt-get安装,都比下载源代码再编译出可执行文件要简单许多,那到底有必要基于源码安装Nginx吗?
  • 当你下载完Nginx源码后,你清楚每个目录与文件的意义吗?

 

本文是《从头搭建1个静态资源服务器》系列文章中的第1篇,也是我在6月4日晚直播内容的文字总结,在这篇文章中我将向你演示:Nginx有什么特点,它的能力上限在哪,该如何获取Nginx,Nginx源代码中各目录的意义又是什么。

 

Nginx到底是什么?

Nginx是一个集静态资源负载均衡于一身的Web服务器,这里有3个关键词,我们一一来分析。

 

l    Web

我爱把互联网服务的访问路径,与社会经济中的供应链放在一起做类比,这样很容易理解“上下游”这类比较抽象的词汇。比如,购买小米手机时,实体或者网上店铺是供应链的下游,而高通的CPU则是上游。类似地,浏览器作为终端自然是下游,关系数据库则是上游,而Nginx位于源服务器和终端之间,如下图所示:

                                               image.png

 

弄明白了上下游的概念后,我们就清楚了“Web服务器”的外延:Nginx的下游协议是Web中的HTTP协议,而上游则可以是任意协议,比如python的网关协议uwsgi,或者C/C++爱用的CGI协议,或者RPC服务常用的gRPC协议,等等。

 

在Nginx诞生之初,它的下游协议仅支持HTTP/1协议,但随着版本的不断迭代,现在下游还支持HTTP/2、MAIL邮件、TCP协议、UDP协议等等。

 

Web场景面向的是公网,所以非常强调信息安全。而Nginx对TLS/SSL协议的支持也非常彻底,它可以轻松的对下游或者上游装载、卸载TLS协议,并通过Openssl支持各种安全套件。

 

l    静态资源


Web服务器必须能够提供图片、Javascript、CSS、HTML等资源的下载能力,由于它们多数是静态的,所以通常直接存放在磁盘上。Nginx很擅长读取本机磁盘上的文件,并将它们发送至下游客户端!你可能会觉得,读取文件并通过HTTP协议发送出去,这简直不要太简单,Nginx竟然只是擅长这个?这里可大有文章!

 

比如,你可能了解零拷贝技术,Nginx很早就支持它,这使得发送文件时速度可以至少提升一倍!可是,零拷贝对于特大文件很不友好,占用了许多PageCache内存,但使用率却非常低,因此Nginx用Linux的原生异步IO加上直接IO解决了这类问题。再比如,小报文的发送降低了网络传输效率,而Nginx通过Nagle、Cork等算法,以及应用层的postpone_out指令批量发送小报文,这使得Nginx的性能远远领先于Tomcat、Netty、Apache等竞争对手,因此主流的CDN都是使用Nginx实现的。

 

l    负载均衡


在分布式系统中,用加机器扩展系统,是提升可用性的最有效方法。但扩展系统时,需要在应用服务前添加1个负载均衡服务,使它能够将请求流量分发给上游的应用。这一场景中,除了对负载均衡服务的性能有极高的要求外,它还必须能够处理应用层协议。在OSI网络体系中,IP网络层是第3层,TCP/UDP传输层是第4层,而HTTP等应用层则是第7层,因此,在Web场景中,需求量最大的自然是7层负载均衡,而Nginx非常擅长应用层的协议处理,这体现在以下4个方面:


1.      通过多路复用、事件驱动等技术,Nginx可以轻松支持C10M级别的并发;

2.      由C语言编写,与操作系统紧密结合的Nginx(紧密结合到什么程度呢?Nginx之父Igor曾经说过,他最后悔的就是让Nginx支持windows操作系统,因为它与类Unix系统差异太大,这使得紧密结合的Nginx必须付出很大代价才能实现),能够充分使用CPU、内存等硬件,极高的效率使它可以同时为几十台上游服务器提供负载均衡功能;

3.      Nginx的架构很灵活,它允许任何第三方以C模块的形式,与官方模块互相协作,给用户提供各类功能。因此,丰富的生态使得Nginx支持多种多样的应用层协议(你可以在Github上搜索到大量的C模块),你也可以直接开发C模块定制Nginx。

4.      Nginx使用了非常开放的2-clause BSD-like license源码许可协议,它意味着你在修改Nginx源码后,还可以作为商业用途发布,TEngine就受益于这一特性。当Lua语言通过C模块注入Nginx后,就诞生了Openresty及一堆Lua语言模块,这比直接开发C语言模块难度下降了很多。而在Lua语言之上,又诞生了Kong这样面向微服务的生态。

 

从上述3个关键词的解释,我相信你已经明白了Nginx的能力范围。接下来,我们再来看看如何安装Nginx。

 

怎样获取Nginx?


Nginx有很多种获取、安装的方式,我把它们分为以下两类:


l    非定制化安装


主要指下载编译好的二进制文件,再直接安装在目标系统中,比如:

u    拉取含有Nginx的docker镜像;

u    在操作系统的应用市场中直接安装,比如用apt-get/yum命令直接安装Nginx;

u    获取到网上编译好的Nginx压缩包后,解压后直接运行;

 

l    定制化安装


http://nginx.org/en/download.html上或者https://www.nginx-cn.net/product上下载Nginx源代码,调用configure脚本生成定制化的编译选项后,执行make命令编译生成可执行文件,最后用make install命令安装Nginx。

 

非定制化安装虽然更加简单,但这样的Nginx默认缺失以下功能:


u    不支持更有效率的HTTP2协议;

u    不支持TCP/UDP协议,不能充当4层负载均衡;

u    不支持TLS/SSL协议,无法跨越公网保障网络安全;

u    未安装stub_status模块,无法实时监控Nginx连接状态;

你可以通过configure –help命令给出的--with-XXX-module说明,找到Nginx默认不安装的官方模块,例如:(dynamic是动态模块,在后续文章中我会演示其用法)

--with-http_ssl_module             enable ngx_http_ssl_module

--with-http_v2_module enable ngx_http_v2_module

--with-http_realip_module enable ngx_http_realip_module

--with-http_addition_module enable ngx_http_addition_module

--with-http_xslt_module enable ngx_http_xslt_module

--with-http_xslt_module=dynamic enable dynamic ngx_http_xslt_module

--with-http_image_filter_module enable ngx_http_image_filter_module

--with-http_image_filter_module=dynamic enable dynamic ngx_http_image_filter_module

--with-http_geoip_module enable ngx_http_geoip_module

--with-http_geoip_module=dynamic enable dynamic ngx_http_geoip_module

--with-http_sub_module enable ngx_http_sub_module

--with-http_dav_module enable ngx_http_dav_module

--with-http_flv_module enable ngx_http_flv_module

--with-http_mp4_module enable ngx_http_mp4_module

--with-http_gunzip_module enable ngx_http_gunzip_module

--with-http_gzip_static_module enable ngx_http_gzip_static_module

--with-http_auth_request_module enable ngx_http_auth_request_module

--with-http_random_index_module enable ngx_http_random_index_module

--with-http_secure_link_module enable ngx_http_secure_link_module

--with-http_degradation_module enable ngx_http_degradation_module

--with-http_slice_module enable ngx_http_slice_module

--with-http_stub_status_module enable ngx_http_stub_status_module


因此,从功能的全面性上来说,我们需要从源码上安装Nginx。

 

你可能会想,那为什么不索性将所有模块都编译到默认的Nginx中呢?按需编译模块,至少有以下4个优点:


u    执行速度更快。例如,通过配置文件关闭功能,就需要多做一些条件判断。

u    减少nginx可执行文件的大小。

u    有些模块依赖项过多,在非必要时启用它们,会增加编译、运行环境的复杂性。

u    给用户提供强大的自定义功能,比如在configure时设定配置文件、pid文件、可执行文件的路径,根据实际情况重新指定编译时的优化参数等等。

 

当然,最重要的还是可以通过configure --add-module选项任意添加自定义模块,这赋予Nginx无限的可能。

 

由于Nginx有许多分支和版本,该如何选择适合自己的版本呢?这有两个技巧,我们先来看mainline和stable版本的区别,在http://nginx.org/en/download.html上你会看到如下页面:

image.png


这里,mainline是含有最新功能的主线版本,它的迭代速度最快。另外,你可能注意到mainline是单号版本,而Openresty由于更新Nginx的频率较低,所以为了获得最新的Nginx特性,它通常使用mainline版本。

 

stable是mainline版本稳定运行一段时间后,将单号大版本转换为双号的稳定版本,比如1.18.0就是由1.17.10转换而来。

 

Legacy则是曾经的稳定版本。如果从头开始使用Nginx,那么你只需要选择最新的stable或者mainline版本就可以了。但如果你已经在使用某一个Legacy版本的Nginx,现在是否把它升级到最新版本呢?毕竟在生产环境上升级前要做完整的功能、性能测试,成本并不低。此时,我们要从CHANGES变更文件中,寻找每个版本的变化点。点开CHANGES文件,你会看到如下页面:

image.png


这里列出了每个版本的发布时间,以及发布时的变更。这些变更共分为以下4类:

u    Feature新功能,比如上图HTTP框架新增的auth_delay指令。

u    Bugfix问题修复,我们尤其要关注一些重大Bug的修复。

u    Change已知特性的变更,比如之前允许HTTP请求头部中出现多个Host头部,但在1.17.9这个Change后,就认定这类HTTP请求非法了。

u    Security安全问题的升级,比如1.15.6版本就修复了CVE-2018-16843等3个安全问题。

 

从Feature、Bugfix、Change、Security这4个方面,我们就可以更有针对性的升级Nginx。

 

认识Nginx的源码目录


当获取到Nginx源码压缩包并解压后,你可能对这些目录一头雾水,这里我对它们做个简单说明。比如1.18.0版本的源代码目录是这样的:

image.png

 

其中包含5个文件和5个目录,我们先来看单个文件的意义:


u    CHANGES:即上面介绍过的版本变更文件。

u    CHANGES.ru:由于Igor是俄罗斯人,所以除了上面的英文版变更文件外,还有个俄文版的变更文件。

u    configure:如同其他Linux源码类软件一样,这是编译前的必须执行的核心脚本,它包含下面4个子功能:

Ø  解析configure脚本执行时传入的各种参数,包括定制的第三方模块;

Ø  针对操作系统、体系架构、编译器的特性,生成特定的编译参数;

Ø  生成Makefile、ngx_modules.c等文件;

Ø  在屏幕上显示汇总后的执行结果。

u    LICENSE:这个文件描述了Nginx使用的2-clause BSD-like license许可协议。

u    README:它只是告诉你去使用http://nginx.org官网查询各模块的用法。

 

再来看各个目录的意义:


u    auto:configure只是一个简单的入口脚本,真正的功能是由auto目录下各个脚本完成的。

u    conf:当安装完Nginx后,conf目录下会有默认的配置文件,这些文件就是从这里的conf目录复制过去的。

u    contrib:包含了Nginx相关的周边小工具,比如下一讲将要介绍vim中如何高亮显示Nginx语法,就依赖于其中的vim子目录。

u    html:安装完Nginx并运行后,会显示默认的欢迎页面,以及出现错误的500页面,这两个页面就是由html目录拷贝到安装目录的。

u    man:目录中仅包含nginx.8一个文件,它其实是为Linux系统准备的man帮助文档,使用man -l nginx.8命令,可以看到Nginx命令行的使用方法:


image.png

u    src:放置所有Nginx源代码的目录。关于src下的子目录,后续我分析源码时再详细介绍它们。

 

以上就是官方压缩包解压后的内容,当你执行完configure脚本后,还会多出Makefile文件以及objs目录,在下一篇文章中我会介绍它们。

 

小结

最后,对《从头搭建静态资源服务器》系列第1篇做个总结。

 

Nginx是集静态资源与负载均衡与一身的Web服务器,它支持C10M级别的并发连接,也通过与操作系统的紧密结合,能够高效的使用系统资源。除性能外,Nginx通过优秀的模块设计,允许第三方的C模块、Lua模块等嵌入到Nginx中运行,这极大丰富了Nginx生态。

 

下载源码编译安装Nginx,可以获得定制Nginx的能力。这样不仅用助于性能的提升,还通过各类模块扩展了Nginx的功能。

 

Nginx源代码中有5个文件和5个一级目录,其中configure脚本极为关键,在它执行后,还会生成Makefile文件和objs目录,它们与定制化的模块、系统的高性能参数密切相关,此后才能正式编译Nginx。

 

下一篇,我将在《如何configure定制出属于你的Nginx》一文中介绍configure脚本的用法,配置文件的语法格式,以及如何配置出静态资源服务。


如果您觉得不错,就打赏支持一下吧~

打赏
3 条评论
Radio 2020-06-14 23:02
谢谢
点赞 0
回复
nigel 2020-08-05 18:13

谢谢。。请问那里有nginx第三方模块的文档。。必须去第三方模块的官网吗?

点赞 1
回复
陶辉 回复 nigel 2020-09-09 15:53
论坛中就有:https://www.nginx-cn.net/product/plug/
点赞 0
回复

要回复文章请先登录注册

关于作者
回答 64
文章 12
粉丝 52
相关推荐
互联网的全球化导致了互联网的数据量快速增长,加上在本世纪初摩尔定律在单核 CPU 上的失效,CPU 朝着多核方向发展,而 Apache 显然并没有做好多核架构的准备,它的一个进程同一时间只能处理一个连接,处理完一个请求后才能处理下一个,这无疑不能应对如今互联网上海量的用户。况且进程间切换的成本是非常高的。在这种背景下,Nginx 应运而生,可以轻松处理数百万、上千万的连接。高并发高性能可扩展性好高可靠性热部署开源许可证静态资源服务,通过本地文件系统提供服务反向代理服务、负载均衡API服务、权限控制,减少应用服务器压力通过 rpm -ql nginx 可以查看 Nginx 安装的配置文件和目录。/etc/nginx/nginx.conf 核心配置文件/etc/nginx/conf.d/default.conf 默认http服务器配置文件/etc/nginx/fastcgi_params fastcgi配置/etc/nginx/scgi_params scgi配置/etc/nginx/uwsgi_params uwsgi配置/etc/nginx/koi-utf/etc/nginx/koi-win/etc/nginx/win-utf 这三个文件是编码映射文件,因为作者是俄国人/etc/nginx/mime.types 设置HTTP协议的Content-Type与扩展名对应关系的文件/usr/lib/systemd/system/nginx-debug.service/usr/lib/systemd/system/nginx.service/etc/sysconfig/nginx/etc/sysconfig/nginx-debug 这四个文件是用来配置守护进程管理的/etc/nginx/modules 基本共享库和内核模块/usr/share/doc/nginx-1.18.0 帮助文档/usr/share/doc/nginx-1.18.0/COPYRIGHT 版权声明/usr/share/man/man8/nginx.8.gz 手册/var/cache/nginx Nginx的缓存目录/var/log/nginx Nginx的日志目录/usr/sbin/nginx 可执行命令/usr/sbin/nginx-debug 调试执行可执行命令正向代理 Forward proxy反向代理 Reverse proxy跨域Gzip请求限制关于请求限制主要有两种类型:访问控制-http_access_module 基于 IP 的访问控制-http_auth_basic_module 基于用户的信任登陆以下是基于 IP 的访问控制:server { location ~ ^/index.html { # 匹配 index.html 页面 除了 127.0.0.1 以外都可以访问 deny 127.0.0.1; allow all; }}ab命令全称为:Apache bench,是 Apache 自带的压力测试工具,也可以测试 Nginx、IIS 等其他 Web 服务器。防盗链负载均衡 Load BalanceNginx 可以为我们提供负载均衡的能力,具体配置如下:# upstream 指定后端服务器地址# weight 设置权重# server 中会将 http://webcanteen 的请求转发到 upstream 池中upstream webcanteen { server 127.0.0.1:66 weight=10; server 127.0.0.1:77 weight=1; server 127.0.0.1:88 weight=1;}server { location / { proxy_pass http://webcanteen }}后端服务器支持以下的状态配置:分配方式转载:https://segmentfault.com/a/1190000023730234?utm_source=sf-related 
前言nginx系列之一:nginx入门nginx系列之二:配置文件解读nginx系列之三:日志配置nginx系列之四:web服务器nginx系列之五: 负载均衡nginx系列之六:cache服务nginx系列之七:限流配置nginx系列之八:使用upsync模块实现负载均衡转自:在此感谢原博主的整理分享使用nginx做负载均衡的两大模块:upstream 定义负载节点池。location 模块 进行URL匹配。proxy模块 发送请求给upstream定义的节点池。一、upstream模块解读nginx 的负载均衡功能依赖于 ngx_http_upstream_module模块,所支持的代理方式有 proxy_pass(一般用于反向代理),fastcgi_pass(一般用于和动态程序交互),memcached_pass,proxy_next_upstream,fastcgi_next_pass,memcached_next_pass 。upstream 模块应该放于http{}标签内。模块写法:upstream backend { ip_hash; server backend1.example.com weight=5; server backend2.example.com:8080; server backup1.example.com:8080 backup; server backup2.example.com:8080 backup;}1234567实例一:upstream dynamic { zone upstream_dynamic 64k; server backend1.example.com weight=5; server backend2.example.com:8080 fail_timeout=5s slow_start=30s; server 192.0.2.1 max_fails=3; server backend3.example.com resolve; server backup1.example.com:8080 backup; server backup2.example.com:8080 backup;}1234567891011语法解释:1.1 nginx默认支持四种调度算法轮询(rr),每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器故障,故障系统自动清除,使用户访问不受影响。轮询权值(weight),weight值越大,分配到的访问几率越高,主要用于后端每个服务器性能不均的情况。ip_hash,每个请求按访问IP的hash结果分配,这样来自同一个IP的固定访问一个后端服务器,主要解决动态网站session共享的问题。url_hash,按照访问的URL的hash结果来分配请求,是每个URL定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率,nginx本身不支持,如果想使用需要安装nginx的hash软件包。fair,这个算法可以依据页面大小和加载时间长短智能的进行负载均衡,也就是根据后端服务器的响应时间来分配请求,相应时间短的优先分配,默认不支持,如果想使用需要安装upstream_fail模块。least_conn 最少链接数,那个机器连接数少就分发。1.2 server模块的写法server IP 调度状态1server指令指定后端服务器IP地址和端口,同时还可以设定每个后端服务器在负载均衡调度中的状态。down 表示当前的server暂时不参与负载均衡。backup 预留的备份服务器,当其他所有的非backup服务器出现故障或者忙的时候,才会请求backup机器,因为这台集群的压力最小。max_fails 允许请求失败的次数,默认是1,当超过最大次数时,返回proxy_next_upstream模块定义的错误。0表示禁止失败尝试,企业场景:2-3.京东1次,蓝汛10次,根据业务需求去配置。fail_timeout,在经历了max_fails次失败后,暂停服务的时间。京东是3s,蓝汛是3s,根据业务需求配置。常规业务2-3秒合理。例:如果max_fails是5,他就检测5次,如果五次都是502.那么,他就会根据fail_timeout 的值,等待10秒,再去检测。server 如果接域名,需要内网有DNS服务器,或者在负载均衡器的hosts文件做域名解析。server后面还可以直接接IP或IP加端口。1.3 长连接 keepaliveupstream backend { server backend2.example.com:8080; server backup1.example.com:8080 backup; keepalive 100;}12345通过该指令配置了每个worker进程与上游服务器可缓存的空闲连接的最大数量。当超出这个数量时,最近最少使用的连接将被关闭。keepalive指令不限制worker进程与上游服务器的总连接。location / { # 支持keep-alive proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://backup;}123456如果是http/1.0 需要配置发送"Connection: Keep-Alive" 请求头。上游服务器不要忘记开启长连接支持。连接池配置建议总长连接数是"空闲连接池"+"释放连接池"的长连接总数。首先,长连接配置不会限制worker进程可以打开的总连接数(超了的作为短连接)。另外连接池一定要根据场景合理进行设置。空闲连接池太小,连接不够用,需要不断建连接。空闲连接池太大,空闲连接太多,还没使用就超时。建议只对小报文开启长连接。二、location 模块解读location作用:基于一个指令设置URI。2.1 基本语法:Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }location @name { ... }Default: —Context: server, location1234= 精确匹配,如果找到匹配=号的内容,立即停止搜索,并立即处理请求(优先级最高)~ 区分大小写~* 不区分大小写^~ 只匹配字符串,不匹配正则表达式@ 指定一个命名的location,一般用于内部重定义请求,location @name {…}匹配是有优先级的,不是按照nginx的配置文件进行。官方的例子:location = / { [ configuration A ]}location / { [ configuration B ]}location /documents/ { [ configuration C ]}location ^~ /images/ { [ configuration D ]}location ~* \.(gif|jpg|jpeg)$ { [ configuration E ]}123456789101112131415结论:/ 匹配A。/index.html 匹配B/documents/document.html 匹配C/images/1.gif 匹配D/documents/1.jpg 匹配的是E。2.1 测试用的例子:location / { return 401; } location = / { return 402; } location /documents/ { return 403; } location ^~ /images/ { return 404; } location ~* \.(gif|jpg|jpeg)$ { return 500; }123456789101112131415测试结果(重点看):[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/402[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/index.html401[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/documents/document.html 403[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/images/1.gif404[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/dddd/1.gif 50012345678910结果总结:匹配的优先顺序,=>^~(匹配固定字符串,忽略正则)>完全相等>~*>空>/ 。工作中尽量将’=‘放在前面三、proxy_pass 模块解读proxy_pass 指令属于ngx_http_proxy_module 模块,此模块可以将请求转发到另一台服务器。写法:proxy_pass http://localhost:8000/uri/; 1实例一: upstream blog_real_servers { server 10.0.0.9:80 weight=5; server 10.0.0.10:80 weight=10; server 10.0.0.19:82 weight=15; } server { listen 80; server_name blog.etiantian.org; location / { proxy_pass http://blog_real_servers; proxy_set_header host $host; } }12345678910111213proxy_set_header:当后端Web服务器上也配置有多个虚拟主机时,需要用该Header来区分反向代理哪个主机名,proxy_set_header host $host;。proxy_set_header X-Forwarded-For :如果后端Web服务器上的程序需要获取用户IP,从该Header头获取。proxy_set_header X-Forwarded-For $remote_addr;3.1 配置后端服务器接收前端真实IP配置如下: log_format commonlog ‘$remote_addr - $remote_user [$time_local] "$request" ‘ ‘$status $body_bytes_sent "$http_referer" ‘ ‘"$http_user_agent" "$http_x_forwarded_for"‘;123rs_apache节点的httpd.conf配置LogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined修改日志记录apacheLogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b" common12343.2 proxy_pass相关的优化参数client_max_body_size 10m; 允许客户端请求的最大的单个文件字节数。client_body_buffer_size 128k; 缓冲区代理缓冲用户端请求的最大字节数 可以理解为先保存到本地再传给用户。proxy_connect_timeout 600; 跟后端服务器连接的超时时间_发起握手等候响应超时时间。proxy_read_timeout 600; 连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理。proxy_send_timeout 600; 后端服务器回传数据时间,就是在规定时间之内后端服务器必须传完所有的数据。proxy_buffer_size 8k; 代理请求缓存区,这个缓存区间会保存用户的头信息以供Nginx进行规则处理,一般只要设置能保存下头信息即可。proxy_buffers 4 32k; 同上 告诉Nginx保存单个页面使用的空间大小,假设网页大小平均在32k以下的话。proxy_busy_buffers_size 64k; 如果系统很忙的时候可以申请更大的proxy_buffers 官方推荐(proxy_buffers*2)。proxy_max_temp_file_size 1024m; 当 proxy_buffers 放不下后端服务器的响应内容时,会将一部分保存到硬盘的临时文件中,这个值用来设置最大临时文件大小,默认1024M,它与 proxy_cache 没有关系。大于这个值,将从upstream服务器传回。设置为0禁用。proxy_temp_file_write_size 64k; proxy缓存临时文件的大小 proxy_temp_path(可以在编译的时候)指定写到哪那个目录。四、健康检查Nginx提供了health_check语句来提供负载(upstream)时的键康检查机制(注意:此语句需要设置在location上下文中)。支持的参数有:interval=time:设置两次健康检查之间的间隔值,默认为5秒fails=number:设置将服务器视为不健康的连续检查次数,默认为1次passes=number:设置一个服务器被视为健康的连续检查次数,默认为1次uri=uri:定义健康检查的请求URI,默认为”/“match=name:指定匹配配置块的名字,用记测试响应是否通过健康检测。默认为测试返回状态码为2xx和3xx一个简单的设置如下,将使用默认值:location / { proxy_pass http://backend; health_check;}1234对就应用,我们可以专门定义一个API用于健康检查:/api/health_check,并只返回HTTP状态码为200。并设置两次检查之间的间隔值为1秒。这样,health_check语句的配置如下:health_check uri="/api/health_check" interval;1匹配match的方法http { server { ... location / { proxy_pass http://backend; health_check match=welcome; } } match welcome { status 200; header Content-Type = text/html; body ~ "Welcome to nginx!"; }}123456789101112131415match 例子举例status 200;: status 等于 200status ! 500;: status 不是 500status 200 204;: status 是 200 或 204status ! 301 302;: status 不是301或302。status 200-399;: status 在 200 到 399之间。status ! 400-599;: status 不在 400 到 599之间。status 301-303 307;: status 是 301, 302, 303, 或 307。header Content-Type = text/html;: “Content-Type” 得值是 text/html。header Content-Type != text/html;: “Content-Type” 不是 text/html。header Connection ~ close;: “Connection” 包含 close。header Connection !~ close;: “Connection” 不包含 close。header Host;: 请求头包含 “Host”。header ! X-Accel-Redirect;: 请求头不包含 “X-Accel-Redirect”。body ~ "Welcome to nginx!";: body 包含 “Welcome to nginx!”。body !~ "Welcome to nginx!";: body 不包含 “Welcome to nginx!”。五、一个完整的nginx实例[root@lb01 conf]# cat nginx.confworker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; #blog lb by oldboy at 201303 upstream blog_real_servers { server 10.0.0.9:80 weight=1 max_fails=1 fail_timeout=10s; server 10.0.0.10:80 weight=1 max_fails=2 fail_timeout=20s; } server { listen 80; server_name blog.etiantian.org; location / { proxy_pass http://blog_real_servers; include proxy.conf; } }}[root@lb01 conf]# cat proxy.conf proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k;12345678910111213141516171819202122232425262728293031323334六、扩展补充6.1 只允许使用GET,HEAD,POST方法去请求## Only allow these request methods ## if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; }1234七、实战7.1 根据URI及location实现动静分离。最终实现:/static/的URL都去访问10.0.0.9。/dynamic/的URL都去访问10.0.0.10。图片这些静态文件去访问10.0.0.9。/upload/的URL都去访问10.0.0.10。[root@lb01 conf]# cat nginx.confworker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; #blog lb by oldboy at 201303 upstream static_pools { server 10.0.0.9:80; } upstream dynamic_pools { server 10.0.0.10:80; } upstream upload_pools { server 10.0.0.9:80; } server { listen 80; server_name blog.biglittleant.cn; location / { proxy_pass http://static_pools; include proxy.conf; } location /static/ { proxy_pass http://static_pools; include proxy.conf; } location ~* \.(gif|jpg|jpeg)$ { proxy_pass http://static_pools; include proxy.conf; } location /dynamic/ { proxy_pass http://dynamic_pools; include proxy.conf; } location /upload/ { proxy_pass http://upload_pools; include proxy.conf; } }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950517.2 实现苹果手机和安卓手机访问不同的地址server { listen 80; server_name blog.etiantian.org; location / { if ($http_user_agent ~* "android") { proxy_pass http://android_pools; } if ($http_user_agent ~* "iphone") { proxy_pass http://iphone_pools; } proxy_pass http://pc_pools; include extra/proxy.conf; } access_log off; }1234567891011121314151617参考文档nginx-proxy_pass官网 
通常使用的nginx负载均衡技术, 在网络分层中处于应用层(第七层)的,nginx与客户端建立TCP连接(握手),然后再根据请求信息以及本地配置信息,将请求灵活的分发到不同的服务上。nginx这类7层负载均衡的优缺点都很明显。除了nginx这种7层负载均衡策略,还有基于传输层(4层)的负载均衡策略。通过分析请求的IP地址以及端口号进行请求的负载均衡。根据请求处理的模式不同4层负载均衡算法可以分为:NAT,D-NAT,DR以及TUN隧道技术等。4层负载均衡的实现方式有:LVS。负载模式NAT(Network Address Translation,网络地址转换)技术,在专用内部网络中,分配一台实现了NAT技术的路由或服务Load Balance Service。这台负载均衡服务器分配了公网IP(VIP, Virtual IP),所有客户端请求服务都请求此IP。LBS通过不同的算法,将请求数据包的源IP以及目标IP修改,转发到真实服务器(Real Service)上进行业务处理。其具体的步骤可以分为:1、客户端发送请求,源IP为:CIP,目标IP为:VIP3、RS 接收到请求,进行业务处理。并将结果返回给LBS。注意:连接是建立在 客户端 与 Real Service 之间,LBS只是解析数据包IP和端口号,进行修改转发而已。DRDR(Direct Routing, 直接路由模式),LB通过修改请求数据包的目标MAC地址,并且在Real Service服务配置只有自己可见的lVIP,实现数据包的接收(自己没有VIP的话,服务并不会接收数据包)。整个转发的流程为:DR模式下,LB只接收请求进行转发,响应数据有Real Service直接发送给客户端,降低了LB的压力。但是NAT和DR都需要LB和Real Service处于同一网段,无法将RS部署在不同的机房。客户端发送请求,SIP:CIP;TIP:VIPLB接收到请求,在数据包外在封装一层IP Tunnel,其SIP为DIP,TIP为RIP3.Real Service接收到请求,根据IP Tunnel 的IP地址判断出是发送给自己的请求,进行后续处理。根据源数据包的IP地址,判断出客户端的地址为CIP,并且VIP也是自己配置的IP,对数据进行业务处理后,通过VIP将响应数据直接发送到客户端。LB虽然没有完全解析数据包无法得知请求信息,但是可以通过监听请求头信息(例如,SYN、FIN等)判断客户端与Real Service之间的连接情况。LB通过监听请求信息,维护了各个Real Service的连接信息表。通过这些信息实现不同的调度算法进行负载均衡。RR 轮询WRR 加权轮询DH 目标地址哈希SH 源地址哈希动态调度策略LB将请求转发至连接最少的RS上LB通过加权轮询以及RS的连接情况来转发请求LB维护目标IP到一台RS的映射表(目标IP最近使用的RS),通过映射表将请求转发至RS,若RS不存在或者超载,通过 最少连接 策略选出一台新的RS进行转发LB维护目标IP到一组RS的映射表(目标IP最近使用的RS),通过最少连接策略从服务器组选择一个RS进行转发,若RS不存在或者超载,通过 最少连接 策略选出一台新的RS进行转发,并将此RS加入映射组中。
PDF课件是放在github上的,地址是https://github.com/russelltao/geektime-nginx  因为github对国内网络不太稳定,有同学在微信群里请我发下课件,干脆我把6个PDF课件放到这篇文章的附件里,需要的同学请下载取用。   Nginx核心知识100讲-第一部分课件.pdf  Nginx核心知识100讲-第二部分课件.pdf  Nginx核心知识100讲-第三部分课件.pdf  Nginx核心知识100讲-第四部分课件.pdf  Nginx核心知识100讲-第五部分课件.pdf  Nginx核心知识100讲-第六部分课件.pdf 
在实时性要求较高的特殊场景下,简单的UDP协议仍然是我们的主要手段。UDP协议没有重传机制,还适用于同时向多台主机广播,因此在诸如多人会议、实时竞技游戏、DNS查询等场景里很适用,视频、音频每一帧可以允许丢失但绝对不能重传,网络不好时用户可以容忍黑一下或者声音嘟一下,如果突然把几秒前的视频帧或者声音重播一次就乱套了。使用UDP协议作为信息承载的传输层协议时,就要面临反向代理如何选择的挑战。通常我们有数台企业内网的服务器向客户端提供服务,此时需要在下游用户前有一台反向代理服务器做UDP包的转发、依据各服务器的实时状态做负载均衡,而关于UDP反向代理服务器的使用介绍网上并不多见。本文将讲述udp协议的会话机制原理,以及基于nginx如何配置udp协议的反向代理,包括如何维持住session、透传客户端ip到上游应用服务的3种方案等。UDP协议简介许多人眼中的udp协议是没有反向代理、负载均衡这个概念的。毕竟,udp只是在IP包上加了个仅仅8个字节的包头,这区区8个字节又如何能把session会话这个特性描述出来呢?图1 UDP报文的协议分层在TCP/IP或者 OSI网络七层模型中,每层的任务都是如此明确:物理层专注于提供物理的、机械的、电子的数据传输,但这是有可能出现差错的;数据链路层在物理层的基础上通过差错的检测、控制来提升传输质量,并可在局域网内使数据报文跨主机可达。这些功能是通过在报文的前后添加Frame头尾部实现的,如上图所示。每个局域网由于技术特性,都会设置报文的最大长度MTU(Maximum Transmission Unit),用netstat -i(linux)命令可以查看MTU的大小:  而IP网络层的目标是确保报文可以跨广域网到达目的主机。由于广域网由许多不同的局域网,而每个局域网的MTU不同,当网络设备的IP层发现待发送的数据字节数超过MTU时,将会把数据拆成多个小于MTU的数据块各自组成新的IP报文发送出去,而接收主机则根据IP报头中的Flags和Fragment Offset这两个字段将接收到的无序的多个IP报文,组合成一段有序的初始发送数据。IP报头的格式如下图所示:图2 IP报文头部IP协议头(本文只谈IPv4)里最关键的是Source IP Address发送方的源地址、Destination IP Address目标方的目的地址。这两个地址保证一个报文可以由一台windows主机到达一台linux主机,但并不能决定一个chrome浏览的GET请求可以到达linux上的nginx。4、传输层主要包括TCP协议和UDP协议。这一层最主要的任务是保证端口可达,因为端口可以归属到某个进程,当chrome的GET请求根据IP层的destination IP到达linux主机时,linux操作系统根据传输层头部的destination port找到了正在listen或者recvfrom的nginx进程。所以传输层无论什么协议其头部都必须有源端口和目的端口。例如下图的UDP头部:图3 UDP的头部TCP的报文头比UDP复杂许多,因为TCP除了实现端口可达外,它还提供了可靠的数据链路,包括流控、有序重组、多路复用等高级功能。由于上文提到的IP层报文拆分与重组是在IP层实现的,而IP层是不可靠的所有数组效率低下,所以TCP层还定义了MSS(Maximum Segment Size)最大报文长度,这个MSS肯定小于链路中所有网络的MTU,因此TCP优先在自己这一层拆成小报文避免的IP层的分包。而UDP协议报文头部太简单了,无法提供这样的功能,所以基于UDP协议开发的程序需要开发人员自行把握不要把过大的数据一次发送。对报文有所了解后,我们再来看看UDP协议的应用场景。相比TCP而言UDP报文头不过8个字节,所以UDP协议的最大好处是传输成本低(包括协议栈的处理),也没有TCP的拥塞、滑动窗口等导致数据延迟发送、接收的机制。但UDP报文不能保证一定送达到目的主机的目的端口,它没有重传机制。所以,应用UDP协议的程序一定是可以容忍报文丢失、不接受报文重传的。如果某个程序在UDP之上包装的应用层协议支持了重传、乱序重组、多路复用等特性,那么他肯定是选错传输层协议了,这些功能TCP都有,而且TCP还有更多的功能以保证网络通讯质量。因此,通常实时声音、视频的传输使用UDP协议是非常合适的,我可以容忍正在看的视频少了几帧图像,但不能容忍突然几分钟前的几帧图像突然插进来:-)UDP协议的会话保持机制有了上面的知识储备,我们可以来搞清楚UDP是如何维持会话连接的。对话就是会话,A可以对B说话,而B可以针对这句话的内容再回一句,这句可以到达A。如果能够维持这种机制自然就有会话了。UDP可以吗?当然可以。例如客户端(请求发起者)首先监听一个端口Lc,就像他的耳朵,而服务提供者也在主机上监听一个端口Ls,用于接收客户端的请求。客户端任选一个源端口向服务器的Ls端口发送UDP报文,而服务提供者则通过任选一个源端口向客户端的端口Lc发送响应端口,这样会话是可以建立起来的。但是这种机制有哪些问题呢?问题一定要结合场景来看。比如:1、如果客户端是windows上的chrome浏览器,怎么能让它监听一个端口呢?端口是会冲突的,如果有其他进程占了这个端口,还能不工作了?2、如果开了多个chrome窗口,那个第1个窗口发的请求对应的响应被第2个窗口收到怎么办?3、如果刚发完一个请求,进程挂了,新启的窗口收到老的响应怎么办?等等。可见这套方案并不适合消费者用户的服务与服务器通讯,所以视频会议等看来是不行。有其他办法么?有!如果客户端使用的源端口,同样用于接收服务器发送的响应,那么以上的问题就不存在了。像TCP协议就是如此,其connect方的随机源端口将一直用于连接上的数据传送,直到连接关闭。这个方案对客户端有以下要求:不要使用sendto这样的方法,几乎任何语言对UDP协议都提供有这样的方法封装。应当先用connect方法获取到socket,再调用send方法把请求发出去。这样做的原因是既可以在内核中保存有5元组(源ip、源port、目的ip、目的端口、UDP协议),以使得该源端口仅接收目的ip和端口发来的UDP报文,又可以反复使用send方法时比sendto每次都上传递目的ip和目的port两个参数。对服务器端有以下要求:不要使用recvfrom这样的方法,因为该方法无法获取到客户端的发送源ip和源port,这样就无法向客户端发送响应了。应当使用recvmsg方法(有些编程语言例如python2就没有该方法,但python3有)去接收请求,把获取到的对端ip和port保存下来,而发送响应时可以仍然使用sendto方法。 接下来我们谈谈nginx如何做udp协议的反向代理。Nginx的stream系列模块核心就是在传输层上做反向代理,虽然TCP协议的应用场景更多,但UDP协议在Nginx的角度看来也与TCP协议大同小异,比如:nginx向upstream转发请求时仍然是通过connect方法得到的fd句柄,接收upstream的响应时也是通过fd调用recv方法获取消息;nginx接收客户端的消息时则是通过上文提到过的recvmsg方法,同时把获取到的客户端源ip和源port保存下来。我们先看下recvmsg方法的定义:ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);相对于recvfrom方法,多了一个msghdr结构体,如下所示:struct msghdr {             void         *msg_name;       /* optional address */             socklen_t     msg_namelen;    /* size of address */             struct iovec *msg_iov;        /* scatter/gather array */             size_t        msg_iovlen;     /* # elements in msg_iov */             void         *msg_control;    /* ancillary data, see below */             size_t        msg_controllen; /* ancillary data buffer len */             int           msg_flags;      /* flags on received message */ };其中msg_name就是对端的源IP和源端口(指向sockaddr结构体)。以上是C库的定义,其他高级语言类似方法会更简单,例如python里的同名方法是这么定义的:(data, ancdata, msg_flags, address) = socket.recvmsg(bufsize[, ancbufsize[, flags]])其中返回元组的第4个元素就是对端的ip和port。配置nginx为UDP反向代理服务以上是nginx在udp反向代理上的工作原理。实际配置则很简单:# Load balance UDP-based DNS traffic across two servers stream {             upstream dns_upstreams {                         server 192.168.136.130:53;                         server 192.168.136.131:53;             }                  server {                         listen 53 udp;                         proxy_pass dns_upstreams;                         proxy_timeout 1s;                         proxy_responses 1;                         error_log logs/dns.log;             } }在listen配置中的udp选项告诉nginx这是udp反向代理。而proxy_timeout和proxy_responses则是维持住udp会话机制的主要参数。UDP协议自身并没有会话保持机制,nginx于是定义了一个非常简单的维持机制:客户端每发出一个UDP报文,通常期待接收回一个报文响应,当然也有可能不响应或者需要多个报文响应一个请求,此时proxy_responses可配为其他值。而proxy_timeout则规定了在最长的等待时间内没有响应则断开会话。如何通过nginx向后端服务传递客户真实IP最后我们来谈一谈经过nginx反向代理后,upstream服务如何才能获取到客户端的地址?如下图所示,nginx不同于IP转发,它事实上建立了新的连接,所以正常情况下upstream无法获取到客户端的地址:图4 nginx反向代理掩盖了客户端的IP上图虽然是以TCP/HTTP举例,但对UDP而言也一样。而且,在HTTP协议中还可以通过X-Forwarded-For头部传递客户端IP,而TCP与UDP则不行。Proxy protocol本是一个好的解决方案,它通过在传输层header之上添加一层描述对端的ip和port来解决问题,例如:但是,它要求upstream上的服务要支持解析proxy protocol,而这个协议还是有些小众。最关键的是,目前nginx对proxy protocol的支持则仅止于tcp协议,并不支持udp协议,我们可以看下其代码:可见nginx目前并不支持udp协议的proxy protocol(笔者下的nginx版本为1.13.6)。虽然proxy protocol是支持udp协议的。怎么办呢?方案1:IP地址透传可以用IP地址透传的解决方案。如下图所示:图5 nginx作为四层反向代理向upstream展示客户端ip时的ip透传方案这里在nginx与upstream服务间做了一些hack的行为:nginx向upstream发送包时,必须开启root权限以修改ip包的源地址为client ip,以让upstream上的进程可以直接看到客户端的IP。server {              listen 53 udp;          proxy_responses 1;          proxy_timeout 1s;          proxy_bind $remote_addr transparent;               proxy_pass dns_upstreams; }upstream上的路由表需要修改,因为upstream是在内网,它的网关是内网网关,并不知道把目的ip是client ip的包向哪里发。而且,它的源地址端口是upstream的,client也不会认的。所以,需要修改默认网关为nginx所在的机器。# route del default gw 原网关ip # route add default gw nginx的ipnginx的机器上必须修改iptable以使得nginx进程处理目的ip是client的报文。# ip rule add fwmark 1 lookup 100 # ip route add local 0.0.0.0/0 dev lo table 100  # iptables -t mangle -A PREROUTING -p tcp -s 172.16.0.0/28 --sport 80 -j MARK --set-xmark 0x1/0xffffffff这套方案其实对TCP也是适用的。方案2:DSR(上游服务无公网)除了上述方案外,还有个Direct Server Return方案,即upstream回包时nginx进程不再介入处理。这种DSR方案又分为两种,第1种假定upstream的机器上没有公网网卡,其解决方案图示如下:图6 nginx做udp反向代理时的DSR方案(upstream无公网)这套方案做了以下hack行为:1、在nginx上同时绑定client的源ip和端口,因为upstream回包后将不再经过nginx进程了。同时,proxy_responses也需要设为0。server {         listen 53 udp;     proxy_responses 0;         proxy_bind $remote_addr:$remote_port transparent;          proxy_pass dns_upstreams; }2、与第一种方案相同,修改upstream的默认网关为nginx所在机器(任何一台拥有公网的机器都行)。3、在nginx的主机上修改iptables,使得nginx可以转发upstream发回的响应,同时把源ip和端口由upstream的改为nginx的。例如:# tc qdisc add dev eth0 root handle 10: htb # tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.11 match ip sport 53 action nat egress 172.16.0.11 192.168.99.10 # tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.12 match ip sport 53 action nat egress 172.16.0.12 192.168.99.10 # tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.13 match ip sport 53 action nat egress 172.16.0.13 192.168.99.10 # tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.14 match ip sport 53 action nat egress 172.16.0.14 192.168.99.10方案3:DSR(上游服务有公网)DSR的另一套方案是假定upstream上有公网线路,这样upstream的回包可以直接向client发送,如下图所示:图6 nginx做udp反向代理时的DSR方案(upstream有公网)这套DSR方案与上一套DSR方案的区别在于:由upstream服务所在主机上修改发送报文的源地址与源端口为nginx的ip和监听端口,以使得client可以接收到报文。例如:# tc qdisc add dev eth0 root handle 10: htb # tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.11 match ip sport 53 action nat egress 172.16.0.11 192.168.99.10结语以上三套方案皆可以使用开源版的nginx向后端服务传递客户端真实IP地址,但都需要nginx的worker进程跑在root权限下,这对运维并不友好。从协议层面,可以期待后续版本支持proxy protocol传递客户端ip以解决此问题。在当下的诸多应用场景下,除非业务场景明确无误的拒绝超时重传机制,否则还是应当使用TCP协议,其完善的流量、拥塞控制都是我们必须拥有的能力,如果在UDP层上重新实现这套机制就得不偿失了。