一文弄懂nginx反向代理和负载均衡

基本概念

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&param2
  • 不可匹配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&param2
  • 可匹配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&param2
  • 不可匹配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&param2
  • 不可匹配http://website.com/abcd/
  • 不可匹配http://website.com/abcde

在Windows系统中~~*都是大小写不敏感的

^~

与无符号的行为类似,URI必须以指定的模式开头,如果匹配,nginx会停止搜索其他模式。

@

定义一个命名的location块,该块不能被客户端访问,只能被内部指令访问,如:try_fileserror_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会以下面的顺序搜索

  1. = 的: 如果完全匹配,则返回该location块
  2. 无符号:如果完全匹配,则返回该location块
  3. ^~ : 如果匹配它指定的开头,则返回该location块
  4. ~~* : 如果正则匹配,则返回该location块
  5. 无符号,如果匹配它指定的开头,则返回该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)

在这里插入图片描述

也可以直接访问某张图片

在这里插入图片描述

发布了148 篇原创文章 · 获赞 57 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/yjw123456/article/details/104963415
今日推荐