Nginx
什么是 Nginx
nginx
[engine x] 是一个毛子开发的高性能的 HTTP
和反向代理服务器, 也是一个邮件代理服务器, TCP
和 UDP
代理服务器.
nginx
处理静态文件的速度很快, 本身不提供动态内容的处理功能, 而是通过反向代理(proxy
)功能来处理动态请求.
什么是 proxy
代理
在 nginx
当中一般有两种代理, 一种是正向代理, 一种是反向代理.
通常来说, 当 nginx
需要处理动态内容的时候, 用的是反向代理, 反向代理一般工作模式类似于:
当客户端 C
试图访问 S
服务器上的 /api/abc
资源时, 而 /api/abc
并不存在于 S
服务器上, 此时 S
服务器将向 S2
服务器请求
/api/abc
资源, 并将获取到的结果返回给客户端 C
. 此时客户端并不知道服务器 S
做了什么, 它只知道资源获取成功了.
而正向代理其实和翻墙用的 VPN
和 Shadowsocks
是类似的用途. 即客户端 C
想要访问服务器 S2
上的资源, 但是由于某种不可抗力的原因 C
无法直接访问 S2
, 但 S
服务器和 S2
却畅通无阻, 此时 C
就可以把 S
设置成代理服务器, 将任何访问 S2
的请求转发给 S
, 让 S
来帮忙获取.
虽然看起来正向代理和反向代理功能似乎差不多, 但是一个重要的区别是, 客户端 C
是否知道 S
是一个 proxy
.
如何获取 nginx
目前来说, nginx
提供 Linux
和 Windows
两个平台的支持, 但由于 nginx
的高性能是依赖于 Linux
上独有的功能, 因此 nginx
在 Windows
上的版本仅仅只能是一个玩具, 一个测试工具, 性能远远低于 Linux
上的版本.
在 http://nginx.org/en/download.html
上可以直接下载到源码和 Windows
的预编译二进制包.
但如果你对如何编译调优等不熟悉的话, 建议还是直接从包管理器里直接安装, 比如:
apt install nginx
pacman -S nginx
yum install nginx
zypper install nginx
不出意外的话安装的是目前处于 stable version
的 1.12.x
的 nginx
如何使用?
启动与管理
nginx
的启动只需要简单的调用 nginx
指令就可以运行
nginx -s reload
nginx -s restart
nginx -s stop
则可以分别对已启动的 nginx
实例进行
* 重新加载配置文件
* 重新启动
* 停止
上面说的直接调用是最基础的管理, 但更推荐的方法是使用更高级的 systemd
来进行管理, 后者可以很方便的提供前者无法做到的事情
例如服务监控, 故障重启, 内存检测.
Ubuntu 16
以及 CentOS 7
版本以上已经将传统的 sysvinit
替换为了 systemd
, 相对于的将原始的 service
指令换成了 systemctl
指令
当你在这些系统上执行 service
的时候它会提示你 redirect to systemctl
.
有了 systemd
之后, 管理 nginx
则可以替换为
systemctl start nginx
systemctl stop nginx
systemctl reload nginx
systemctl restart nginx
systemctl status nginx
配置文件的编写
要想要发挥 nginx
的完整功能, 熟悉的编写 nginx
的配置文件是必不可少的技能.
nginx
的配置文件采用的是 类似 于 shell
语法的指令. 几乎所有的指令都是由 nginx
的不同的 module
来提供的.
在编写 nginx
的配置文件的时候, 你需要时刻记住, 你所写的是 nginx
的配置文件, 尽管它和 shell
类似, 但它只在语法上有些相似,
在 nginx
配置文件当中, 没有什么的严格的执行顺序, 写在前面的指令可能会在任何时候被执行.
如果你发现你所用到的指令被提示不存在, 那一定是你的 nginx
版本不符合或者没有编译所需要的 module
.
关于 if 指令
特别需要提醒的一点是, if
指令并不是 nginx
的核心指令, 它是由 ngx_http_rewrite_module
提供的, 同时该模块还提供了
break
, return
, rewrite
和 set
指令.
正因为如此, if
指令应当避免和第三方模块一起使用, 这将会导致一些不可思议的问题. 而且 if
指令推荐只和 rewrite
有关操作一起使用,
使用 if
来处理一些想当然的问题同样会导致令人难以理解的问题和 bug
.
nginx
的最主要的配置文件是 nginx.conf
, 一般来说它位于 /etc/nginx/nginx.conf
当 nginx
用作 HTTP(S)
服务器的时候, 最主要的就是 http {}
块, 你的所有其他指令都应当放在 http
块当中.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
return 403;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
上面是一个基本的配置文件的示例, http {}
段当中首先包含了一些基本设置, 和一些与性能有关系的指令.
在这里我们需要关注的是 server {}
段.
在 nginx
当中, 每个 server {}
段都是一个独立的虚拟主机段, 这些不同的 server {}
段当中通过 listen
指令指定的端口号和 server_name
指令指定的域名来区分不同的服务.
上述例子当中, server {}
段只负责处理了静态文件, root
指令设置了静态文件所在的位置, location
指令则设定当你访问 /
的时候返回 HTTP 403 forbidden
server_name
和 listen
server_name
用来指示当前 server {}
段匹配的域名, 可以由多个域名, 例如
server_name abc.com abcd.com a.abc.com *.cdef.com;
listen
指令则设定需要监听的端口号, 端口号可以任意, 只要不是一个非 nginx
占用的就可以, 不同的 server {}
里面的 listen
可以使用相同
的端口号, 这是不会冲突的.
location
location
是 nginx
当中一个较为重要的匹配指令, 它主要是匹配访问的路径, 然后根据不同的路径你可以进行不同的操作, 例如你可以让 /abc/
对应 /var/www/abc/
路径,
而 /def/
则对应 /srv/def/
路径:
location /abc {
root /var/www/;
}
location /def {
root /srv/;
}
这时候需要注意一点, 当 root
指令放在 location
里面使用的时候, 实际物理路径就会变成 root
指定的路径 + location
匹配的路径.
如果你需要让 /abc
对应物理路径 /var/www/def
的时候, 则需要使用 alias
指令:
location /abc {
alias /var/www/def;
}
这里别名的意思是, 当我访问 /abc
的时候, 实际上将会访问 /var/www/def
这个文件, 当我访问 /abcd
的时候, 实际上访问
的物理文件是 /var/www/defd
这个文件, 而当访问 /abd/def
的时候, 访问的物理文件则变成了 /var/www/def/def
这个
这也是 alias
的本来的意思, 别名.
这是 root
和 alias
的一个重要区别.
location
指令匹配的路径可以是单纯的字符串按照前缀来匹配, 也可以使用正则表达式来匹配
当使用正则表达式进行匹配的时候, 前面有两种修饰符, ~*
: 大小写不敏感匹配, ~
: 大小写敏感匹配.
例如
location ~* \.js$ {}
则表示匹配任何以 .js
为结尾切不区分大小写的请求.
多个 location
出现的时候, 匹配顺序一般是按照从最长到最短的顺序
例如
location /a {
}
location /a/b {
}
当访问 /a
的时候匹配的是第一个 location
, 当访问 /a/
的时候同样是第一个, 而当访问 /a/b
的时候则匹配到的是第二个 location
反向代理的配置
前面说的都是静态文件的处理, 对于后端来说, 最主要的部分就是配置反向代理来正确处理后端的请求.
在 nginx
当中一般有 proxy_pass
, fastcgi_pass
, uwsgi_pass
, scgi_pass
等这几种反向代理指令,
分别对应着以普通的 HTTP(S)
协议进行代理, 以 fastcgi
协议代理, 以 uwsgi
协议代理和以 scgi
协议代理.
对于目前 Python
和 Flask
来说, 常用的是 proxy_pass
和 uwsgi_pass
.
最基本的反向代理其实很简单, 只需要在对应的指令后面跟上目标地址就可以, 例如
proxy_pass http://127.0.0.1:8080
就表示将请求转发给 http://127.0.0.1:8080
通常反向代理可以和 location
一起使用, 比如
location / {
root /path/to/html;
}
location /api {
proxy_pass http://127.0.0.1:8080;
}
上面的配置文件表示, 对于所有的请求, 如果是以 /api
开头的, 则将请求转发给 http://127.0.0.1:8080
, 而其他请求则当作静态文件去 /path/to/html
目录寻找
例如当访问 /api/abc
的时候, nginx
会以 http
协议去访问 http://127.0.0.1:8080/api/abc
, 并将访问结果放回给客户端.
那么现在假设一种情况, 你的后端代码有一个路由 : /function
, 当后端运行在 5000
端口的时候, 访问
http://127.0.0.1:5000/function
就可以获取到内容, 但是你的前端非常不满意, 认为需要加一个 /api
前缀,
你的前端认为这个接口应该是 /api/function
, 但后端不想改代码, 怎么办呢?
这个时候只需要在 nginx
里面这么写
location ~* /api(.*) {
proxy_pass http://127.0.0.1:5000$1;
}
就可以解决问题.
当然最简单的方法是, 打死你的后端.
除此以外, 反向代理也是可以设置很多参数的. http://nginx.org/en/docs/http/ngx_http_proxy_module.html
nginx
的变量
其实这部分如果只是简单的用 nginx
的话一般人用不到.
nginx
的变量是使用 set
指令来设定的, 之前也说过, set
指令是 rewrite
模块提供的指令.
nginx
的变量不同之处在于, 变量的创建时间是在配置文件被读取的时候, 而赋值操作是在运行时执行的.
例如
server {
...
location /a {
set $a '/a';
}
location /b {
return 200 $a;
}
}
如果你不了解 nginx
的变量的话, 你会觉得这部分配置文件是错误的, 你的理由很简单, location /b
里面直接用了一个未定义的 $a
变量
但事实上, 这是完全合法的, 因为当 nginx
读取你的配置文件的时候, 发现你在 location /a
里面创建了 $a
变量, 于是 nginx
就会创建一个变量
$a
, 这个变量将会是在这个 server
里面的全局存在. 所以在 location /b
里面也就可以使用这个变量了. 只不过这个变量是空值罢了.