Nginx+lua+openresty系列 | 第三篇:nginx反向代理

Posted 秃头A计划

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx+lua+openresty系列 | 第三篇:nginx反向代理相关的知识,希望对你有一定的参考价值。

上篇文章,我们了解了nginx的error_page和虚拟主机的配置。今天我们就讲讲nginx的反向代理。nginx的负载均衡配置就依赖于反向代理。


1.什么是反向代理


首先看官方说法:


反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。


有反向代理,那么是不是就应该有正向代理?我们讲个找媳妇儿的故事了解下正向和反向代理。

角色设定:(读完故事在看后面的解释)

  1. 秃头男:代表客户机。找媳妇动作=要访问促进中日交流的小网站

  2. 王婆:代表正向代理服务器。找月老动作=找到目标网站

  3. 月老:代表反向代理服务器。把媳妇说给王婆动作=把正向代理服务器要访问的网站内容返回

  4. 众仙子:代表服务器资源。等于网站内容。



话说秃头男,已年方二十有四,仍然还是单身狗一只,虽然钱多话少,依然得不到姑娘的芳心啊。秃头男着急啊,自己谈不到媳妇那就相亲吧!相亲得去找媒婆,村头的王婆可是这方面老手了,一相一个准。于是乎,秃头男厚着脸皮去找王婆:王婆,俺要找媳妇,俺现在就要!王婆欢心道:你可找对人了,媳妇可不是你想找就能找,没有本婆子,村里头的那些秃头们也只能靠麒麟臂了,这不村西的王二麻子刚刚给他找了个媳妇哟。


于是,王婆连线找到月老,告诉月老:又来生意了,快给我整个媳妇过来。月老回复到:666啊,马上就给你整。月老这边忙把众仙子叫来:你们都是犯了天条的仙子,玉皇大帝慈悲,只罚你们与凡人结亲。我从尔等中择一人,去与凡人结亲去吧。



故事看起来没什么意思,仅仅为了帮助理解。那我们就来分析下整个正向和反向代理的过程。


故事解析:

比如我要访问google网站,就是秃头男找媳妇的动作。在浏览器敲入www.google.com,但是你发现你的机器访问不了,为啥呢?这是政府企业层面的原因,咱小小平民也管不着啊。然后我就要访问怎么办?找个代理服务器,也就是俗话说的fanqiang软件,就是王婆了。这时候fq软件连接到了google服务器,但是谷歌考虑到了我是为全球用户服务,我的用户体验一定要好,响应速度要快,怎么办呢?简单做法多搞几台服务器(实际复杂的要死...),服务器内容是一模一样的,这样可以同时为多个用户服务。然后统一由google服务器根据策略选择内部的哪台服务器上的内容返回给用户。google服务器就是月老啦,对外表现就是一个反向代理服务器。内部的多台服务器就是众仙子。然后层层返回,最终我们访问到了谷歌网站,也就是娶到媳妇啦。

对于正向代理服务器来说,是主动方;反向代理服务器,是被动方。反向代理服务器感受不到客户机的存在,同样的,正向代理服务器感受不到众仙子的存在。一切都是由代理方也就是中间人去沟通,头尾做的事情就是一个要一个给。看起来就好像是,正向代理和反向代理两个人在通信。可以只存在正向代理,也可以只存在反向代理,又或者是两者皆存在。


2.反向代理有何用途


  • 网关服务器

  • 负载均衡

  • 请求转发、请求统计

  • 黑白名单拦截


nginx常用做反向代理服务器,用于请求拦截控制。作为一个代理人,把原本请求到nginx服务器上的请求,转发到符合规则和策略的资源上或者访问者是在黑名单上,拒绝访问。作为一个网站的统一入口,可以灵活的控制请求的访问次数、统计访问记录、拦截不安全访问等等。


3.如何配置反向代理


废话少说,还是直接撸代码比较直接。千言万语,不敌千次敲击。


这里先讲下location

第二篇文章简单了说了下location,并没有说怎么用的。

1location / {
2            root   html;
3            index  index.html index.htm;
4        }

上面代码片段是nginx的默认配置,启动nginx服务,程序就会监听该机器的80端口,然后location拦截与其URI路径相匹配(这里是根路径/)的请求。最终请求进入到location块,展示nginx欢迎页。我们的url现在比较流行的是restful形式,还有以.do结束的请求,还有静态文件的请求以 .jpg、.js、.css等请求,为了方便这些请求的管理,于是乎location就有了匹配规则。可以使用正则表达式拦截静态文件

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

location的先后匹配顺序不用过分研究,实践见真章,试试就能知道,心里大致有个数就好。



反向代理实战部分


我们做一个简单的demo演示:我们以代理服务器的身份访问Google网站,模拟网站内部服务器的调用。


