循序渐进nginx(二):反向代理、负载均衡、缓存服务、静态资源访问


前置知识章节:
1.介绍、安装、hello world、location匹配
2.▶▶反向代理、负载均衡、缓存服务、静态资源访问✅
3.日志管理、http限流、https配置,http_rewrite模块,第三方模块安装,结语。✅


反向代理


💡代理有正向代理和反向代理
💡正向代理:
所谓的正向,就是以请求发起为角度的,此时代理的是用户发起的请求。
用户A无法访问G网,但A能访问B网,而B网能访问G网。那么如果A先通过访问B网,B网帮他访问G网,返回数据给他,那么此时B网就作为了一个代理服务器,而且是正向代理。此时A其实是知道它要访问G网的,G网并不知道是A访问它的,所以此时是代理了A,正向代理。

💡反向代理:
相对于正向代理的代理用户发起的请求,反向代理代理的是资源服务器的请求。
用户A访问B网的某个资源,但B网没有,然后它发给C网,C网返回给B网,B网返回给C网。此时用户A不知道它访问的是C网,所以此时代理的是C网,反向代理。
场景一般是,A能访问B,但不能访问C,B能访问C。我们使用B这台机来对外提供服务,让关于C的请求都先经过B,B再请求C来返回结果。
反向代理有时候用在内网中,用来做请求转发到内网,这样一定程度的保护了内网的资源和利用上了内网的服务器。当然对于我们业务来说,服务器有可能并不是部署在内网中的,也可以用在外网服务器代理上。

💡反向代理是一个非常重要的功能,可以说nginx你最常用的功能或许就是反向代理了,动静分离、负载均衡和缓存有时候你可能用不上。


使用

1.创建代理目标服务端:

我们首先需要创建一个web服务端,由于我是主攻java的,所以我这里部署一个java的web服务器,并让他监听8081端口。(192.168.48.131是我的虚拟机的IP,nginx和这个java web服务端都部署在这里。)
20200614012418
访问一下这个要被代理的服务端的接口http://192.168.48.131:8081/user/list,发现调用成功。
20200614001834


2.配置nginx反向代理目标服务端:

💡2.1 修改server:
因为此时是作为一个代理服务器,所以我们要新建一个server块来充当代理服务器。


❓有人有点疑惑,上面的例子只配了location,我们为什么要配server呢?
其实这里是从业务需求来做区分的。因为我们现在的目标是弄一个代理服务器,当然了你也可以不创建,然后把下面的location放到之前的server下即可。什么时候是必须创建的呢?当server_name不一样的时候,但这个也是需要你根据业务来判断的,比如你原本使用80端口接收发过来的请求,而现在使用8080端口接收发过来的请求的时候就需要一个新的server_name。
这里新建一个server其实也有介绍server_name的用法的意思。


修改server_name:server_name是当前服务端监听的地址的意思,
server_name支持几种语法:

  • 基于确切的域名,域名可以一个或多个,多个使用空格隔开server_name example.com www.example.com
  • 支持通配符的方式,通配符*只能使用在开头或者结尾,不能使用在中间。当有多个匹配结果的时候,会选择最长的匹配结果server_name *.example.com www.example.*
  • 基于正则表达式,当使用正则表达式的时候,开头必须加上一个~server_name ~^www\d+\.example\.com$;
  • 正则表达式支持<>来获取一个变量到后面使用以实现二级域名的功能,这里不讲,有兴趣自查。
  • 上面几个语法的优先级是:
    • 精确域名 > 通配符在前 > 通配符在后 > 正则表达式

