一文带你掌握nginx的基本使用
nginx是什么
在开发中我们对nginx肯定不陌生,不管是前端开发,还是后端开发,多少会听说过nginx的大名。那么nginx到底是个什么东西呢?我们可以将其看做是一个转发请求的工具。如下图所示:

客户端不直接请求web服务器,而是将请求发给nginx,由nginx将这个请求转发给对应的服务器。至于具体转发给哪个服务器,则取决于nginx的配置文件中所配置的转发策略。
nginx的使用场景
作为集群应用的入口
稍微有点常识的工程师都会知道我们在工作中的应用肯定是不会单机部署的,因为如果单机部署,只要这个服务器挂掉了,那么整个应用就无法使用了,这会影响用户体验,导致客户流失。所以,实际工作中不管我们的用户是多是少,一般都会部署到至少两台服务器上来提高应用的可用性。(可用性是指用户可以正常使用应用所具备的功能)。在软件开发中,我们将多个功能相同的应用称之为集群。
下图中的3台功能完全相同的web服务器就组成了一个集群

那么问题就来了,前端如果要将后端所有服务器的ip地址写死到应用中,这未免也太麻烦了,而且后端应用的ip和端口号可能会随时发生变化,这种设计不能很好应付web服务ip和端口可能会变化的场景。
我们希望前端应用只需要请求一个地址,而且其发送的请求要有机会触达集群中的任何一个机器,那么怎么实现呢?其实前面那张图已经给出答案了,那就是使用nginx。

前端应用不需要知道后端所有web服务器的ip和端口号,它只需要知道nginx的ip和端口号即可。前端应用直接向nginx发送请求,由nginx将请求转发给具体的服务器,然后服务器将响应返回给nginx,nginx再将服务器返回的响应报文返回给前端应用,整个过程如上图所示。
所以这里就引出了nginx的第一个使用场景,那就是作为web服务集群应用的入口,降低前端应用请求接口的复杂度。nginx就像我现在工作中的领导,他只负责分配任务给我们这些一线打工人,它自己并不用下地干活。
负载均衡
前面我们已经说过了nginx是集群应用的总入口,由nginx来负责请求的转发或者说是任务的调度。那么它是如何分配请求任务的就很关键了,我们希望它能公正一点,不能旱的旱死涝的涝死。
如果所有服务器的性能差不多,我们希望nginx能够平等对待每台服务器,每次来了一个任务,nginx能够按照服务器的名单一个个去分配任务,这样每个服务器收到的任务数量是差不多的。
如果每个服务器的处理能力参差不齐,有天壤之别,就好像我们工作中会碰到有的同事能力特别强,一个人顶10个人,还有的同事做事水平实在不敢恭维。碰到这种情况,我们应该让能力强的服务器多干一些活,不要浪费它的能力。让能力弱的服务器少干一点活,反正就算给它了,它也处理不过来。
还有的时候,某个服务器因为掌握了特殊资源,比如用户登录后的session数据,特定用户的请求过来后只能让它去处理,如果交给其他服务器去干这个活肯定会出问题。这种情况我们又需要一种完全不同的任务分配方式。
还有些场景需要有比较好的用户体验,我们希望用户能够以最快的速度拿到响应结果,而不是让我们的应用去试探用户的耐心的底线。这种情况下,我们希望nginx每次将任务分配给脑子转的最快,能最快给出答案的服务器。
在软件开发中,我们将上面这些不同的分配任务方式称之为负载均衡的策略,它们分别是:
- 轮询策略:轮流分配任务给服务器,相当于一个排班表,咱们轮流值班。
- 随机策略:随机分配,和轮询策略的作用差不多,只不过不再是轮流值班了,而是抽签值班。
- 权重轮询:根据服务器能力来分配任务,给能力强的服务器多分配一些请求任务。如果你记不住这个策略,那就想想见人下菜碟这句话。
- ip_hash策略:对于同一个ip发过来的请求,每次都转发给同一个服务器,适用于会话保持的场景。
- 响应时间加权策略:每次有限将任务分配给脑子转的最快的服务器,因为它们能够以最快的速度给出结果,能够让用户有更好的体验。
当然nginx的负载均衡策略有很多种,我上面只是列举了最常用的5种,后面还有最少连接策略,基于哈希的负载均衡,加权最少连接(相当于结合加权策略和最少连接策略)就留给大家自行摸索了。
处理请求和响应
相信有过java开发经验的朋友应该对spring aop和切面编程不会感到很陌生,如果我们写一个@Around环绕通知的切面,那么在这个切面中我们是可以在请求到达具体的接口前,对请求参数,请求头做一些处理,而且我们还可以在接口返回响应结果前,对响应头,响应内容做一些特殊处理。

