Nginx实战

1. Nginx简介

Nginx 是一款轻量级的 Web 服务器。通常用在反向代理负载均衡HTTP 缓存。目前全球很多知名互联网公司在使用 Nginx。

反向代理和正向代理

Nginx 的一个作用是反向代理,那什么是正向代理和反向代理?在知乎上有一个回答总结的不错放到这里。

以下内容来自知乎用户班长他姐夫的回答

正向代理隐藏真实客户端,反向代理隐藏真实服务端。

以下内容来自知乎用户刘志军的回答

我们常说的代理也就是只正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,某些科学上网工具扮演的就是典型的正向代理角色。用浏览器访问 http://www.google.com 时,被残忍的block,于是你可以在国外搭建一台代理服务器,让代理帮我去请求google.com,代理把请求返回的相应结构再返回给我。

image.png

反向代理隐藏了真实的服务端,当我们请求 www.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,www.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。

两者的区别在于代理的对象不一样:正向代理代理的对象是客户端,反向代理代理的对象是服务端

image.png

基于对上面的理解,我们可以看到 Nginx 对于处理有高并发需求的网站是有非常大的作用的。下面我们看一下在 MacOS 上 Nginx 的安装和基本使用。

2. Nginx基本使用

Nginx安装

我们使用 homebrew 来安装 Nginx

  • 搜索Nginx

    1
    brew search nginx
  • 安装Nginx

    1
    brew install nginx

    如果 homebrew 需要更新的话这个过程会比较慢,耐心等待即可。安装完成后会有如下提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
brew services start nginx
Or, if you don't want/need a background service you can just run:
nginx
==> Summary
🍺 /usr/local/Cellar/nginx/1.17.8: 25 files, 2MB
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /usr/local/Cellar/pcre/8.43... (204 files, 5.5MB)
Pruned 1 symbolic links and 1 directories from /usr/local
==> Caveats
==> nginx
Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
brew services start nginx
Or, if you don't want/need a background service you can just run:
nginx
  • 查看Nginx信息

    1
    brew info nginx

    这个命令和安装完成之后的提示基本一致,会显示 Nginx 的基本配置信息。

  • 卸载Nginx

    1
    brew uninstall Nginx

    注意这个命令只会卸载 Nginx 软件本身,并不会删除配置文件。如果我们想彻底删干净 Nginx,需要手动删除 /usr/local/var/www/usr/local/etc/nginx/ 下的文件。

  • 查看Nginx版本

    1
    nginx -v

    我本机的 Nginx 版本如下

    1
    nginx version: nginx/1.17.8
  • 检查Nginx配置是否正常

    1
    nginx -t

如果正常的话,我们会得到如下输出:

1
2
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
  • 配置文件
    1
    cat /usr/local/etc/nginx/nginx.conf
    以上命令可以输出 nginx.conf 默认状态下的配置,在关键的地方我加了注释说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#user  nobody;
worker_processes 1;
#(1)进程数

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#(2)错误日志位置 Mac上在/usr/local/var/log/nginx/

#pid logs/nginx.pid;


events {
worker_connections 1024;
#(3)最大连接数
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#(4)日志格式

#access_log logs/access.log main;

#(5)访问日志位置 Mac上在/usr/local/var/log/nginx/

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 8080;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm;

#(6)第一种情况 拒绝访问ip地址段为 50-100 的ip访问
deny 192.168.10.50/100;

# 第二种情况 只允许ip地址为 192.168.10.50 的ip访问
allow 192.168.10.50;
deny all;

# 第三种情况 这样配置都不能访问,从上到下依次匹配
deny all;
allow 192.168.10.50;

}

#(7)精确匹配 /test 路径拒绝访问
location =/test {
deny all;
}

#(8)精确匹配 /test2 路径都可以访问
location =/test2 {
allow all;
}

#(9)精确匹配 已 php 结尾拒绝访问
location ~ \.php$ { # 正则匹配
deny all;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
#(10)其他配置文件
}
  • 启动Nginx
1
2
3
cd /usr/local/Cellar/nginx/1.17.8/bin

./nginx
  • 重新加载配置文件
1
./nginx -s reload

启动完成之后执行 ps -ef|grep nginx,如果出现下面日志则说明访问成功。

1
2
3
501 65101     1   0  8:59下午 ??         0:00.00 nginx: master process ./nginx
501 65102 65101 0 8:59下午 ?? 0:00.00 nginx: worker process
501 65173 63026 0 9:00下午 ttys001 0:00.00 grep nginx

访问http://localhost:8080,如果出现以下页面说明我们配置成功,并且访问正常。

image.png

3. 实战

现在我们已经安装和配置好了 Nginx,那我们先来看一下在反向代理这种场景下 Nginx 是如何使用的。首先我们先用 SpringBoot 创建一个 Web 服务。

start.spring.io 初始化一个项目方便我们测试。

image.png

用 Eclipse 或 InteliJ IDEA 打开刚刚初始化好的项目,整个工程的目录如下。

image.png

NginxTestController 中写两个简单的接口,分别是 /hello/nginx/hi/nginx,同时在接口的响应中返回当前程序在监听哪个端口即 serverPort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
public class NginxTestController {

@Value("${server.port}")
private int serverPort;

@RequestMapping(value = "/hello/nginx", method = RequestMethod.GET)
public TestResponse getNginx() throws Exception {

TestResponse response = new TestResponse();
response.setCode(200);
response.setMessage("success");
response.setName("hello nginx");
response.setServerPort(serverPort);
return response;
}

@RequestMapping(value = "/hi/nginx", method = RequestMethod.GET)
public TestResponse getIncome() throws Exception {

TestResponse response = new TestResponse();
response.setCode(200);
response.setMessage("success");
response.setName("hi nginx");
response.setServerPort(serverPort);
return response;
}
}

application.properties 配置文件中添加监听的端口。

1
server.port=8090
1
server.port=9090

使用 maven 分别打包两个监听 8090 和 9090 端口的 Web 服务的 jar 包为 nginxdemo-SNAPSHOT-8090.jarnginxdemo-SNAPSHOT-9090.jar

分别启动两个 server

1
2
启动第一个 server
java -jar nginxdemo-SNAPSHOT-8090.jar
1
2
启动第二个 server
java -jar nginxdemo-SNAPSHOT-9090.jar

在不使用 Nginx 做反向代理的情况下,我们先访问下两个 server 的接口是否都能正常使用,如下图表示均正常。

监听 8090 端口的 tomcat

image.png

监听 9090 端口的 tomcat

image.png

反向代理

  • 规则匹配

我们先来看一下上面讲过的关于访问规则的匹配,在 server 块中添加以下 location。

1
2
3
location =/hello/nginx {
deny all;
}

server 块的完整配置如下,修改完成后重新加载 Nginx 配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 9000;
server_name localhost;

