【笔记】Nginx随手记(持续)
Jan 6, 2019笔记nginxNginx随手记(持续)
背景
传统的 Web 服务器,每个客户端连接作为一个单独的进程或线程处理,需在切换任务时将 CPU 切换到新的任务并创建一个新的运行时上下文,消耗额外的内存和 CPU 时间,当并发请求增加时,服务器响应变慢,从而对性能产生负面影响。
Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器,而且支持热部署,几乎可以做到 7 * 24 小时不间断运行,即使运行几个月也不需要重新启动,还能在不间断服务的情况下对软件版本进行热更新。性能是 Nginx 最重要的考量,其占用内存少、并发能力强、能支持高达 5w 个并发连接数,最重要的是,Nginx 是免费的并可以商业化,配置使用也比较简单。
基本
相关地址
- 官网:https://www.nginx.com/
- nginx下载地址:http://nginx.org/
- nginx文档地址:http://nginx.org/en/docs/
服务器依赖模块
- GCC 编译器(GNU Compiler Collection),用来编译C语言程序,用C++来编写 Nginx HTTP 模块。
- PCRE 库(Perl Compatible Regular Expressions),Perl 兼容正则表达式,由 RegEx 演化而来。如果在配置文件 nginx.conf 里使用了正则表达式,则需要该库。安装
pcre
、pcre-devel
(二次开发需要的库)。 - zlib 库:用于对 HTTP 包的内容做 gzip 格式的压缩。安装
zlib
、zlib-devel
(二次开发需要的库)。 - OpenSSL 开发库:需要 SSL 协议上传输 HTTP,或者使用 MD5、SHA1 等散列函数,则需要安装它。安装
openssl
、openssl-devel
(二次开发需要的库)。
主要命令
默认情况下,Nginx安装在目录/usr/local/nginx/
中,二进制文件路径为/usr/local/nginx/sbin/nginx
,配置文件在/usr/local/nginx/conf/nginx.conf
- 默认启动
1
/usr/local/nginx/sbin/nginx
读取默认路径的配置文件
指定配置文件的启动方式
1
/usr/local/nginx/sbin/nginx -c /tmp/nginx.conf
另指定安装目录的启动方式
1
/usr/local/nginx/sbin/nginx -p /usr/local/nginx
另行指定全局配置项的启动方式
1
/usr/local/nginx/sbin/nginx -g "pid /var/nginx/test.pid"
把pid文件写到/var/nginx/test.pid中,要求不能与nginx.conf中的配置项冲突,执行其他命令的时候,也要把-g参数带上,否则可能出现配置项不匹配的情形。
- 测试配置信息是否有错误
1
/usr/local/nginx/sbin/nginx -t
执行结果中显示配置是否正确
- 在测试配置阶段不输出信息
1
/usr/local/nginx/sbin/nginx -t -q
不把error级别以下的信息输出到屏幕
显示版本信息
1
/usr/local/nginx/sbin/nginx -v
显示编译阶段的参数
1
/usr/local/nginx/sbin/nginx -V
快速停止服务
1
/usr/local/nginx/sbin/nginx -s stop
强制停止服务,想master进程发送TERM信号
- “优雅”地停止服务
1
/usr/local/nginx/sbin/nginx -s quit
区别:stop时,worker进程与master进程收到信号后立刻跳出循环,退出进程;quit时,首先关闭监听端口,停止接收新的连接,然后把当前正在处理的连接全部处理完,最后退出进程。
- 使运行中的nginx重读配置项并生效
1
/usr/local/nginx/sbin/nginx -s reload
先检查新的配置项是否正确,然后以quit方式关闭,再重启
- 日志文件回滚
1
/usr/local/nginx/sbin/nginx -s reopen
重新打开日志文件,这样可以先把当前日志文件改名或转移到其他目录中进行备份,再重新打开时就会生成新的日志文件,使得日志文件不至于过大。
平滑升级Nginx。升级步骤:
- (1)
kill -s SIGUSER2 <nginx master pid>
,运行中的nginx会将pid文件重命名,在nginx.pid重命名为nginx.pid.oldbin - (2)启动新版本的nginx
- (3)通过kill命令向旧版本的master进程发送SIGQUIT信号
- (1)
显示命令行帮助
1
/usr/local/nginx/sbin/nginx -h
ngx_http_core_module 模块提供的变量
内核参数的优化
默认的 Linux 内核参数考虑的是最通用的场景,这不符合用于支持高并发访问的 Web 服务器的定义,所以需要修改 Linux 内核参数,使其可以拥有更高的性能。
当 Nginx 作为静态 Web 内容服务器、反向代理服务器或是图片缩略图功能的服务器时,内核参数的调整都是不同的。需要通过修改 etc/sysctl.conf
来更改内核参数。如:
1 | fs.file-max = 999999 |
参数:
- file-max:进程可以同时打开的最大句柄数
- tcp_tw_reuse:
1
表示允许将TIME-WAIT 状态的 socket 重新用于新的 TCP 连接。 - tcp_keepalive_time:当keepalive 启动时,TCP 发送 keepalive 消息的频度,默认是两小时。
- tcp_fin_timeout:当服务器主动关闭连接时,socket 保持在 FIN-WAIT-2 状态的最大时间。
- tcp_max_tw_buckets:表示操作系统允许TIME_WAIT 套接字数量的最大值,如果超出,TIME_WAIT 套接字将立刻被清除并打印警告信息。该参数默认为 180000。
- tcp_max_syn_backlog:表示 TCP 三次握手建立阶段接收 SYN 请求队列的最大长度,默认为1024.
- ip_local_port_range:定义了在UDP和TCP连接中本地端口的取值范围。
- net.ipv4.tcp_rmem:TCP接收缓存(用于TCP接收滑动窗口)的最小值、默认值、最大值。
- net.ipv4.tcp_wmem:TCP发送缓存(用于TCP接收滑动窗口)的最小值、默认值、最大值。
- netdev_max_backlog:当网卡接收数据包的速度大于内核处理的速度时,有一个队列保存这些数据包。这个参数表示该队列的最大值。
- rmem_default:表示内核套接字接收缓存区默认的大小。
- wmem_default:表示内核套接字发送缓存区默认的大小。
- rmem_max:表示内核套接字接收缓存区的最大大小。
- wmem_max:表示内核套接字发送缓存区的最大大小。
执行 sysctl -p
命令使上述修改生效。
运行中的nginx
部署 nginx 都是使用一个 master 进程来管理多个 worker 进程,一般情况下,worker 进程的数量与服务器上的 CPU 核心数相等。每一个 worker 都在提供互联网服务,而 master 进程则很“清闲”,只负责监控管理 worker 进程。worker 进程之间通过共享内存、原子操作等一些进程间通信机制来实现负载均衡等功能。
- 通常会利用 root 用户启动 master 进程。worker 进程的权限要小于或等于 master 进程,这样 master 进程才可以完全地管理 worker 进程。当任意一个 worker 进程出现错误从而导致 coredump 时,master 进程会立刻启动新的worker 进程继续服务。
- 多个 worker 进程处理互联网请求不但可以提高服务的健壮性,最重要的是,这样可以充分利用现在常见的 SMP 多核架构,从而实现微观上真正的多核并发处理。在 Apache 上每个进程在一个时刻只处理一个请求,因此如果希望 Web 服务器拥有并发处理的请求数更多,就要把 Apache 的进程或线程数设置得更多,通常会达到一台服务器拥有几百个工作进程,这样大量的进程间切换将带来无谓的系统资源消耗。而 nginx 一个 worker 进程可以同时处理的请求数只受限于内存大小,而且在架构设计上,不同的 worker 进程之间处理并发请求时几乎没有同步锁的限制,worker 进程通常不会进入睡眠状态。因此,当 nginx 上的进程数与 CPU 核心数相等时,进程间切换的代价是最小的。
Worker 进程的数量会直接影响性能。那么配置多少个 worker 进程才好?这与实际业务有关。每个 worker 进程都是单线程的进程,它们会调用各个模块以实现多种多样的功能。如果这些模块确认不会出现阻塞式的调用,那么有多少 CPU 内核就应该配置多少个进程;反之,如果有可能出现阻塞式调用,那么需要配置稍多一些的 worker 进程。
比如业务方面会致使用户请求大量读取本地磁盘上的静态资源文件,而且服务器上的内存较小,以至于大部分的请求访问静态资源文件时都必须读取磁盘,而不是内存中的磁盘缓存,那么磁盘I/O调用可能会阻塞住 worker 进程少量时间,进而导致服务整体性能下降。
多 worker 进程可以充分利用多核系统架构,但若 worker 进程的数量多于 CPU 内核数,那么会增大进程间切换带来的消耗。如果有4颗 CPU 内核,就可以进行如下配置:1
2
3
4worker_processes 4;
worker_cpu_affinity 1000 0100 0010 0001;
# * worker_cpu_affinity 配置仅对Linux 操作系统有效。
作用
Nginx主要功能:
- 1、反向代理
- 2、负载均衡
- 3、HTTP服务器(包含动静分离)
- 4、正向代理
一、反向代理
反向代理在电脑网络中是代理服务器的一种。服务器根据客户端的请求,从其关系的一组或多组后端服务器(如Web服务器)上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器集群的存在。 ——wiki
反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet上 的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。 ——百度百科
反向代理应该是 Nginx 做的最多的一件事了,简单来说就是真实的服务器不能直接被外部网络访问,所以需要一台代理服务器,而代理服务器能被外部网络访问的同时又跟真实服务器在同一个网络环境,当然也可能是同一台服务器,端口不同而已。
如:
1 | server { |
在这份配置下,我们访问 localhost 的时候,就相当于访问 localhost:8080 了。
二、负载均衡
负载平衡(Load balancing)是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。 使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。 主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。 ——wiki
负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。负载均衡构建在原有网络结构之上,它提供了一种透明且廉价有效的方法扩展服务器和网络设备的带宽、加强网络数据处理能力、增加吞吐量、提高网络的可用性和灵活性。 ——百度百科
负载均衡也是 Nginx 常用的一个功能,负载均衡其意思就是分摊到多个操作单元上进行执行,例如:Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。简单而言就是当有2台或以上服务器时,根据规则随机的将请求分发到指定的服务器上处理,负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡。而Nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略。
RR(默认)
如1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
# 简单配置
upstream test {
server localhost:8080;
server localhost:8081;
}
server {
listen 81;
server_name localhost;
client_max_body_size 1024M;
location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
配置了2台服务器,当然实际上是一台,只是端口不一样而已,而8081的服务器是不存在的,也就是说访问不到,但是我们访问 localhost 的时候,也不会有问题,会默认跳转到 localhost:8080。 具体是因为Nginx会自动判断服务器的状态,如果服务器处于不能访问(服务器挂了),就不会跳转到这台服务器,所以也避免了一台服务器挂了影响使用的情况,由于 Nginx 默认是RR策略,所以我们不需要其他更多的设置。
权重
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 例如1
2
3
4upstream test {
server localhost:8080 weight=9;
server localhost:8081 weight=1;
}
那么10次一般只会有1次会访问到8081,而有9次会访问到8080。
ip_hash
上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
1 | upstream test { |
fair(第三方)
1 | upstream backend { |
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。
1 | upstream backend { |
以上5种负载均衡各自适用不同情况下使用,所以可以根据实际情况选择使用哪种策略模式,不过fair和url_hash需要安装第三方模块才能使用。
三、HTTP服务器
Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现,首先看看Nginx做静态资源服务器。
1 | server { |
这样如果访问http://localhost 就会默认访问到E盘wwwroot目录下面的index.html,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署。
动静分离
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
1 | upstream test{ |
这样我们就可以把HTML以及图片和css以及js放到wwwroot目录下,而tomcat只负责处理jsp和请求,
例如当我们后缀为gif的时候,Nginx默认会从wwwroot获取到当前请求的动态图文件返回,当然这里的静态文件跟Nginx是同一台服务器,我们也可以在另外一台服务器,然后通过反向代理和负载均衡配置过去就好了,只要搞清楚了最基本的流程,很多配置就很简单了,另外localtion后面其实是一个正则表达式,所以非常灵活。
四、正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。 ——百度百科
当你需要把你的服务器作为代理服务器的时候,可以用Nginx来实现正向代理
1 | resolver 114.114.114.114 8.8.8.8; |
resolver是配置正向代理的DNS服务器,listen 是正向代理的端口,配置好了就可以在ie上面或者其他代理插件上面使用服务器ip+端口号进行代理了。
配置文件
1 | ... # 全局块 |
Nginx 配置文件由三部分组成:
- 全局块,主要设置一些影响 Nginx 服务器整体运行的配置指令。比如:worker_processes 1;worker_processes 值越大,可以支持的并发处理量就越多。
- Events 块,涉及的指令主要影响 Nginx 服务器与用户的网络连接。比如:worker_connections 1024;支持的最大连接数。
- HTTP 块,又包括 HTTP 全局块和 Server 块,是服务器配置中最频繁的部分,包括配置代理、缓存、日志定义等绝大多数功能。Server 块:配置虚拟主机的相关参数。Location 块:配置请求路由,以及各种页面的处理情况。
配置实例
1 | ########### 每个指令必须有分号结束。################# |
原理
工作原理
Nginx由内核和模块组成。
Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。
用户根据自己的需要开发的模块都属于第三方模块。正是有了这么多模块的支撑,Nginx的功能才会如此强大。
Nginx的模块从结构上分为核心模块、基础模块和第三方模块:
- 核心模块:HTTP模块、EVENT模块和MAIL模块
- 基础模块:HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块,
- 第三方模块:HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块。
Nginx的模块从功能上分为如下三类:
- Handlers(处理器模块)。此类模块直接处理请求,并进行输出内容和修改headers信息等操作。Handlers处理器模块一般只能有一个。
- Filters (过滤器模块)。此类模块主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出。
- Proxies (代理类模块)。此类模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGI等进行交互,实现服务代理和负载均衡等功能。
HTTP 模块调用的简化流程
Worker 进程会在一个for循环语句里反复调用事件模块检测网络事件;
当事件模块检测到某个客户端发起的TCP请求时,将会为它建立TCP连接,成功建立连接后根据nginx.conf文件中的配置会交由HTTP框架处理;
HTTP 框架会试图接收完整的HTTP头部,并在接收到完整的HTTP头部后将请求分发到具体的 HTTP模块中处理;
HTTP 模块在处理请求的结束时,大多会向客户端发送响应,此时会自动地依次调用所有的额 HTTP 过滤模块,每个过滤模块可以根据配置文件决定自己的行为。
进程模型
Nginx默认采用多进程工作方式,Nginx启动后,会运行一个master进程和多个worker进程。其中master充当整个进程组与用户的交互接口,同时对进程进行监护,管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。worker用来处理基本的网络事件,worker之间是平等的,他们共同竞争来处理来自客户端的请求。
nginx的进程模型如图所示:
在创建master进程时,先建立需要监听的socket(listenfd),然后从master进程中fork()出多个worker进程,如此一来每个worker进程多可以监听用户请求的socket。一般来说,当一个连接进来后,所有在Worker都会收到通知,但是只有一个进程可以接受这个连接请求,其它的都失败,这是所谓的惊群现象。nginx提供了一个accept_mutex(互斥锁),有了这把锁之后,同一时刻,就只会有一个进程在accpet连接,这样就不会有惊群问题了。
先打开accept_mutex选项,只有获得了accept_mutex的进程才会去添加accept事件。nginx使用一个叫ngx_accept_disabled的变量来控制是否去竞争accept_mutex锁。ngx_accept_disabled = nginx单进程的所有连接总数 / 8 -空闲连接数量,当ngx_accept_disabled大于0时,不会去尝试获取accept_mutex锁,ngx_accept_disable越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept,每个worker进程的连接数就控制下来了,其它进程的连接池就会得到利用,这样,nginx就控制了多进程间连接的平衡。
每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。这里的连接池里面保存的其实不是真实的连接,它只是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。一个nginx能建立的最大连接数,应该是worker_connections worker_processes。当然,这里说的是最大连接数,对于HTTP请求本地资源来说,能够支持的最大并发数量是worker_connections worker_processes,而如果是HTTP作为反向代理来说,最大并发数量应该是worker_connections * worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。
处理HTTP请求流程
http请求是典型的请求-响应类型的的网络协议。http是文件协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。通常在一个连接建立好后,读取一行数据,分析出请求行中包含的method、uri、http_version信息。然后再一行一行处理请求头,并根据请求method与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。在将响应发送给客户端之后,一个完整的请求就处理完了。
知识点整理
add_header
及其限制
语法:1
add_ header name value [always];
- 环境: http、 server、 location、 if in location
- 用途:添加自定义的响应头,比如设置CORS跨域相关响应头。
注意:add_ header只能在 HTTP状态码是 200、 201、 204、 206、 301、 302、 303、 304、 307或 308时输出响应头,如果出现 404、 500等异常状态码则无法输出响应头。至于这样处理的原因大概如下:为了避免在错误的响应中添加响应头,从而导致安全问题。比如只有当请求处于正常的返回状态时,才会发送缓存头,毕竟在浏览器上缓存一个错误状态不是什么好事情。——参考文档:https://nginx.org/en/docs/http/ngx_http_headers_module.html
always参数
Nginx 1.7.5以上的版本新增了 always参数,使之可以在任何 HTTP状态下输出响应头,示例如下:响应头信息输出如下:测试结果表明,即使发生了 500错误,加入 always参数后依然可以输出 add_header响应头信息。
比如无论什么情况都设置支持跨域:1
2
3
4
5# ...
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range' always;
add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range' always;
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com