我们再回顾一下这张图,既然所有的请求和响应都会经过nginx,那nginx自然也是有能力对请求参数,请求体做一些预处理的,以及对web服务器返回的结果做二次处理。
解析静态资源
nginx是非常优秀的高性能静态资源渲染服务器,我们经常会使用nginx来处理高并发的静态资源请求。这里解释一下什么是静态资源,什么是动态资源。静态资源就是内容不会在发生变化的文件,动态资源就是内容会随时变化,随时调整,需要实时渲染的文件。
对于动态资源的处理,比如写接口从数据库中查询数据,由于每个用户的数据都是不一样的,所以需要实时渲染。常用的动态资源处理服务器包括tomcat,jetty,Undertow等等。
而对于静态资源的处理,最常用的就是nginx了,它性能高且内存占用小,堪称web服务器的瑞士军刀。
nginx的基本使用
前面说过了nginx的使用场景,接下来我们动手实践一下,感受下nginx的魅力。
负载均衡
其实我们在之前的一文带你实现分布式锁中就已经用过nginx的负载均衡功能了,这里我带大家复习一下。
我在本地开发了一个商城应用,采用集群部署,将商城应用部署了3份,端口号分别是:8401,8402,8403。
我希望nginx能够将请求轮询分配给这3个应用,下面是我的nginx配置(默认的负载均衡策略就是轮询,所以我这里没有配置负载均衡的策略)
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream jdMall {
server 127.0.0.1:8401;
server 127.0.0.1:8402;
server 127.0.0.1:8403;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://jdMall;
proxy_set_header Host $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;
}
}
}
为了演示方便,我这里使用的是Windows版的nginx,大家可以去nginx官网下载。
修改完nginx.conf配置文件后,启动nginx,如下图所示:

在cmd中执行指令: netstat -ano | findstr :80 可以看到80端口处于监听的状态,说明nginx启动成功了。

然后我们使用浏览器请求商城应用中的心跳接口:

我们使用浏览器请求3次这个接口后,会发现这3个应用每个都收到了一个心跳请求,这说明nginx的确是以轮询的方式将请求转发给应用服务器。

接下来,我们修改负载均衡的策略,将其改成ip_hash的策略,如果使用这种策略,由于我们的客户端的ip地址是不会变的,所以我们在浏览器发送的请求应该始终只会转发给某一个应用。

在上图中我将nginx的负载均衡策略改成了ip_hash,然后我们重启下nginx。Windows电脑上重启nginx的方式和Linux不太一样。这里具体说一下:
- 首先执行
taskkill /F /IM nginx.exe这个指令杀死nginx进程。
- 然后再次执行
nginx.exe指令,启动服务器即可。
接下来我们再次通过浏览器请求应用的心跳接口,连续请求3次

可以看到,所有的请求都转发给了8403这个应用,这说明我们的ip_hash策略生效了。
反向代理(微服务的总入口)
反向代理又是一个专业术语,其实它和负载均衡的功能非常相似,这取决于各个应用的功能是否相同,如果是相同的应用组成的集群,那么nginx将请求转发给这个集群中的中的某个服务器,这叫负载均衡。
如果应用的功能是不同的,比如有的服务只处理和订单有关的业务,有的服务只处理和物流有关的业务。如果我们将一个应用的不同业务拆分成多个功能不同的服务,我们将这种架构称之为微服务架构。如下图所示:

由于每个服务的ip地址和端口号都不一样,所以我们同样需要一个总入口。在微服务架构中,我们将其总入口称之为网关。nginx自然是可以作为所有微服务的总入口的,所以nginx可以充当网关的角色。
我们没必要纠结反向代理是什么意思,其实它的意思就是说nginx可以作为所有微服务的总入口。
现在我希望nginx能够将所有/dev/api/stock开头的请求转发给库存服务,将所有/dev/api/order开头的请求转发订单服务,将所有/dev/api/transport开头的请求转发给物流服务。
为了演示nginx反向代理的功能,我这里创建了一个TaoBaoMall的微服务

