Java道经第3卷 - 第8阶 - Nginx
文章目录
心法:本章使用 Maven 父子结构项目进行练习
练习项目结构如下:
|_ v3-8-ssm-nginx
|_ 13801 nginx-a
|_ 13802 nginx-b
|_ 13803 nginx-web
武技:搭建练习项目结构
- 创建父项目 v3-8-ssm-nginx,删除 src 目录。
- 在父项目中管理依赖:
<dependencyManagement>
<dependencies>
<!--spring-boot-starter-parent-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<!--声明该项目是一个POM类型的父项目,不参与打包,只用于传递依赖-->
<type>pom</type>
<!--导入该项目的 `<dependencyManagement>` 到本项目-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 在父项目中添加依赖:
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
</dependencies>
S01. Nginx基础入门
E01. 基础概念入门
心法:Nginx 是一款开源的服务器和反向代理 Web 服务器,邮件服务器,支持很多第三方的模块扩展。
Nginx 服务端启动模式:Nginx 服务端采取多进程模式,启动 Nginx 服务时,后台会至少启动 2 个进程:
- 守护进程
Master
:负责向 Worker 转发外界信号,负责监控 Worker 状态,当 Worker 异常时会自动重启新的 Worker 进程。 - 工作进程
Worker
:负责监听端口和处理用户请求,Worker 之间相互隔离独立,Worker 数量建议配置与 CPU 核数一致。
1. 正向代理模式
心法:正向代理是客户端的代理,对客户端负责,客户端发送请求到代理服务器,代理服务器转发请求到具体服务器,响应也是原路返回。
正向代理优势如下:
- 破解限制:某服务器限制客户端直接访问,则可以找个可访问的代理服务器来访问该服务器,比如 KX 上网。
- 加速访问:电信客户端访问联通服务器太慢,则可以找个电信联通都能访问的代理服务器调节,比如游戏加速器。
- 缓存数据:每次请求获取的数据都可以缓存到代理服务器中,后续相同的请求可以直接从缓存中获取。
- 保护客户端隐私:服务端仅知道请求来自于哪个代理服务器,但并不知道请求具体来自于哪个客户端。
2. 反向代理模式
心法:反向代理是服务端的代理,对服务端负责,代理服务器接收到请求之后,按一定规则分发给某个具体的服务器,响应也是原路返回。
反向代理的优势如下:
- 负载均衡:代理服务器可以使用多种负载均衡策略分发请求,分摊服务器集群的压力 。
- 保护服务端隐私:客户端仅知道请求发送给了哪个代理服务器,但并不知道请求具体由哪台服务器进行处理。
3. Nginx服务器安装
心法:Windows 系统中 Nginx 服务器启动和核心是 logs/nginx.pid 文件,后文简称 PID 文件。
PID 文件须知:
- 使用 CMD 命令启动 nginx 服务时,会自动生成 PID 文件,该文件用于记录 nginx 服务中 master 进程的 ID 号。
- 使用 CMD 命令关闭 nginx 服务时,会根据 PID 文件内容来结束 nginx 所有后台进程,然后自动删除 PID 文件。
- 多次启动 nginx 服务不会端口占用,但会覆盖 PID 文件中的内容,进而导致之前的 nginx 进程失联并泄露,不建议。
- 双击
nginx.exe
也可以启动 nginx 服务,但不会生成 PID 文件,此时只能在任务管理器中手动结束相关进程,不建议。
武技:在 Windows 系统中安装 Nginx 服务器
- 下载 nginx for windows 服务器的压缩包
nginx-1.20.2.zip
,然后加压缩到D:\nginx
即可。 - 启动nginx服务:
# 切换到nginx家目录
cd D:\nginx
# 启动nginx服务并生成PID文件
start nginx
# 查看nginx服务启动的两个nginx.exe后台进程
tasklist | findstr "nginx*"
# 确认nginx默认的80端口是否正在被worker进程监听
netstat -ano | findstr "80"
# 强制结束某个后台进程
taskkill /pid 进程号 /f
# 关闭nginx服务,结束所有相关进程,并删除PID文件(若PID文件不存在则报错)
nginx -s stop
- 访问 nginx 服务器 http://localhost:80(80 端口可省略)。
4. Nginx单机容器搭建
武技:在 Docker 中搭建单机 Nginx 容器
- 创建相关目录:
# 创建Nginx相关目录
mkdir -p /opt/nginx/conf;
mkdir -p /opt/nginx/logs;
mkdir -p /opt/nginx/static;
chmod -R 777 /opt/nginx;
- 开发配置文件:
# 拉取镜像(二选一)
docker pull nginx:1.25.2;
docker pull registry.cn-hangzhou.aliyuncs.com/joezhou/nginx:1.25.2;
# 创建并运行临时的容器: 该容器仅用于拷贝配置文件,用完即删
docker run -d --name tmp registry.cn-hangzhou.aliyuncs.com/joezhou/nginx:1.25.2;
# 拷贝主配文件,子配置目录和HTML目录到虚拟机
docker cp tmp:/etc/nginx/nginx.conf /opt/nginx/conf/;
docker cp tmp:/etc/nginx/conf.d /opt/nginx/;
docker cp tmp:/usr/share/nginx/html /opt/nginx/;
# 删除临时容器,为后续真正的容器让出端口
docker rm -f tmp
- 创建运行容器:
# 创建并运行Nginx容器: 额外暴露几个端口,用于测试Nginx的多server模型
docker run --name nginx -d --network my-net \
-p 80:80 -p 81:81 -p 82:82 -p 83:83 -p 84:84 -p 85:85 -p 86:86 -p 87:87 -p 88:88 -p 89:89 \
-v /opt/nginx/static:/opt \
-v /opt/nginx/logs:/var/log/nginx \
-v /opt/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
-v /opt/nginx/conf.d:/etc/nginx/conf.d \
-v /opt/nginx/html:/usr/share/nginx/html \
registry.cn-hangzhou.aliyuncs.com/joezhou/nginx:1.25.2;
# 查看Nginx容器
docker ps --format "{{.ID}}\t{{.Names}}\t{{.Ports}}"
docker logs nginx --tail 30
# 进入容器的内部并查看Nginx版本
docker exec -it nginx bash
nginx -version
- 访问单机容器:在 Windows 机器上访问 Nginx 服务器 http://192.168.40.77:80 (虚拟机默认开放了 80 端口)。
E02. Nginx配置文件
1. nginx.conf
心法:Nginx 主配置文件
/opt/nginx/conf/nginx.conf
包括全局配置,events 块和 http 块三部分的配置内容。
内容解析如下:
# 启动Worker进程的用户和组
user nginx;
# 生成的Worker进程数: 设置为auto表示自动设置为机器的CPU核数
worker_processes auto;
# 错误日志文件: notice级别
error_log /var/log/nginx/error.log notice;
# PID文件,用于记录当前Master进程的ID值
pid /var/run/nginx.pid;
events {
# 每个Worker进程能并发处理的最大连接数,包括前后端所有连接
worker_connections 1024;
}
http {
# 导入扩展名与文件类型的映射表types文件
include /etc/nginx/mime.types;
# Nginx自动下载不识别的文件类型
default_type application/octet-stream;
# 日志格式: `客户端IP 用户 访问时间 请求信息/状态 内容大小 请求源头 浏览器`,命名为main
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 访问日志文件,采用main日志格式
access_log /var/log/nginx/access.log main;
# 开启高效文件传输模式,关闭使用off,普通应用建议开启,下载相关的应用建议关闭
sendfile on;
# 开启sendfile的情况下,会将请求合并,然后统⼀发送给客⼾端
tcp_nopush on;
# HTTP连接超时时间,上传文件时设置大一些,避免超时断开连接而上传失败
keepalive_timeout 65;
# 对网络传输的数据内容进行压缩以提升传输效率
gzip on;
# 引入该目录下的所有Nginx子配置文件
include /etc/nginx/conf.d/*.conf;
}
2. default.conf
心法:Nginx 子配置文件
/opt/nginx/conf.d/default.conf
主要包括的就是 server 块,可以存在多个,保证端口号和服务名不相同即可。
内容解析如下:
server {
# 配置服务器监听的端口号
listen 80;
listen [::]:80;
# 配置服务名
server_name localhost;
# 爆发50开头的错误时,执行名为 `/50x.html` 的 `location{}` 块
error_page 500 502 503 504 /50x.html;
# 当请求 `/50x.html` 时,响应 `/usr/share/nginx/html/50x.html` 页面
location = /50x.html {
# 请求资源在服务器上的真实页面所在的目录
root /usr/share/nginx/html;
}
# 当请求 `/` 时,响应 `/usr/share/nginx/html/index.html` 页面
location / {
# 请求资源在服务器上的真实页面所在的目录
root /usr/share/nginx/html;
# 请求资源在服务器上的真实页面
index index.html index.htm;
}
}
3. location匹配
location 匹配原则:
- location 所有匹配过程都会忽略请求末尾查询串。
- location 匹配顺序是首先精准匹配,然后正则匹配,最后前缀匹配。
- Windows 下的 Nginx 服务器对大小写不敏感,均可匹配成功。
location 精准匹配:一旦匹配成功则立刻停止,如 location = /a {}
:
http://IP:PORT/a
:精准匹配,成功。http://IP:PORT/A
:Linux 下匹配失败,Windows 下忽略大小写匹配成功。http://IP:PORT/a/
:对末尾的/
敏感,失败。http://IP:PORT/b
:不匹配,失败。
location 正则匹配:按照编写顺序依次匹配,一旦匹配成功则立刻停止,如 location ~ ^/a$ {}
:
-
http://IP:PORT/a
:正则匹配,成功。 -
http://IP:PORT/A
:Linux 下匹配失败,Windows下忽略大小写匹配成功。 -
http://IP:PORT/a/
:对末尾的/
敏感,失败。 -
http://IP:PORT/b
:正则不匹配,失败。 -
若将
~
符号替换为!~
表示取反操作。 -
若将
~
符号替换为~*
表示忽略大小写正则匹配。 -
若将
~
符号替换为!~*
表示忽略大小写正则匹配的取反效果。
location 前缀匹配:按照编写顺序依次匹配,最终只有匹配度最高的那一个生效,如 location /a {}
:
http://IP:PORT/a
:前缀匹配,成功。http://IP:PORT/ab
:前缀匹配,成功。http://IP:PORT/a/
:前缀匹配,成功。http://IP:PORT/A
:Linux 下匹配失败,Windows 下忽略大小写匹配成功。http://IP:PORT/b
:前缀不匹配,失败。- 若使用
^~
符号和前缀匹配效果一致,唯一不同的是匹配成功后立即停止继续匹配。
4. Nginx路径映射
武技:使用 Nginx 代理本地资源(一张本地图片)
- 拷贝一张图片到虚拟机的
/opt/nginx/static/image
中,如avatar.png
。 - 开发子配置文件:
cp /opt/nginx/conf.d/default.conf /opt/nginx/conf.d/82.conf
文件内容如下:
server {
listen 82;
listen [::]:82;
server_name static-image;
location /image {
# 需要指定Nginx容器内的静态资源地址
alias /opt/image/;
}
}
- 重启 Nginx 服务使得配置生效:
docker restart nginx
- 在 Windows机器上访问静态资源 http://192.168.40.77:82/image/avatar.png
S02. Nginx项目部署
E01. 反向代理后端项目
武技:创建 nginx-a 和 nginx-b 两个子项目
1. 开发nginx-a项目
- 添加三方依赖:
<dependencies>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
- 开发主配文件:
server:
port: 13801 # 端口号
spring:
application:
name: nginx-a # 项目名
- 开发启动类:
package com.joezhou;
/** @author 周航宇 */
@SpringBootApplication
public class NginxA {
public static void main(String[] args) {
SpringApplication.run(NginxA.class, args);
}
}
- 开发控制器:
package com.joezhou.controller;
/** @author 周航宇 */
@RestController
public class NginxController {
@GetMapping("/hello")
public Map<String, Object> hello(HttpSession httpSession) {
Map<String, Object> resultMap = new HashMap<>(3);
resultMap.put("applicationName", "nginx-a");
resultMap.put("port", 13801);
resultMap.put("session-id", httpSession.getId());
return resultMap;
}
}
- 访问子项目 http://localhost:13801/hello:关注sessionId是否共享。
2. 开发nginx-b项目
- 添加三方依赖:
<dependencies>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
- 开发主配文件:
server:
port: 13802 # 端口号
spring:
application:
name: nginx-b # 项目名
- 开发启动类:
package com.joezhou;
/** @author 周航宇 */
@SpringBootApplication
public class NginxB {
public static void main(String[] args) {
SpringApplication.run(NginxB.class, args);
}
}
- 开发控制器:
package com.joezhou.controller;
/** @author 周航宇 */
@RestController
public class NginxController {
@GetMapping("/hello")
public Map<String, Object> hello(HttpSession httpSession) {
Map<String, Object> resultMap = new HashMap<>(3);
resultMap.put("applicationName", "nginx-b");
resultMap.put("port", 13802);
resultMap.put("session-id", httpSession.getId());
return resultMap;
}
}
- 访问子项目 http://localhost:13802/hello:关注sessionId是否共享。
3. 共享Session会话
心法:默认情况下,多个 Nginx 节点之间的 Session 是不一致的,会导致负载均衡效果下出现各种各样的意外,在使用 Nginx 负载均衡时,建议使用 Redis 来共享 Session 配置。
使用 Redis 来共享 Session 配置要求:
- 要求项目中引入
spring-boot-starter-data-redis
依赖。 - 要求项目中引入
spring-session-data-redis
依赖。 - 要求项目主配中正确配置了 Redis 服务地址和端口号等项。
武技:共享两个子项目的 Session 会话
- 分别在两个子项目中引入依赖:
<dependencies>
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring-session-data-redis-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
- 分别在两个子项目中添加 Redis 相关主配项:
spring:
data:
redis:
host: 192.168.40.77 # Redis服务器IP
port: 6379 # Redis服务器端口
- 访问子项目:
- http://localhost:13801/hello:关注sessionId是否共享。
- http://localhost:13802/hello:关注sessionId是否共享。
4. Docker部署测试项目
武技:在 Docker 中部署两个子项目
- 创建相关目录:
# 创建相关目录
mkdir -p /opt/app/a;
mkdir -p /opt/app/b;
- 打包 SpringBoot 项目:
- 将项目 nginx-a 打包为 a.jar 文件并拷贝到
/opt/app/a
目录中。 - 将项目 nginx-b 打包为 b.jar 文件并拷贝到
/opt/app/b
目录中。
- 将项目 nginx-a 打包为 a.jar 文件并拷贝到
- 创建 a.jar 的 DockerFile 文件,内容如下:
# 开发 a.jar 项目节点的Dockerfile文件
cd /opt/app/a;
touch Dockerfile;
echo 'FROM registry.cn-hangzhou.aliyuncs.com/joezhou/jdk:17' >> Dockerfile;
echo 'MAINTAINER JoeZhou' >> Dockerfile;
echo 'ADD a.jar a.jar' >> Dockerfile;
echo 'EXPOSE 13801' >> Dockerfile;
echo 'CMD ["nohup", "java", "-jar", "a.jar", "&"]' >> Dockerfile;
# 根据Dockerfile文件制作镜像: 必须在Dockerfile文件同级目录下
docker build -t a:1.0 .
# 查看镜像
docker images
- 创建 b.jar 的 DockerFile 文件,内容如下:
# 开发 b.jar 项目节点的Dockerfile文件
cd /opt/app/b;
touch Dockerfile;
echo 'FROM registry.cn-hangzhou.aliyuncs.com/joezhou/jdk:17' >> Dockerfile;
echo 'MAINTAINER JoeZhou' >> Dockerfile;
echo 'ADD b.jar b.jar' >> Dockerfile;
echo 'EXPOSE 13802' >> Dockerfile;
echo 'CMD ["nohup", "java", "-jar", "b.jar", "&"]' >> Dockerfile;
# 根据Dockerfile文件制作镜像: 必须在Dockerfile文件同级目录下
docker build -t b:1.0 .
# 查看镜像
docker images
- 创建运行容器:
# 创建并运行 a 节点容器
docker run -itd --name a --network my-net -p 13801:13801 a:1.0;
# 创建并运行 b 节点容器
docker run -itd --name b --network my-net -p 13802:13802 b:1.0;
# 查看节点容器
docker ps --format "{{.ID}}\t{{.Names}}\t{{.Ports}}"
# 永久开放13801和13802端口
firewall-cmd --add-port=13801/tcp --permanent
firewall-cmd --add-port=13802/tcp --permanent
firewall-cmd --reload
- 访问项目容器:
- http://192.168.40.77:6625/hello
- http://192.168.40.77:6626/hello
5. Nginx代理测试项目
心法:Nginx 代理服务器可以使用基础轮询,最少连接,地址哈希,加权轮询等负载均衡策略分发请求,分摊服务器集群的压力。
负载均衡策略:
负载均衡策略 | 描述(如何将请求转发到目标服务器节点) | 配置(假设负载均衡器的名称为 x ) |
---|---|---|
基础轮询 | 按顺序轮流转发:每个服务器节点均摊请求 | 默认策略,无需配置 |
最少连接 | 按空闲度转发:当前连接数越少,接收到的请求就越多 | upstream x {least_conn; 服务列表} |
地址哈希 | 按客户端IP地址转发:定向转发到服务器节点 | upstream x {ip_hash; 服务列表} |
加权轮询 | 按权重转发:配置的权重越高,接收到的请求就越多 | upstream x {服务 weight=1} |
负载均衡器 http{} -> upstream apps {}
:
- 负载均衡器的名称随意,如
apps
,但不能重复。 - 负载均衡器中使用
server IP:PORT;
设置节点,可设置多个。 - 负载均衡器中的每个Server节点都可以额外添加以下设置:
负载均衡器配置:
weight=1
:设置该节点的权重,权重越大,访问的机率越大,压力也就越大,数字从 1 开始。down
:设置该服务不参与负载均衡。max_fails=2
:设置该节点允许请求失败的最大次数,超出次数后报错。fail_timeout=30s
:设置该节点请求失败后暂停访问的超时时间。backup
:设置节点为备用节点,即当其它节点全忙或宕机时,该节点才会被启用。
武技:在 Nginx 服务器中反向代理两个子项目节点
- 在 Nginx 主配置文件的
http{}
块中添加负载均衡器:
# 负载均衡器默认使用轮询策略
upstream apps {
server 192.168.40.77:6625;
server 192.168.40.77:6626;
}
- 开发子配置文件:
cp /opt/nginx/conf.d/default.conf /opt/nginx/conf.d/83.conf
内容如下:
server {
listen 83;
listen [::]:83;
server_name app-server;
location / {
# 拦截全部请求并代理转发到主配中的负载均衡器 `apps{}` 中,并进行负载均衡调用
proxy_pass http://apps;
}
}
- 重启 Nginx 服务使得配置生效:
docker restart nginx
- 在Windows机器上多次访问项目节点 http://192.168.40.77:83/hello,查看轮询效果。
E02. 反向代理前端项目
武技:使用 Nginx 代理前端 Vue 项目
v3-8-ssm-nginx/nginx-web
- 将项目中的
localhost
全部替换为虚拟机IP地址192.168.40.77
。 - 删除整个项目中的
console.log()
打印语句,尤其是请求拦截器中的打印语句(生产环境下不要使用打印语句,打印的内容无法回收,容易造成内存泄露,页面越来越卡)。 - 使用
npm run build
命令打包后端前台项目,打包后会生成一个dist
目录。
1. 上传dist目录
- 将打包生成的
dist
目录,整体拷贝到/opt/nginx/html/
目录中。
# 进入Nginx的html目录
cd /opt/nginx/html
# 重命名dist目录为
mv dist nginx-web
2. 使用Nginx代理
- 开发子配置文件:
cp /opt/nginx/conf.d/default.conf /opt/nginx/conf.d/84.conf
内容如下:
server {
listen 84;
listen [::]:84;
server_name nginx-web;
# Nginx首页所在目录
location / {
root /usr/share/nginx/html/nginx-web;
index index.html;
}
}
- 重启 Nginx 服务使得配置生效:
docker restart nginx
- 在 Windows 机器上访问前端项目 http://192.168.40.77:84
Java道经第3卷 - 第8阶 - Nginx