本篇博客我会演示日常的工作中,我们是怎么利用nginx部署项目的。我们以部署一套前后分离的项目为本次讲述的内容
创建一个最简单的springboot项目:
提供一个api接口
,可以获取服务端的主机地址
和服务端口
:
@RestController
public class NginxController implements ApplicationListener<WebServerInitializedEvent> {
private int port;
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
this.port = event.getWebServer().getPort();
}
@GetMapping("host")
public String getHost(HttpServletRequest request){
InetAddress address;
try {
address = InetAddress.getLocalHost();
return address.getHostAddress() + ":" + port;
} catch (UnknownHostException e) {
e.printStackTrace();
}
return "error:Network card information is not available!";
}
}
测试接口:
搭建前端工程,使用vue官方推荐的vite搭建一个基础工程:
启动项目,打开项目:
安装axios:
npm install axios
使用axios访问后端的api接口,修改app.vue
如下:
<script>
import axios from 'axios'
export default {
name: 'App',
data() {
return {
host: ""
}
},
mounted() {
var ip_addr = document.location.hostname
axios.get('http://' + ip_addr + ':8080/host').then(res => {
this.host = res.data
})
}
}
script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
<br>服务器的ip和端口是:{{ host }}
header>
template>
<style scoped>style>
浏览器访问,发生了跨域问题:
我们都知道,nginx的安装目录中有这样一个文件夹:
我们再结合nginx的基础配置文件中的以下内容:
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
我们不妨猜想一下当url为/时(当然这是错的),会去html目录寻找
index.html
文件作为首页。我们可以得出结论只需要将我们的前端文件放在html目录即可(事实上放在哪里都可以)通过修改root html,例如要放在www文件夹下:root /data/www
即可。
我们尝试将前端构建的结果放入html文件夹:
访问浏览器:
我们在前端直接点击路由的按钮可以访问,因为这种情况并未再次向nginx发送请求,仅仅是前端的路由切换:
但是,如果直接访问/about就GG了,这个url直接访问nginx时,nginx会认为你要查找about这个资源,当然是404了:
所以我们要通过一些配置来解决这个问题,vue工程都是单页面的,所以无论哪个路由都应该使用唯一的index.html,所以我们可以做如下的配置,该配置的意思就是将其他的所有请求,都强制使用/index.html:
location / {
root /data/www/ui;
try_files $uri $uri/ $uri/index.html $uri.html /index.html;
}
在http模块中添加如下内容:
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_types image/png;
gzip_vary on;
解释如下:
gzip on
:使用"gzip on;"参数来启用压缩,默认是关闭的。
gzip_min_length 1k
:gzip压缩的最小文件,小于设置值的文件将不会压缩#指定Nginx服务需要向服务器申请的缓存空间的个数*大小,默认32 4k|16 8k;
gzip_buffers 4 16k
:设置压缩缓冲区大小,此处设置为4个16K内存作为压缩结果流缓存
gzip_http_version 1.1
:启用压缩功能时,协议的最小版本,默认HTTP/1.1
gzip_comp_level 5
:压缩比例由低到高从1到9,默认为1。但需要注意的是压缩比设置的越高就会越消耗CPU的资源,因此在生产环境中我们会设置该参数的值在3~5之间,最好不要超过5,因为随着压缩比的增大的确会降低传输的带宽成本但发送数据前会占用更多的CPU时间分片。
gzip_types image/png
:指明仅对哪些类型的资源执行压缩操作;默认为gzip_types text/html,不用显示指定,否则出错。
gzip_vary on
:该指令用于设置在使用Gzip功能时是否发送带有“Vary: Accept-Encoding”头域的响应头部。该头域的主要功能是告诉接收方发送的数据经过了压缩处理。开启后的效果是在响应头部添加了Accept-Encoding: gzip,这对于本身不支持Gzip压缩的客户端浏览器是有用的。
这是没有设置图片压缩:
设置图片压缩后,响应多了如下的首部信息:
1、配置nginx反向代理
我们可以通过proxy_pass
参数设置反向代理的服务器,语法如下:
location / {
proxy_pass http://127.0.0.1:8080;
}
我们的实现逻辑很简单,就是将以/api
为前缀的uri全部反向代理到真正的后端服务即可。
我们为了区分前端页面和api接口,将所有访问后端api的url统一加上前缀 /api
mounted(){
var ip_addr = document.location.hostname
axios.get('http://'+ip_addr+'/api/host').then(res=>{
this.host = res.data
})
}
现在我们想的是将前端发送的以api
打头的url全部代理到后端8080
端口:
前端访问的接口:http://localhost:80/api/host
后端的接口:http://localhost:8080/host
在实现代理的过程中,我们需要将/api
这个前缀删除掉,有以下两种方法,一种是重写url,如下:
location ^~ /api/ {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://127.0.0.1:8080;
}
更简单的做法是,在代理地址的后边加/
,这样做也会去掉前缀,但不如以上方式灵活:
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080/;
}
小知识:location中的rewirte
:
rewrite break
:url重写后,直接使用当前资源,不再执行location里余下的语句,完成本次请求,地址栏url不变
rewrite last
:url重写后,马上发起一个新的请求,再次进入server块,重试location匹配,超过10次匹配不到报500错误,地址栏url不变
rewrite redirect
:返回302临时重定向,地址栏显示重定向后的url。
有时候,我们的后端工程压力太大,可能需要将后端工程部署在多台服务器上,此时就需要使用负载均衡了,在学习负载均衡的时候我们不妨先了解一下upstream模块。
1、upstream模块解读
nginx 的负载均衡功能依赖于 ngx_http_upstream_module模块。upstream 模块应该放于http{}
标签内。
upstream liming {
ip_hash;
server backend1.example.com;
server backend2.example.com:8080;
server 127.0.0.1:8080;
server backup2.example.com:8080;
}
然后在location处使用如下写法:
location / {
proxy_pass http://liming;
}
以上写法的意思就是,将来同一个url访问我们的服务时,服务可以由liming中的服务器按照某种特定规则轮流提供
(1)round robin 轮询 (默认) 按时间顺序依次将请求分配到各个后台服务器中,挂掉的服务器自动从列表中剔除
upstream liming {
server 192.168.0.1 down;
server 192.168.0.2;
}
(2)weight 轮询权重 weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下,或在主从的情况下设置不同的权值,达到合理有效的地利用主机资源
upstream liming {
server 192.168.0.1 weight=20;
server 192.168.0.2 weight=10;
}
(3)ip_hash:每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题
upstream liming {
ip_hash;
server 192.168.0.1:88;
server 192.168.0.2:80;
}
(4)url_hash:按访问的URL的哈希结果来分配请求,使每个URL定向到同一台后端服务器,可以进一步提高后端服务器缓存的效率。Nginx本身不支持url_hash,需要安装Nginx的hash软件包
upstream liming {
server 192.168.0.1:88; //使用hash语句时,不能在使用weight等其他参数
server 192.168.0.2:80;
hash $request_uri;
hash_method crc32; //使用hash算法
}
(5)fair算法:可以根据页面大小和加载时间长短智能地进行负载均衡,根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身不支持fair,要安装upstream_fair模块才能使用
upstream liming {
server 192.168.0.1:88;
server 192.168.0.2:80;
fair;
}
首先我们需要将我们的后端项目在服务器中启动两份或多份,可以是同一台服务器,也可以是多台服务器,只要可以互联互通即可。
同一台服务器可以使用如下命令,重新指定一个端口即可:
java -jar nginx-demo.jar --server.port=8081
我们需要定义一个upstream,将都有的后端服务配置在其中:
upstream liming {
server 127.0.0.1:8080 weight 10;
server 127.0.0.1:8081 weight 20;
}
修改location的proxy_pass:
location ^~ /api/ {
proxy_pass http://liming/;
}
在浏览器中不停的刷新,发现端口在不停的变化,说明我们的多次请求确实落在了不同服务上
如果现在本机的前端项目(也就是其他服务器的前端项目)也想要访问虚拟机中ngixn代理的api接口。这是一个典型的不同的项目之间进行访问的问题,这必然存在跨域问题,如下图:
我们将本地的vue工程进行如下修改:
mounted(){
axios.get('http://192.168.111.200/api/host').then(res=>{
this.host = res.data
})
}
此时,确实发生了跨域问题:
解决方案:需要在客户端发送【预检请求】时指定对应的响应头即可,nginx可以很方便的给响应增加一些首部信息,方法如下:
location ^~ /api/ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow_Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
proxy_pass http://ydlclass/;
}
访问本地地址,本次访问跨域的问题被解决了:
我们也能看到对应的响应首部信息,多了如下内容: