文章目录
基本概念
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的。
其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好。
它的应用主要有反向代理、负载均衡和动静分离等。
先简单介绍下反向代理。在介绍反向代理之前,需要先说下正向代理。
假设我们(Client)想访问Google(Server),直接访问是不能的,需要科学上网,通过访问一个代理,由代理来访问Google。这个过程叫正向代理。代理的是客户端。
下面来介绍下反向代理
反向代理对客户端来说是透明的,客户端感知不到代理的存在。代理将请求转发到不同的服务器上,可以实现分流。
下面谈谈负载均衡和动静分离。
在负载均衡中,Nginx起到了反向代理的角色,为了避免单独一个服务器压力过大,将来自用户的请求转发到不同的服务器。
为了加快网站的解析速度,把动态页面和静态资源由不同的服务器来解析,加快解析速度,降低单个服务器的压力。
这里只是简单的介绍下这几个概念,下文会详解这些概念。
安装过程就省略了,下面介绍下nginx常用的操作命令。
常用命令
[root@instance-54lh4cfv nginx]# whereis nginx
nginx: /usr/sbin/nginx /usr/lib64/nginx /etc/nginx /usr/share/nginx /usr/share/man/man8/nginx.8.gz /usr/share/man/man3/nginx.3pm.gz
这里我已经安装好了,可以直接敲入nginx
来执行命令。
查看版本号
[root@instance-54lh4cfv ~]# nginx -v
nginx version: nginx/1.12.2
关闭nginx
我们先查看下nginx起来了没有:
[root@instance-54lh4cfv ~]# ps -ef | grep nginx
root 2054 1 0 Oct03 ? 00:00:00 nginx: master process /usr/sbin/nginx
root 17211 17118 0 22:06 pts/1 00:00:00 grep --color=auto nginx
nginx 18066 2054 0 Dec07 ? 00:00:00 nginx: worker process
起来了,我们执行停止命令
nginx -s stop
在查看nginx:
[root@instance-54lh4cfv ~]# ps -ef | grep nginx
root 18209 17118 0 22:37 pts/1 00:00:00 grep --color=auto nginx
已经没有nginx的进程了。
启动nginx
启动命令比较简单,直接执行nginx
就可以了。
[root@instance-54lh4cfv ~]# nginx
[root@instance-54lh4cfv ~]# ps -ef | grep nginx
root 18229 1 0 22:38 ? 00:00:00 nginx: master process nginx
nginx 18230 18229 0 22:38 ? 00:00:00 nginx: worker process
root 18253 17118 0 22:39 pts/1 00:00:00 grep --color=auto nginx
重新加载配置文件
[root@instance-54lh4cfv ~]# nginx -s reload
配置文件
下面探讨下nginx的配置文件,一般nginx的配置文件是在它的安装目录中的conf目录下,
我这里的是直接在安装目录下,叫 nginx.conf
。
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
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"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location ~ .*\.(jpg|gif|png)$ {
root /opt/nginx/app/images;
}
location ~ .*\.(txt|xml)$ {
root /opt/app/code/doc;
}
location ~ ^/download {
gzip_static on;
tcp_nopush on;
root /opt/app/code;
}
location / {
root html;
index index.html index.htm;
}
#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;
# }
#}
}
其中#就是注释。nginx的配置文件由三部分组成:全局块、events块以及http块组成。
全局块
从配置文件到events块之间的内容是全局块,主要设置一些影响nginx服务器整体的配置指令。
#user nobody;
worker_processes 1; #工作进程数量
#error_log logs/error.log; 错误日志保存文件名称
#error_log logs/error.log notice; notice级别
#error_log logs/error.log info; info级别
#pid logs/nginx.pid; 可以配置进程id,初始化nginx时会读取该文件
worker_processes 1
指的是工作进程数量,一般设置成auto,Nginx会根据系统的CPU核数去设置它的值,nginx一般会有一个master进程和多个工作进程(worker process)。
默认是1,所以我们通过
[root@instance-54lh4cfv ~]# ps -ef | grep nginx
root 18229 1 0 22:38 ? 00:00:00 nginx: master process nginx
nginx 18230 18229 0 22:38 ? 00:00:00 nginx: worker process
root 18253 17118 0 22:39 pts/1 00:00:00 grep --color=auto nginx
只能看到一个worker process。我们将它改为auto
(然后执行nginx -s reload
重新加载配置),再次查看:
[root@instance-54lh4cfv nginx]# ps -ef | grep nginx
root 18229 1 0 22:38 ? 00:00:00 nginx: master process nginx
nginx 18603 18229 0 22:50 ? 00:00:00 nginx: worker process
nginx 18604 18229 0 22:50 ? 00:00:00 nginx: worker process
root 18614 15276 0 22:50 pts/0 00:00:00 grep --color=auto nginx
可以看到,有两个工作进程,这样它们就能并发的处理请求了。
再次通过命令确认下是否真的只有2个CPU:
[root@instance-54lh4cfv nginx]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 2 #真的只有2个!!
这是个人的云服务器,比较便宜,所以配置不高。
events块
主要影响nginx服务器与用户的网络连接
events {
worker_connections 1024; #每个工作线程 支持最大的连接数
}
http块
这是nginx中配置最常用的部分,http块又包含http全局块以及server块。
http全局块
包含文件引入、MIME-TYPE定义、日志定义、连接超时时间以及单连接请求数上限等。
include mime.types; # 引入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"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
server块
和虚拟主机有密切关系,虚拟主机从用户角度来看,和一台独立的硬件主机是一样的。
每个http块可以包含多个server块,每个server块就相当于一个虚拟主机。
server {
listen 80; #监听80端口
server_name localhost; #主机名称
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location ~ .*\.(jpg|gif|png)$ { #匹配图片文件
root /opt/nginx/app/images;
}
}
下面我们通过反向代理实例来详细讲解下出现的配置。
server中的location
syntax: location [=|~|~*|^~|@] pattern {...}
=
精确匹配,只能使用字符串,不能用正则表达式。
server {
server_name website.com;
location = /abcd {
}
}
- 可匹配
http://website.com/abcd
(精确匹配) - 可能匹配
http://website.com/ABCD
- 可匹配
http://website.com/abcd?param1¶m2
- 不可匹配
http://website.com/abcd/
- 不可匹配
http://website.com/abcde
无符号
server {
server_name website.com;
location /abcd {
}
}
- 可匹配
http://website.com/abcd
- 可能匹配
http://website.com/ABCD
- 可匹配
http://website.com/abcd?param1¶m2
- 可匹配
http://website.com/abcd/
- 可匹配
http://website.com/abcde
~
表示后面是大小写敏感的正则
server {
server_name website.com;
location ~ ^/abcd$ {
}
}
- 可匹配
http://website.com/abcd
- 不可匹配
http://website.com/ABCD
- 可匹配
http://website.com/abcd?param1¶m2
- 不可匹配
http://website.com/abcd/
- 不可匹配
http://website.com/abcde
~*
大小写不敏感的正则
server {
server_name website.com;
location ~* ^/abcd$ {
}
}
- 可匹配
http://website.com/abcd
- 可匹配
http://website.com/ABCD
- 可匹配
http://website.com/abcd?param1¶m2
- 不可匹配
http://website.com/abcd/
- 不可匹配
http://website.com/abcde
在Windows系统中
~
和~*
都是大小写不敏感的
^~
与无符号的行为类似,URI必须以指定的模式开头,如果匹配,nginx会停止搜索其他模式。
@
定义一个命名的location块,该块不能被客户端访问,只能被内部指令访问,如:try_files
或error_page
访问。
下面说下搜索顺序和优先级
因为可以定义多个location块,nginx会搜索最匹配的location
server {
server_name website.com;
location /files/ {
}
location = /files/ {
}
}
当访问http://website.com/files/doc.txt
匹配第一个location;当访问http://website.com/files/
匹配第二个location,尽管第一个location也能匹配,但是第二个location是完全匹配,它的优先级要高于第一个。
location的位置是无关的,nginx会以下面的顺序搜索
- 有
=
的: 如果完全匹配,则返回该location块 - 无符号:如果完全匹配,则返回该location块
- 有
^~
: 如果匹配它指定的开头,则返回该location块 - 有
~
或~*
: 如果正则匹配,则返回该location块 - 无符号,如果匹配它指定的开头,则返回该location块
反向代理
实例
我们要实现的效果是,输入www.test.com ,跳转到springboot后台中。
我们实现一个简单的SpringBoot项目,只需要添加spring-boot-starter-web
依赖。
修改启动文件:
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Value("${server.port}") //注入server.port配置
private int port;
@Value("${message}") //注入自定义的message配置
private String message;
@GetMapping() //配置 / 路由
public String index() {
return "Server Run At Port:" + port +"<br>Message is : " + message;//输出这两个配置
}
}
application.properties文件内容:
server.port=8080
message=Default message
写完后,我们打包成jar文件,并上传到服务器上,然后通过java -jar demo-0.0.1-SNAPSHOT.jar
启动该项目
其中IP进行了脱敏处理哈。
下面就通过nginx来配置反向代理。
本地浏览器访问我们云主机暴露的80端口,由nginx反向代理到云主机上8080端口的SpringBoot服务上。
对于这个www.test.com,我们只需要修改hosts文件即可。
修改C:\Windows\System32\drivers\etc\hosts
文件:
增加以下配置:
106.12.109.* www.test.com
然后先在浏览器中通过8080端口访问下:
可以看到,能够访问,由于是在谷歌浏览器上,它把http://www 都给省略掉了,显示的效果就如上图。
接下来配置nginx的反向代理,修改80端口的server块,改成如下:
server {
listen 80;#监听80端口
server_name 106.12.109.*;#改成linux主机的IP地址
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
proxy_pass http://127.0.0.1:8080;#代理到本机的8080端口
index index.html index.htm;
}
}
然后重新加载
[root@instance-54lh4cfv nginx]# nginx -t #先用-t命令检查下配置是否有问题
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok # ok
nginx: configuration file /etc/nginx/nginx.conf test is successful #successful 没问题
[root@instance-54lh4cfv nginx]# nginx -s reload #重载
然后直接访问80端口,因为80端口是默认端口,可以显示地写出来:
如果访问失败可以检查下是否端口没开放,我这里直接关闭了防火墙。
我们改造下配置,改成根据不同的路径跳到不同的项目中去(这里用两个不同的端口来代替)。
开启两个终端分别执行
java -jar -Dserver.port=8081 demo-0.0.1-SNAPSHOT.jar
java -jar demo-0.0.1-SNAPSHOT.jar
这样就在两个不同的端口上启动了
[root@instance-54lh4cfv java]# ps -ef | grep demo-0.0.1
root 29082 28972 3 20:54 pts/0 00:00:09 java -jar -Dserver.port=8081 demo-0.0.1-SNAPSHOT.jar
root 29219 29136 6 20:55 pts/1 00:00:13 java -jar demo-0.0.1-SNAPSHOT.jar
root 29426 29337 0 20:59 pts/2 00:00:00 grep --color=auto demo-0.0.1
接下来修改nginx配置:
server {
listen 80;#监听80端口
server_name 106.12.109.*;#改成linux主机的IP地址
#charset koi8-r;
#access_log logs/host.access.log main;
location /dev/ {
proxy_pass http://106.12.109.*:8080/;
}
location /prod/ {
proxy_pass http://106.12.109.*:8081/;#代理到本机的8081端口
}
}
接下来重启下Nginx,并访问
细说反斜杠
在配置上面反向代理的时候,发现反斜杠很重要。因此这里做一个详细的探讨。
还是那个SpringBoot项目,我们本地改下代码:
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Value("${server.port}")
private int port;
@Value("${message}")
private String message;
@GetMapping("/demo")
public String demo() {
return "access demo";
}
@GetMapping("/demo/test")
public String demoTest() {
return "access demo/test";
}
@GetMapping("/test")
public String test() {
return "access test";
}
@GetMapping("/sometest")
public String someTest() {
return "access sometest";
}
@GetMapping("/some/test")
public String some_test() {
return "access some/test";
}
}
定义了一些不同路径的接口,然后在本地Windows电脑上安装一个nginx,接下来就可以开始测试不同的情况了。
下面几种情形都用http://localhost/demo/test
进行访问
情形一
将nginx配置修改为:
server {
listen 80;
server_name 127.0.0.1;
location /demo/ {
proxy_pass http://127.0.0.1:8080/;
}
}
最终被代理到 http://localhost:8080/test
所以在proxy_pass
后面的 url加 /
,表示绝对根路径
情形二
location /demo/ {
proxy_pass http://127.0.0.1:8080;
}
最终被代理到 http://localhost:8080/demo/test
如果在proxy_pass
后面的 url没有/
,表示相对路径,把匹配的路径部分都代理过去
情形三
location /demo/ {
proxy_pass http://127.0.0.1:8080/some/;
}
最终被代理到 http://localhost:8080/some/test
情形四
location /demo/ {
proxy_pass http://127.0.0.1:8080/some;
}
最终被代理到 http://localhost:8080/sometest
建议在
proxy_pass
后面不要包含路径
负载均衡实例
但单服务器遇到性能瓶颈时,增加服务器的数量,将请求分发到各个服务器上。
我们想要实现的效果是,在浏览器输入http://www.test.com
,将请求平分到8080,8081,8082端口的服务上去。
先启动这三个服务:
[root@instance-54lh4cfv projects]# nohup java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8080 --message=From_Service_One &
[1] 464
[root@instance-54lh4cfv projects]# nohup: ignoring input and appending output to ‘nohup.out’
nohup java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8081 --message=From_Service_Two &
[2] 504
[root@instance-54lh4cfv projects]# nohup: ignoring input and appending output to ‘nohup.out’
nohup java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8082 --message=From_Service_Three &
[3] 536
[root@instance-54lh4cfv projects]# nohup: ignoring input and appending output to ‘nohup.out’
ps -ef | grep demo
root 464 31269 37 21:10 pts/0 00:00:10 java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8080 --message=From_Service_One
root 504 31269 39 21:11 pts/0 00:00:05 java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8081 --message=From_Service_Two
root 536 31269 27 21:11 pts/0 00:00:01 java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8082 --message=From_Service_Three
可以看到,现在这3个服务启动完毕。
下面进行负载均衡配置。
首先添加upstream
upstream myserver {
server 106.12.109.*:8080 weight=1;
server 106.12.109.*:8081 weight=1;
server 106.12.109.*:8082 weight=1;
}
server {
listen 80;#监听80端口
...
在upstream
中配置负载均衡服务器的列表。
修改loaction
location / {
proxy_pass http://myserver;
proxy_connect_timeout 10;
}
proxy_pass
后面的myserver
就是我们刚才起的upstream
的名称
完整配置如下:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream myserver {
server 106.12.109.*:8080;
server 106.12.109.*:8081;
server 106.12.109.*:8082;
}
server {
listen 80;#监听80端口
server_name 106.12.109.*;#改成linux主机的IP地址
location / {
proxy_pass http://myserver;
proxy_connect_timeout 10;
}
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
这样就实现了负载均衡。
上面我们配置的负载均衡的策略是轮询,默认啥都不配就是轮询。
nginx提供了几种策略。
轮询
每个请求按照时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除;##
weight
weight : 权重默认都为1,权重越高被分配的请求越多。假设某台服务器的配置很高,则可以给它权重设高一点
upstream myserver {
server 106.12.109.*:8080 weight=1;
server 106.12.109.*:8081 weight=1;
server 10.18.109.33:8080 weight=2;
}
ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器:
upstream myserver {
ip_hash;
server 106.12.109.*:8080;
server 106.12.109.*:8081;
server 106.12.109.*:8082;
}
fair(第三方)
按后端服务响应时间来分配,响应时间段的优先分配。
upstream myserver {
server 106.12.109.*:8080;
server 106.12.109.*:8081;
server 106.12.109.*:8082;
fair;
}
动静分离实例
把动态和静态请求分开,可以理解成使用Nginx处理静态页面,Tomcat处理动态页面。
请求图片、HTML页面、JS叫做静态请求;而请求某个后端接口,叫做动态请求。
动静分离目前主要有两种实现方式,一是纯粹把静态文件放到独立的服务器,独立成单独的域名;另外一种是动态根静态文件混合在一起发布,通过nginx来分开。
我们准备两个静态资源,建立两个文件夹:
drwxr-xr-x 2 root root 4096 Dec 7 21:36 images
drwxr-xr-x 2 root root 4096 Dec 12 21:52 www
[root@instance-54lh4cfv app]# pwd
/opt/nginx/app
[root@instance-54lh4cfv app]# ll images
total 192
-rw-r--r-- 1 root root 196114 Nov 5 22:28 test.png
[root@instance-54lh4cfv app]# ll www
total 4
-rw-r--r-- 1 root root 69 Dec 12 21:52 index.html
在images中放一个图片,在www中放一个简单的html页面。
下面来进行配置
server {
listen 80;#监听80端口
server_name 106.12.109.*;#改成linux主机的IP地址
location /www/ {
root /opt/nginx/app;
index index.html index.htm;
}
location /images/ {
root /opt/nginx/app;
autoindex on;#允许列出整个目录
}
}
重启后分别访问www目录(和访问http://www.test.com/www/index.html
效果是一样的,因为当访问/www/
目录时,index index.html
说的是默认会去查看是否有index.html
文件,有的话返回)
可以访问images目录,查看该目录下所有的文件(autoindex on
)
也可以直接访问某张图片