首先提醒一下,国内绝大多数程序员都把Nginx的读音搞错了(甚至包括很多国外的程序员),Nginx官方推荐的正确的读音应该是:Engine-X,而不是En-jingks.
Nginx分为主进程(master process)和工作进程(worker process),每个进程中只有一个线程(也可以配置线程池),通过IO多路复用(底层使用epoll/kqueue等技术)和事件循环达到高并发(这点跟Node.js比较相似)。主进程负责总体协调工作,比如在配置文件更新后重新应用配置、协调哪个worker process应该退役等等。工作进程的个数一般设置为CPU的个数。
还有个Nginx Plus,它是Nginx背后的商业公司,提供比Nginx开源版更多功能的Nginx软件,以及一些有关Nginx的技术支持服务,Nginx和Nginx Plus的对比在这里。
安装Nginx
通过Yum安装
首先安装epel-release
源:
sudo yum install epel-release
然后安装Nginx:
sudo yum install nginx
最后启动Nginx:
sudo systemctl start nginx
查看安装之后的文件路径:
rpm -ql nginx
在笔者的CentOS/7虚拟机上,Nginx的配置文件位于/etc/nginx/nginx.conf
,默认静态资源目录为/usr/share/nginx/html/
。
对于不同的操作系统或安装方式,在安装完Nginx之后,配置文件放置的位置可能不一样,因此如果一个地方找不到,不妨试试其他目录。以下是几个比较常见的地方:
- /etc/nginx/nginx.conf
- /usr/local/nginx/conf/nginx.conf
- /usr/local/etc/nginx/nginx.conf
默认静态资源文件目录:
- /usr/share/nginx/html
通过Docker安装
Docker官网提供了Nginx镜像,运行Docker:
$ sudo docker run -p 8081:80 -d nginx
如果要对外提供静态资源文件:
$ sudo docker run -p 8081:80 -v /some/content:/usr/share/nginx/html:ro -d nginx
此时,将本机静态资源文件夹/some/content
映射成了nginx的默认静态资源文件夹,然后将Docker的80端口映射到了宿主机的8081端口,此时访问http://localhost:8081/
便可以访问宿主机中/some/content/
下的静态文件了。
当然,要指定静态资源文件,更好的方式是通过Dockerfile将资源拷贝到Docker容器中,通常前端项目便可以通过这种方式构建Docker镜像:
FROM nginx
COPY /some/content /usr/share/nginx/html
另外,如果要定制化Nginx的配置,也可以通过Dockerfile:
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
以上Dockerfile将宿主机的nginx.conf
文件拷贝到Docker容器中。
更多安装方式请参考Nginx官网。
Nginx命令行
可以通过发信号的方式对Nginx进行控制,发信号有两种方式,一种是通过nginx
命令,一种是通过kill
,详细请参考官网,这里列出通过该nginx
控制Nginx的功能,命令格式:
nginx -s <SIGNAL>
其中,SIGNAL取值和功能如下:
- quit – 安全地关闭
- reload – 重新加载配置文件
- reopen – 重新打开日志文件
- stop – 直接关闭
配置文件
Nginx的主配置文件nginx.conf
通过上下文context
组织结构,context
中可以包含配置项directive
,而directive
又可以反过来包含context
。
Nginx包含以下顶层的context
:
- events - 通用的与连接相关的配置
- http - http流量相关配置
- mail - 邮件流量相关配置
- stream - TCP/UDP流量相关配置
位于顶层context
之外的directive也被称为在main context
中。
Virtual Server(Server Block)
在每个流量配置context
中(http
,mail
和stream
),都可以通过server
配置多个虚拟服务器(Virtual Server),server
下的配置基于context
的不同而不同。比如对于http
,server
下可以配置多个location
来处理不同的URL,而对于mail
和stream
,server
中则可以配置端口或者Socket。
示例配置文件
user nobody; # a directive in the 'main' context
events {
# configuration of connection processing
}
http {
# Configuration specific to HTTP and affecting all virtual servers
server {
# configuration of HTTP virtual server 1
location /one {
# configuration for processing URIs starting with '/one'
}
location /two {
# configuration for processing URIs starting with '/two'
}
}
server {
# configuration of HTTP virtual server 2
}
}
stream {
# Configuration specific to TCP/UDP and affecting all virtual servers
server {
# configuration of TCP virtual server 1
}
}
配置继承
有些directive可以位于多种context中, 此时便形成了一种继承关系,即子context将继承父context中的directive配置,当然子context也可以通过显式配置的方式覆盖继承自父context的配置。
另外,主配置文件nginx.conf
可以通过include
来包含其他配置文件,一个常用的实践是nginx.conf
只负责配置nginx本身,而将server
相关的配置放到各自的配置文件中,然后在nginx.conf文件中include
这些文件。
虚拟服务器选择
Nginx处理HTTP请求原理:先根据server
中的listen
和server_name
等判断该请求应该由哪个server
来处理,然后通过匹配server
下的location
来决定应该由哪个location
来处理,服务器配置示例如下:
server {
listen 80;
server_name example.org www.example.org;
...
}
需要注意一下几点:
- 如果没有
listen
,那么Nginx默认监听所有网卡的80端口(以root启动nginx),或者默认监听所有网卡的8000端口(非root启动) -
server_name
的前后可以使用通配符*
来匹配任意字符,但是*
不能出现在域名中间 - 可以使用
~
对server_name
进行正则匹配。
Nginx通过将server_name
与请求中的Host
头信息进行匹配,通过以下顺序,第一个满足匹配条件的胜出:
- 精确匹配
- 以
*
开头的最长server_name
- 以
*
结尾的最长server_name
- 第一个匹配的正则表达式
- 如果以上都没有匹配到,则匹配标有
default_server
的server - 如果以上都没有匹配到,则选择第一个server
Location匹配
在Nginx决定了由哪个server处理请求之后,将进一步将请求URL与该server中的各个location
匹配,以最终决定由哪个location
来处理该请求。
location
分为两
- 前缀字符串,示例:
location /some/path/ {
...
}
- 正则表达式,示例:
location ~ \.html? {
...
}
匹配规则如下:
- 先将请求URL与所有
location
进行前缀匹配 - 如果某个前缀命中的的
location
有=
修饰符,则选择之并停止匹配 - 如果最长前缀的
location
有^~
修饰符,则停止匹配,并选择之 - 保存最长的前缀匹配
location
- 进行正则表达式匹配,根据
location
出现的顺序进行匹配 - 找到第一个匹配成功的正则表达式
location
,选择之,停止匹配(请注意,只要有正则表达式匹配上,那么先前保存的最长前缀匹配将失效) - 如果没有匹配上的正则表达式,那么匹配先前保存的最长前缀匹配
神奇的斜杠/
todo
服务静态文件
先将静态资源拷贝到/data/www
目录下,然后在http
中配置:
server {
location / {
root /data/www;
}
}
此时访问http://localhost/
,如果/data/www
下有index.html文件,那么将直接返回index.html文件内容。
另外,还可以通过index
直接指定默认的index文件:
location / {
root /data/www;
index index.html index.php;
}
当前很多单页面应用采用了HTML5的History API,在使用Nginx时,由于所有的URL都将被Nginx处理,但是单页面有只有一个页面,也即所有URL都将路由到同一个页面,比如Index.html,再有浏览器根据URL做客户端路由。此时可以通过try_files
对Nginx进行配置:
server {
...
location / {
try_files $uri /index.html;
}
}
在上例中,Nginx会先尝试访问原URL资源,如果资源不存在则返回index.html。
try_files
的工作机制是会依次访问参数中的资源直到正常返回,需要注意,最后的参数资源必须存在,不然nginx将报错。更多详情请参考Nginx官网或这里。
负载均衡
首先在http
中定义多台机器组,组名为backend
:
upstream backend {
server backend1.example.com;
server backend2.example.com;
server 192.0.0.1 backup;
}
然后就可以在server
中引用backend
了,完整例子:
http {
upstream backend {
server backend1.example.com;
server backend2.example.com;
server 192.0.0.1 backup;
}
server {
location / {
proxy_pass http://backend;
}
}
}
配置负载均衡方式
在upstream
中可以配置以哪种方式分发请求:
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
}
Nginx有以下方式分发请求:
- Round Robin(默认) - 依次轮训
- Least Connection - 连接最少的节点胜
- IP Hash - 通过IP地址的hash值进行路由,可以保证来源相同的请求总是路由到同一个节点,进而支持
sticky session
。 - 通用Hash - 通过自定义算法计算Hash值来决定路由
每种方式都有其自定义的配置,更多详情请参考Nginx官网。
Health Check
如果upstream服务器返回不正常,那么Nginx将在一段时间内不再向该服务代理请求:
upstream backend {
server backend1.example.com;
server backend2.example.com max_fails=3 fail_timeout=30s;
server backend3.example.com max_fails=2;
}
上例中,对于backend2.example.com
,如果超过了3(max_fails
)次返回失败,那么Nginx将在30秒内(fail_timeout
)不再路由到该节点,30秒后再次启用该节点。
默认情况下,max_fails
=1,fail_timeout
=10s。
代理多个应用
配置HTTPS
最佳实践
设置worker_processes
通常的做法是将worker_processes设置成与CPU的数量相同,也可以设置为auto
让Nginx自动为你决定。
worker_processes auto;
停用access_log
启动access_log后Nginx记录每一次请求,这将增加磁盘空间并给Nginx带来额外负担,如果你确定不需要access_log,可以:
access_log off;
去掉Nginx版本号
server_tokens off; #为了安全
基于server配置gzip
配置gzip是把双刃剑,不配吧数据量太大,配了吧有安全风险。因此推荐的实践是只针对静态资源文件使用gzip,并且基于server单独配置:
server {
listen 80;
server_name example1.com;
gzip on;
gzip_types text/html text/css image/jpg image/jpeg image/png image/svg;
}
server {
listen 443 ssl;
server_name example2.com;
gzip off;
}
注意add_header
add_header
用于向最终返回给客户端的response中添加HTTP Header信息,需要注意的是add_header
并不享受Nginx的继承机制,意味着如果子context中有add_header,那么它将覆盖所有的父context中的add_header
配置。比如,在http
中配置了3个add_header
,然后在server
中配置了1个add_header
,那么server
中的add_header
会将http
中的所有3个add_header
给覆盖掉。
防止加入iframe
某些钓鱼网站会通过iframe的方式将你的网站加入钓鱼网站,此时我们可以通过Nginx配置声明自己的网站不应该被放入iframe中,在server
中配置:
add_header X-Frame-Options DENY;
启用XSS过滤器
以下配置中,Nginx会通知浏览器启用XSS过滤器,虽然对于多数浏览器来说这个是默认设置:
add_header X-XSS-Protection "1; mode=block";
调优
- Nginx中,当使用sendfile函数时,TCP_NOPUSH才起作用,因为在sendfile时,Nginx会要求发送某些信息来预先解释数据,这些信息其实就是报头内容,典型情况下报头很小,而且套接字上设置了TCP_NODELAY。有报头的包将被立即传输,在某些情况下(取决于内部的包计数器),因为这个包成功地被对方收到后需要请求对方确认。这样,大量数据的传输就会被推迟而且产生了不必要的网络流量交换。而通过设置TCP_NOPUSH=on,表示将所有HTTP的header一次性发出去,参考这里。
- Nginx的TCP_NODELAY只有在配置长连接时才起作用,因为长连接可能引起小包的阻塞,配置TCP_NODELAY可以避免该阻塞,参考这里
- Use the
tcp_nopush
directive together with thesendfile
on;
directive. This enables NGINX to send HTTP response headers in one packet right after the chunk of data has been obtained bysendfile()
. - 在 nginx 中,tcp_nopush 配置和 tcp_nodelay “互斥”。
- 默认情况下,nginx已经自动开启了对client连接的keep alive支持(同时client发送的HTTP请求要求keep alive)。
- 默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,Nginx 新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接)。
- 当
location
值的最后含有斜杠,比如/hello/
,然后该location有设置的proxy_pass
,那么当客户端访问不带最后斜杠的/hello
时,Nginx将301重定向到带有斜杠的/hello/
,参考这里。
网友评论