操作步骤:

  • 配置upstream模块,用于选择哪台服务器。该配置位于HTTP块内,server块外。

  • 活学活用,使用虚拟主机模拟两台内部服务器

  • 配置proxy_pass命令,使用反向代理功能 (待会详解概念含义,先看效果,再去理解

  • 启动测试




Nginx+lua+openresty系列 | 第三篇:nginx反向代理


如上图所示:注意三个地方


特别注意:upstream别名不要使用下划线


  • upstream块,紧跟着后面有一个to_google别名,因为可以配置多个upstream块,所以为了区别需要别名,且在server块使用的时候需要指定该别名。块内是两个server,也就是选择哪台内部服务器展示网站内容的域名,也可以写成  ip:port 形式。也可以只写一个server,相当于请求转发;配置多个就是负载均衡。

  • location内的proxy_pass配置。使用 http:// + upstream别名的形式,也就是当访问www.google.com域名的时候,location匹配到根路径,然后使用反向代理功能,将该请求‘跳转’到upstream块中,由该块决定请求策略。upstream块发现有两个server可以使用,于是使用轮训策略(默认情况下),依次访问localhost:5588和localhost:6699

  • 内部服务器使用的是上篇中的虚拟主机功能,使一台主机可以作为多个网站使用。(别忘了配置hosts文件哦,需要将谷歌网站指向本地)


我们看下访问效果:

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

Nginx+lua+openresty系列 | 第三篇:nginx反向代理


这样nginx反向代理服务器对外就是一台真正的服务器,对内它是一个管理者,控制着请求的指向,也就是让你朝东你不能朝西,让你打狗你不准撵鸡...。



我们详细说说proxy_pass 这个东西。

聪明的读者都会发现,nginx的配置文件除了events块,http块,server块等块内容,基本都是key:value的形式。虽然nginx不是一门语言,但是它也有变量,可以自定义变量也可以直接使用模块提供的内置变量(我们一直说的模块就类似于Java的类库,有nginx自带的也有第三方的)。proxy_pass 就是nginx的ngx_http_proxy_module模块提供的配置。

打开nginx官网,点击右侧的documentation进入文档查看页,如下图


nginx的变量要讲的话几篇文章也搞不定,有兴趣的同学可以看看章亦春的漫谈变量系列http://blog.sina.com.cn/s/articlelist_1834459124_1_1.html。


Nginx+lua+openresty系列 | 第三篇:nginx反向代理

打开proxy模块,我们可以看到很多的命令配置,包括HTTP内容的cache、header等相关命令。

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

我们ctrl+f 输入proxy_pass快速定位到该命令(这个肯定会吧?),然后点击超链接到指定的文档位置,查看文档释义

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

具体用法和含义都是英文文档,所以要有点英语功底能看懂,不行就复制到Google翻译去也行。模块的内置变量在最下面的Embedded Variables部分。因为proxy_pass这个命令很常用,反向代理必备,所以不用看文档也知道它是干嘛的,但是其他的命令可能就需要琢磨一下子了。


4.用户IP传递问题


  
    
    
  
1#左右滑动查看代码
2server {
3   listen       80;
4   server_name www.google.com;
5   location / {
6      echo 'Hello, This is 80 machine! remote_addr=$remote_addr';#使用内置变量获取客户机ip地址
7       #proxy_pass http://to_google; # 井号是注释符号
8    }
9}


  
    
    
  
1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:80
3Hello,This is 80 machine! remoteAddr= 192.168.1.132


 1#左右滑动查看代码
2[root@localhost ~]# ifconfig
3ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
4        inet 192.168.1.132  netmask 255.255.255.0  broadcast 192.168.1.255
5        inet6 fe80::a093:c75b:1854:e4a  prefixlen 64  scopeid 0x20<link>
6        ether 00:50:56:37:8c:de  txqueuelen 1000  (Ethernet)
7        RX packets 5482  bytes 650903 (635.6 KiB)
8        RX errors 0  dropped 0  overruns 0  frame 0
9        TX packets 5211  bytes 537377 (524.7 KiB)
10        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

4.2.然后我们使用代理再来看下是什么结果,代码如下

 1    #左右滑动查看代码
2    upstream to_google{
3        server localhost:5588;
4        server localhost:6699;
5    }
6
7    server {
8        listen       80;
9        server_name www.google.com;
10        location / {
11            proxy_pass http://to_google;#反向代理
12        }
13    }
14    server {
15        listen       5588;
16        location / {
17           echo 'Hello, This is 5588 machine! remote_addr=$remote_addr';
18        }
19    }
20    server {
21        listen       6699;
22        location / {
23           echo 'Hello, This is 5588 machine! remote_addr=$remote_addr';
24        }
25    }



当我们不代理的时候,初次请求确实是客户机请求过来的。但是经过代理之后,对于虚拟主机来说,它的上次请求是从nginx转发过去的,也就是本机,本机ip就是127.0.0.1咯。



1#左右滑动查看代码
2@RestController
3public class NginxController {
4    @RequestMapping("/nginx")
5    public String nginx(HttpServletRequest request){
6        String remoteAddr = request.getRemoteAddr();
7        return "remoteAddr:"+remoteAddr;
8    }
9}

结果就是和nginx的结果一致,不同的是只要代理到java程序,使用request获取到的ip都是nginx服务器的ip。所以不要质疑nginx的能力,它就是HTTP请求的私人定制。


解决方案:

 1    #左右滑动查看代码
2    upstream springboot{
3        #springboot项目开启18080端口 最终访问者
4        server 127.0.0.1:18080;
5    }
6    upstream twoProxy{
7        server 127.0.0.1:5588;
8    }
9
10    server {
11        listen       80;
12        location / {
13            #访问80端口 直接代理到spring boot
14            proxy_pass http://springboot;#试验一次代理的情况
15            proxy_set_header X-Real-IP $remote_addr;
16            proxy_set_header Host $host;
17            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
18        }
19    }
20    server {
21        listen       81;
22        location / {
23            #访问81端口,先代理到5588端口,再通过5588代理到spring boot
24            proxy_pass http://twoProxy;#试验多次代理的情况
25            proxy_set_header X-Real-IP $remote_addr;
26            proxy_set_header Host $host;
27            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
28        }
29    }
30    server {
31        listen       5588;
32        location / {
33          proxy_pass http://springboot;
34          proxy_set_header X-Real-IP $remote_addr;
35          proxy_set_header Host $host;
36          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
37        }
38    }


看一下上面的代码配置。

一次代理:localhost:80  --> localhost:18080

二次代理:localhost:80 -->  localhost:5588 --> localhost:18080


每个location块都贴上了proxy_set_header,且使用了多个内置变量。

proxy_set_header的意思是将每次代理的HTTP请求都设置了一个请求头,key X-Real-IP, value就是内置变量,这样在java项目中就可以使用request获取到请求头中的内容。X-Real-IP只是一种头的字符串表示,因为用的多了所以也就成了标准,大家都用,看到了也就明白什么意思,类似的X-Forwarded-For也是。$proxy_add_x_forwarded_for则是proxy模块的一个内置变量


  • $proxy_add_x_forwarded_for

  • the “X-Forwarded-For” client request header field with the $remote_addr variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.


碰到这么长的字符串,很多人就会怕,感觉很难。因为大脑是惰性的,碰到陌生的东西就反感。当你去了解去接触的时候,就会发现就那么回...


我们看下springboot项目的代码,很简单,获取请求头中的内容并输出

 1#左右滑动查看代码
2@RestController
3public class NginxController {
4
5    @RequestMapping("/nginx")
6    public String nginx(HttpServletRequest request, HttpServletResponse response) throws IOException {
7
8        String remoteAddr = request.getRemoteAddr();
9        String remoteHost = request.getRemoteHost();
10        String realIP = request.getHeader("X-Real-IP");
11        String forward = request.getHeader("X-Forwarded-For");
12        String Host = request.getHeader("Host");
13
14        PrintWriter writer = response.getWriter();
15
16        writer.println("remoteAddr:"+remoteAddr);
17        writer.println("remoteHost:"+remoteHost);
18        writer.println("Host:"+Host);
19        writer.println("realIP:"+realIP);
20        writer.println("forward:"+forward);
21
22        return null;
23    }
24}


最后测试看结果:(因为只有一台机器,只能使用VMware虚拟机访问)

一次代理

1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:80/nginx
3remoteAddr:127.0.0.1
4remoteHost:127.0.0.1
5Host:192.168.1.1
6realIP:192.168.1.132
7forward:192.168.1.132

二次代理

  
    
    
  
1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:81/nginx
3remoteAddr:127.0.0.1
4remoteHost:127.0.0.1
5Host:192.168.1.1
6realIP:127.0.0.1
7forward:192.168.1.132, 127.0.0.1


分析:


proxy模块还有其他更多的指令配置,一般常见就是设置头和设置反向代理一起使用,有兴趣可以研究下其他指令,官方文档写的都很详细。


我们后续将研究nginx负载均衡的功能。



本文如有瑕疵错误,欢迎指正!



以上是关于Nginx+lua+openresty系列 | 第三篇:nginx反向代理的主要内容,如果未能解决你的问题,请参考以下文章

高并发 Nginx+Lua OpenResty系列——Lua模版渲染

高并发 Nginx+Lua OpenResty系列——Lua模版渲染

Nginx+lua+openresty系列 | 第三篇:nginx反向代理

高并发 Nginx+Lua OpenResty系列——HTTP服务

高并发 Nginx+Lua OpenResty系列——HTTP服务

高并发 Nginx+Lua OpenResty系列(10)——商品详情页