然后我们修改nginx的配置文件,最终的nginx配置文件如下:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
resolver 8.8.8.8 8.8.4.4; # 显式指定DNS服务器,确保Nginx能解析域名(即使localhost也需要)。
# 定义映射关系, 配置不同前缀的url与对应服务器的映射关系,
map $uri $TaoBaoMall {
# 直接使用127.0.0.1替代localhost,避免潜在的解析问题, 前面因为使用localhost导致报了502 bad gateway的错误
~^/dev/api/stock http://127.0.0.1:8202; # 这里相当于是在定义变量,~^/dev/api/stock是uri变量的值,http://127.0.0.1:8202;则是在定义变量$TaoBaoMall的值
~^/dev/api/order http://127.0.0.1:8201;
~^/dev/api/transport http://127.0.0.1:8203;
}
server {
listen 80; # nginx服务器的端口号,类似于Springboot项目的server.port
server_name localhost;
location ~ ^/dev/api/ {
proxy_pass $TaoBaoMall$uri;
proxy_set_header Host $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;
}
}
}
然后我们使用.\nginx.exe -t的指令检验配置文件是否是正确的,在重启nginx前一定要这样做,否则nginx会重启失败,影响在线用户的体验。

上图说明我们修改后的nginx配置文件是有效的,然后我们重启nginx。
接下来我们使用浏览器请求下单接口:

可以看到请求确实到了订单服务,这说明我们对nginx的配置是有效的

然后我们再试下使用浏览器请求库存服务的接口:

可以看到请求确实转发到了库存微服务,再次说明我们的nginx配置是有效的。

类似的,我们请求物流服务的接口:

可以看到请求已经到了物流微服务,这一切都说明了我们的nginx配置是有效的。

前端应用只需要向nginx发送请求,即可访问所有的微服务接口,因为nginx可以通过接口的前缀配置路由规则,不同的接口url前缀转发给不同的服务器。
就这样,nginx成了所有微服务的入口,nginx的反向代理功能到此演示完毕!
处理请求和响应
前面我们讲过,我们可以将nginx看做是拦截器,可以处理每个请求的请求头和入参,以及每个请求的响应结果和响应头。现在我们来演示下给客户端请求添加请求头和响应头的过程,看看有没有效果。
我在订单微服务中写了一个接口用来获取所有的请求头,如下所示:
@Slf4j
@RestController
public class TestController {
@GetMapping("/getAllRequestHeaders")
public RespBean getAllRequestHeaders(HttpServletRequest request) {
log.info("返回所有请求头");
Enumeration<String> headerNames = request.getHeaderNames();
HashMap<String, String> reqHeaders = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
log.info("headerName:{},headerValue:{}", headerName, headerValue);
reqHeaders.put(headerName, headerValue);
}
return RespBean.success(reqHeaders);
}
}
接下来我们修改配置文件,为请求和响应分别添加请求头和响应头

然后重启nginx,使用浏览器查看请求头
可以看到:当我们直接请求订单微服务时,并没有看到password的请求头

而当我们通过nginx请求订单微服务时,可以发现多了一个passoword请求头,除此之外,我们确实还能看到user-agent的值被nginx改成了CSDN

这里说明了nginx是可以处理请求的,下面我们来看下响应头。
我们先直接请求订单微服务

然后我们通过nginx访问订单服务,发现确实多了一个plan-to-do的响应头,这说明nginx确实可以对微服务的响应进行二次处理。

静态资源渲染
前面我们说过nginx是一个非常优秀的静态资源渲染服务求,现在我们就来见识一下。
首先我们修改nginx的配置文件,如下:
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 localhost;
# 注意:这里要设置utf-8编码格式,告诉nginx使用utf-8这种方式解码文件,否则通过nginx访问文件时会出现乱码
charset utf-8;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
下面是我写的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>csdn-nginx</title>
</head>
<body>
<h1 style="text-align: center;color: red">演示nginx的静态资源渲染</h1>
<a href="./picture/godGirl.jpg">点击我查看美女照片</a> <br/>
<a href="./video/beaty.mp4">点击我查看电影</a> <br/>
<a href="./files/nginx.txt">点击我查看文件</a>
</body>
</html>
然后我们访问首页,可以看到我写的index.html文件的内容

点击首页中的超链接,我们可以查看对应的文件内容,照片或者视频

也就是说,我们可以通过nginx访问磁盘上的任何一个文件 ,这就是nginx的静态资源渲染功能,也是我最喜欢的一个功能。
文章中用到的代码

最后的总结
这篇文章中我们讲了nginx是什么,以及它的使用场景,并且带大家探索了nginx的一些基本功能,比如负载均衡,反向代理,请求和响应的处理,静态资源的渲染等等。相信大家目前对nginx应该有了一个不错的印象。
关于nginx有很多值得写的东西,比如nginx的健康检查,为静态资源的路径起别名等等,限于篇幅限制,不能再为大家一一列举了,有兴趣的朋友可以自行研究。
觉得有收获的朋友可以点个赞,您的鼓励就是我最大的动力!
更多推荐


所有评论(0)