💡2.2 修改location
🔵proxy_pass用于设置被代理服务器的地址,可以是主机名称(https://www.baidu.com这样的)、IP地址(域名加端口号)的形式。
🔵下面的这个location的意思是,如果请求路径开头是/api的,那么都代理到proxy_pass指定的地址,比如访问了/api/user/list,那么得到的结果是http://localhost:8081/user/list的结果。

server {
  listen 8080;

  location /api/ {
    proxy_pass http://localhost:8081/;
  }
}

🔵在lcoation都是location /api/时,proxy_pass不同,请求的资源也是不一样的:

  • proxy_pass http://localhost:8081;:请求nginx主机IP:8080/api/user/list,nginx会将该请求代理转发到http://locahost:8081/api/user/list
  • proxy_pass http://localhost:8081/;:请求nginx主机IP:8080/api/user/list,nginx会将该请求代理转发到http://locahost:8081/user/list
  • proxy_pass http://localhost:8081/test;请求nginx主机IP:8080/api/user/list,nginx会将该请求代理转发到http://locahost:8081/testuser/list
  • proxy_pass http://localhost:8081/test/;请求nginx主机IP:8080/api/user/list,nginx会将该请求代理转发到http://locahost:8081/test/user/list


3.测试使用:

在前面,我创建了一个8081的web服务端,而且http://nginx服务器IP:8081/user/list是有一个接口的.
20200614001834
我们试一下使用8080来代理一下这个8081这个服务端:
注意下面的这个server如果写在default.conf的时候,是与其它server块同级的
20200614002940

如果我们能够通过8080来访问到http://192.168.48.131:8081/user/list这个接口,那么就说明了我们的反向代理成功了。此时发向8080端口的,以/api开头的请求都会代理到8081中。

20200614003048


## 其他反向代理指令 下面的其他反向代理指令我解释了一下用途,有需要就自己去了解一下吧。 * proxy_set_header:在将客户端请求发送给后端服务器之前,更改来自客户端的请求头信息。 * proxy_connect_timout:配置nginx与后端服务器尝试建立连接的超时时间。 * proxy_read_timeout:定义用于从代理服务器读取响应的超时。 * proxy_send_timeout:设置用于将请求传输到代理服务器的超时。 * proxy_redirect:用于修改后端服务器返回的响应头中的Location和Refresh。[proxy_redirect](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect)

负载均衡

负载均衡其实也算是基于反向代理的。

上面的反向代理提到一点反向代理可以为我们的服务端进行代理,你有时候可能是多个服务端提供同一功能的,为了让他们能够平摊压力,那么你可能需要负载均衡功能。


使用

1.准备服务端

🔴准备负载均衡用的业务服务端,我这里给的是一个java Spring Boot端的简单代码,我部署成多个服务端的时候,由于端口不同,访问info接口返回的数据也不同,这样就可以测试是否达到了负载均衡。
20200615152747

🔴部署服务端,使用--server.port来部署到不同的端口,例如java -jar a-simple-web-0.0.2-SNAPSHOT.jar --server.port=8082就把我的jar程序部署到了8082端口。



2.修改nginx配置

我们这里新建一个conf.d/loadbalance.conf,由于nginx.conf内部有一个include /etc/nginx/conf.d/*.conf;可以把conf.d下的配置文件都导入到nginx.conf的http块中,所以我们新建的这个conf也是可以导入到nginx.conf中的。
20200615155759



3.测试

访问http://192.168.31.128:9001/user/info,看是否是轮询的分发请求,如果响应的时候返回了不同的端口,那么就证明了是轮询的分发请求。



负载均衡策略

💡默认轮询负载均衡:在不指定负载均衡策略的时候,默认的策略是按顺序给负载均衡服务端发送请求,并且一次顺序中每个服务端处理两次请求,比如两个服务端,那么就是AABBAABB这样循环下去。 如果服务端有宕机的,会从负载均衡顺序中去除。等到宕机的服务端可用后,会在30S(好像是)之后加入到可用负载均衡服务端列表中。


💡加权轮询负载均衡:在轮询的基础上加上权重的考虑,假如服务端A的权重是1,服务端B的权重是2,那么6个请求中,服务端A会收到2个请求,服务端B会收到4个请求。
20200615155954


💡ip_hash负载均衡:每个请求按ip的哈希结果分配到指定的负载服务端响应,(原理类似求余数,把服务端排序之后,根据哈希处理再处理之后得到的余数来选取服务端),这样同一个ip响应的服务端是固定的。可以在一定程度解决服务端Session共享的问题。但这样可能负载压力就分配的不平均了。
语法例子:

upstream ip-hash {
  ip_hash;
  server localhost:8081;
  server localhost:8082;
}

💡也可以使用第三方模块来负载均衡。但由于我们上面没有讲过第三方模块,引入前置知识需要占用大量篇幅,所以这里只引出一下,有需要的自查吧。

  • 第三方模块fair:可以基于响应时间分配请求,优先分配到响应时间短的服务端上。
  • 第三方模块url_hash:可以基于url的hash结果来分配请求。


负载均衡的额外参数

上面介绍了使用weight参数来实现加权轮询负载均衡,其实还有一些其他的参数。

  • max_fails:允许请求失败的次数。默认值是1。
  • fail_timeout:重新检测服务的时间,当服务端请求失败后,会在fail_timeout时间内标记成不可用,fail_timeout时间之后再次检测是否可用,不行就再等fail_timeout时间之后再次检测是否可用。默认是10S.
  • backup:预留的备份服务端。只有当其他服务端都宕机或者处于忙碌状态时,才会分发请求给backuo标注的服务端。
  • down:暂时不参与负载均衡的服务端,只有其他服务端都宕机的时候,这个服务端才参与负载均衡。
  • max_conns:限制接收的最大的连接数。
upstream web-server {
  server localhost:8081 max_fails=1 fail_timeout=10;
  server localhost:8082;
  server localhost:8083 backup;
  server localhost:8084 down;
}

💡使用ip_hash时,不能使用weight和backup。




缓存服务


💡在浏览器访问某个网站的资源的时候,会看浏览器是否缓存了这个数据。如果本地有这个缓存数据,那么就会直接从本地中获取了,不会再请求网络了。这是客户端缓存。nginx传递的响应的某些数据会影响浏览器是否缓存数据以及缓存多久。
💡除了客户端缓存,还有一种代理缓存nginx的缓存是代理缓存,因为它其实是将代理的服务端的结果缓存了。代理缓存使用ngx_http_proxy_module的指令来配置。
💡(除了这两种,还有(后端)服务端缓存,也就是使用redis等技术进行的后端服务端缓存。)。


代理缓存

语法介绍

💡proxy_cache_path path:用来定义缓存文件路径。只能用在http块。
语法:

proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
  • path是缓存文件存放路径
  • levels用于定义文件存放层级,可以有一层(1),两层(1:2),三层(1:2:2)。目录会根据请求URL地址的哈希结果来创建(从末尾开始截取,),假如哈希结果是9cad383e7b0ee3d1d4b7099aace20b3f,那么levels=1:2代表第一层目录为长度为一的字符f,第二层是长度为2的字符b3
  • keys_zone用来定义当前这个缓存存放空间的名字,10m是一个大小
  • max_size用来定义目录最大的大小。
  • inactive用来定义不活跃的缓存多久清除。
  • use_temp_path:缓存临时目录。一般可以不定义。


💡proxy_cache zone|off:可以用在http, server, location。zone是proxy_cache_path的keys_zone.
💡proxy_cache_valid [code...] time:缓存过期周期,过期之后就不返回代理缓存了。 code是http状态码。
💡proxy_cache_key string:配置缓存的标识,比如说请求不一样的话那肯定不返回同样的代理缓存了,这就是是否返回同一个缓存的区分标识。默认值是proxy_cache_key $scheme$proxy_host$request_uri;,可以用在http, server, location。其他例子:proxy_cache_key $scheme$proxy_host$uri$args;



使用例子

下面来做一个实验:尝试使用nginx的代理缓存作为代理的响应。


1.我们新建一个后端接口。这个后端接口能在每一次访问的时候都在控制台打印出一个消息(下面是一个Spring Boot例子):
20200622222735
2.配置反向代理:
20200622223500

3.一开始先测试一下http://192.168.31.128/api/user/info,看每一次访问是否都向后端发起了请求,确实是每一次都发请求的:
20200622223617

4.然后配置代理缓存:
20200626003102

5.再次测试访问http://192.168.31.128/api/user/info是否每一次访问都向后端发起了请求,结果应该是后端服务端只会打出一次,或者你可以尝试关闭后端服务端,然后访问,如果还是能够访问,那么应该是已经代理缓存成功了。



代理缓存补充:

💡永久缓存:上面使用proxy_cache配置的其实是临时缓存。也就是说一定时间后会自动过期。如果你的数据在很长很长时间都不会过期,那么可以考虑使用proxy_store.
💡如果你想让部分请求不要缓存,可以使用proxy_no_cache [string...],string可以是变量,如果存在某个string值不为空也不为0,那么此请求不会被缓存。
💡缓存清理:incative配置会帮我们清除过期的缓存文件,但还没过期的不会清除,需要我们手动清除(场景是比如说你更新了大量数据,此时缓存中的数据很多都错误了,此时需要清除所有缓存。),如果你需要清除的话,那么一种方法是手动定义一个Linux脚本来清除缓存;一种方法是使用模块nginx-cache-purge。这些由于篇幅问题,不讲述。



浏览器缓存

浏览器是怎么判断缓存是否需要使用本地缓存以及缓存是否过期的呢?它通过响应头中的expirecache-controlLast-ModifiedEtag等头信息来判断的。


  • 用于本地校验是否过期的头:expireCache-control(max-age),如果有本地缓存,那么会使用expire来判断是否过期,不过期,直接使用本地缓存,本地过期之后,再进行远程校验。
  • 用于远程校验的Last-Modified头信息:用于远程文件修改校验的,是一个GMT时间,例如Thu, 02 Jul 2020 01:05:03 GMT,如果校验时间不一致,那么不使用本地缓存,一致则使用本地缓存。
  • 用于远程校验的的Etag头信息:用于远程文件修改校验的,是一个类时间戳的数据,例如:"5efd32bf-3fa8e",如果校验时间不一致,那么不使用本地缓存,一致则使用本地缓存。Etag与Last-Modified的区别是,Etag更精确,所以会优先判断Etag,然后再判断Last-Modified。
  • 相关nginx模块:ngx_http_headers_module

❗浏览器缓存机制你可以自己了解一下:博客园-HTTP缓存机制


💡Expire

  • 语法:expires [modified] time;
    • modified用于执行修改后过期,比如expires modified +24h;就代表修改后24小时内不过期。
    • time:过期时间,例如有expires 24h;24天不过期,expires 30d;30天不过期,expires -1;代表永不过期

测试

测试之前我们先要提几点:

  • 浏览器有默认的缓存策略,Etag和Last-Modified默认是自带的,会有基于对文件修改的缓存,第一次响应200之后,第二次响应为304的时候,浏览器还是会发请求,但此时发请求用于测试文件是否过期,不过期则不会传输文件
  • 当配置了expires的时候怎么判断它生效了呢?响应码为200,并且不对nginx发起请求。
  • expires并不会在任何情况都生效,比如说F5刷新Ctrl+F5刷新就无效,此时测试应该使用在链接栏按回车发请求来测试。expires可用于什么情况可以参考博客园-HTTP缓存机制

1.我们先在/usr/share/nginx/html下面存储一张图片,后面会通过访问这种图片,来测试客户端缓存。


2.我们一开始先不要配置expires:

server {
    listen       80;
    server_name  127.0.0.1;
    location / {
      root /usr/share/nginx/html;
    }
}

3.多次访问一下http://192.168.31.128/a.jpg看看nginx的访问日志/var/log/nginx/access.log是否有添加
此时要多留意每次请求的响应码,应当有以下几个情况:

  • 如果你使用F5刷新,那么第一次响应码为200,后面都是304,第一个响应码为200的时候用于获取文件,后面的304会检查文件是否修改,不修改则使用缓存的文件,此时每次刷新都应该发了一次请求,access.log中可以观察到。
  • 如果你使用在链接栏按回车发请求来测试,那么响应码应当都是200,是每一次都会去请求文件,此时每次按回车都应该发了一次请求,access.log中可以观察到。

4.加了expires之后看看,我们简单的使用两分钟看看。

server {
    listen       80;
    server_name  127.0.0.1;
    location / {
      expires 2m;
      root /usr/share/nginx/html;
    }
}

5.多次访问一下http://192.168.31.128/a.jpg看看nginx的访问日志是否有添加

  • 如果你使用F5刷新,由于expires不会在F5刷新时生效,所以效果应该和未配置之前是一样的。
  • 如果你使用在链接栏按回车发请求来测试,那么一开始响应码应当都是200但不会真正的发起请求,而是使用缓存中的数据,access.log中不会看到请求。(因为我上面定义缓存是两分钟)两分钟之后第一次请求是304,用于校验文件是否修改,如果没修改,那么后面的两分钟之内的响应又是不发请求的200。


静态资源访问

💡nginx可以对外接收静态资源访问的请求。

💡在上面的location的内容的时候,其实有讲到返回文件资源的知识。
比如location ~* \.(gif|jpg|jpeg)$匹配任何以.gif、.jpg 或 .jpeg 结尾的请求,然后你发的请求匹配成功的时候,会使用root+location得到的路径的资源作为响应。其实这些也就是静态资源了,所以其实也可以通过nginx来达到静态资源的访问。

💡在前后端分离之后,前端作为静态资源访问,应该部署到哪里呢?因为我们此时是不应该把前端静态资源部署到后端服务器上的。那么这时候放到nginx服务端上也是可以的。利用nginx的静态资源访问作为前端的服务端。

💡或者你也可以把nginx作为文件资源访问的服务端。




posted @ 2020-07-22 08:53  随风行云  阅读(2450)  评论(0编辑  收藏  举报