location / {
proxy_pass http://localhost:8090;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location =/hello/nginx {
deny all;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

由上面的配置可以 /hello/nginx 被拒绝访问,我们在浏览器访问可得到如下图结果:

/hello/nginx 被拒绝访问。

image.png

/hi/nginx 仍然可以访问。

image.png

  • 使用端口号进行反向代理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #user  nobody;
    worker_processes 1;

    events {
    worker_connections 1024;
    }


    http {
    include mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;

    server {
    listen 9000;
    server_name localhost;

    location / {
    proxy_pass http://localhost:8090;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root html;
    }
    }

    server {
    listen 9001;
    server_name localhost;

    location / {
    proxy_pass http://localhost:9090;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    }
    }

    include servers/*;
    }

使用端口进行反向代理比较简单,我们在配置文件中添加两个 server 块,让 Nginx 分别监听 9000 端口和 9001 端口。然后在 location 块中通过 proxy_pass 分别代理到 tomcat 的 8090 端口和 9090 端口。

通过 Nginx 访问 9000 端口,可以看到返回的是 tomcat 8090 端口的内容。
image.png
通过 Nginx 访问 9001 端口,可以看到返回的是 tomcat 9090 端口的内容。
image.png

这样做的目的是,在真实的服务器环境中,我们不可能把所有的端口都开放出去,比如 mysql 常用的 3306 端口,这样会有很大的安全问题,通过 Nginx 的反向代理就可以解决。

  • 使用域名进行反向代理

上面讲过了使用端口进行反向代理,在真实的开发环境中,我们更多的是使用域名或者二级域名来访问某些页面或接口,我们通常是不需要在域名上加上端口号的。这是因为 80 是 http 协议的默认端口。我们在访问 http://baidu.com 时其实是访问 http://baidu.com:80

那么我们来看一下如何使用 Nginx 通过域名来做反向代理。由于我是在本地进行开发测试的,是没有公网的 ip 地址的,这时候怎么让一个域名解析到我自己的 ip 上呢?我们可以修改 hosts 文件。

1
2
127.0.0.1 hello.democome.local
127.0.0.1 hi.democome.local

以上是我给本机的 hosts 文件添加的两行配置,我们知道是没有 .local 这个域名的,但是加上上面的配置之后当我访问 hello.democome.localhi.democome.local 时,就会解析到我的本机,这样就可以通过 Nginx 进行反向代理了,我们看看具体的 Nginx 配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#user  nobody;
worker_processes 1;

events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;

server {
listen 80;
server_name hello.democome.local;

location / {
proxy_pass http://localhost:8090;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

server {
listen 80;
server_name hi.democome.local;

location / {
proxy_pass http://localhost:9090;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

include servers/*;
}

以上配置,我们把两个 server 块中监听的端口都改为了 80,然后两个二级域名 hello.democome.localhi.democome.local 分别代理到了 http://localhost:8090http://localhost:9090 两个 web server。

在浏览器访问 http://hello.democome.local/hello/nginx 可以看到可以正常返回 8090 端口的内容。

image.png

在浏览器访问 http://hi.democome.local/hello/nginx 可以看到可以正常返回 9090 端口的内容。

image.png

通过以上测试,我们的配置是生效的。

如果不想使用默认的 80 端口,我们依然可以使用域名加上端口的形式访问,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 9000;
server_name hello.democome.local;

location / {
proxy_pass http://localhost:8090;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

修改完配置重新加载,可以看到通过域名加端口也可以正常访问。
image.png

  • 代理到其他网站
    1
    2
    3
    4
    5
    6
    7
    8
    server {
    listen 80;
    server_name hi.democome.local;

    location / {
    proxy_pass https://www.so.com/;
    }
    }

我们把 server 块改成如下配置,然后重新加载 Nginx 配置,可以发现虽然我们访问的是 hi.democome.local 但是真正返回的页面是 360 搜索的页面,并且域名也不会变化。

image.png

负载均衡

说到负载均衡,那么什么是负载均衡,先来看一下维基百科的定义

负载平衡(Load balancing)是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。 使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。 主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。

总结来说负载均衡就是解决高并发的。假如我们有多台服务器,我们在每台服务器上都跑了相同的应用,当用户访问时,我们希望 Nginx 把用户的请求根据一定的策略转发的不同的服务器,已解决单个服务器访问压力大的问题。

为了测试这个效果,我先把之前打包的 jar 文件上传一个到远程的服务器上,并启动。

我们看一下具体如何配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#user  nobody;
worker_processes 1;

events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;

upstream localhost {
server 62.234.66.219:8090 weight=1; #远程服务器
server 192.168.0.101:8090 weight=3;
}

server {
listen 80;
server_name hello.democome.local;

location / {
add_header Backend-IP $upstream_addr;
proxy_pass http://localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

include servers/*;
}

其中主要是 upstream 块的配置,我配置了两个服务器,其中 62.234.66.219:8090 是腾讯云服务器的地址, 192.168.0.101:8090 是我本机的 ip 地址。策略是按照比重来分别转发到两个服务器上,比例为1:3。关于策略还有很多规则,比如根据 ip hash 等。我们这里只演示 weight 策略。

1
2
3
4
upstream localhost {
server 62.234.66.219:8090 weight=1; #远程服务器
server 192.168.0.101:8090 weight=3;
}

完成好上面的配置执行 nginx -s reload 重新加载配置。继续访问http://hello.democome.local/hello/nginx

如下图,本次请求来自192.168.0.101
image.png

如下图,本次请求来自 62.234.66.219
image.png

通过多次请求我们发现,转发到各个服务器的比例基本是 1:3,以上说明我们配置的负载均衡策略是生效的。

https证书配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 443 ssl;
server_name democome.com www.democome.com; #配置域名
ssl_certificate /etc/letsencrypt/live/democome.com/fullchain.pem; #配置证书位置
ssl_certificate_key /etc/letsencrypt/live/democome.com/privkey.pem; #配置证书位置
location /{ #反向代理配置
proxy_pass http://localhost:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

配置证书比较简单,首先监听 443 端口,然后执行证书位置即可。

更多推荐

免费 https 证书申请 letsencrypt