nginx搭建域名访问环境
正向代理与反向代理
正向代理:客户端向代理服务器发请求并指定目标服务器,代理向目标服务器转交请求并将获得的内容返回给客户端。
反向代理:用户直接访问反向代理服务器就可以获得目标服务器的资源。反向代理服务器统一了访问入口。反向代理是对外界屏蔽了很多内部的信息
需求
访问“gulimall.com”,Nginx反向代理到网关地址“http://localhost:88/#”,展示商品页面
大致流程
配置反向代理-使得访问gulimall时访问nginx
windows域名解析会先找C:\Windows\System32\drivers\etc下的hosts文件,然后才找dns服务器
当我们在浏览器输入gulimall.com的时候,就会去找我们的虚拟机,此时虚拟机中的nginx是运行的话,就可以访问到nginx,因为nginx监听的是80端口
192.168.56.10 gulimall.com
方法一 直接使用nginx反向代理 使所有gulimall.com都转到首页
配置nginx.conf的主要内容
配置/etc/nginx/conf.d/gulimall.conf
监听gulimall.com域名 将其定位到首页
使用nginx将请求反向代理到网关
配置nginx.conf
在http中配置上由服务器 实现nginx负载均衡
upstream gulimall{ #配置上游服务器,起名为gulimall
server 192.168.56.1:88; #配置上游服务器为网关地址
}
include /etc/nginx/conf.d/*.conf; #该路径下的配置文件会全部合并到这里
修改/etc/nginx/conf.d/gulimall.conf
location / { #配置请求的路由
proxy_pass http://gulimall; #因为主配置文件配置了上游服务器为网关地址,所以可以请求路由到=
}
网关配置
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=**.gulimall.com
踩坑:
nginx正确处理转发api请求到网关 但是主页访问失败
原因:nginx转发请求给网关时会丢失掉请求的host信息
解决方法:配置请求头
proxy_set_header Host $host;
压力测试
压力测试考察当前软硬件环境下系统所能承受的最大负荷并帮助找出系统瓶颈所在。使用压力测试, 我们有希望找到很多种用其他测试方法更难发现的错误。 有两种错误类型是:内存泄漏, 并发与同步问题。
内存泄漏:内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
并发与同步:有效的压力测试系统将应用以下这些关键条件:重复, 并发, 量级, 随机变化
压力测试的性能指标
响应时间:响应时间指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间。响应时间越少越好。
HPS(Hits Per Second):每秒点击次数,单位是次/秒。
TPS(Transaction per Second):系统每秒处理交易数,单位是笔/秒。
Qps(Query per Second):系统每秒处理查询次数,单位是次/秒。
一般情况下用 TPS来衡量整个业务流程,用QPS来衡量接口查询次数,用HPS来表示对服务器单击请求。
无论TPS、QPS、HPS,此指标是衡量系统处理能力非常重要的指标,越大越好,根据经验,一般情况下:
金融行业:1000TPS~5000OTPS,不包括互联网化的活动
保险行业:100TPS~10000OTPS,不包括互联网化的活动
制造行业:10TPS~5000TPS
互联网电子商务:1000OTPS~1000000TPS
互联网中型网站:1000TPS~50000TPS
互联网小型网站:500TPS~10000TPS
最大响应时间(MaxResponse Time)指用户发出请求或者指令到系统做出反应(响应)的最大时间。
最少响应时间(Mininum ResponseTime)指用户发出请求或者指令到系统做出反应(响应)的最少时间。
90%响应时间(90%Response Time)是指所有用户的响应时间进行排序,第90%的响应时间。
JMeter 压测示例
添加线程组 :右键“test plan” 添加
线程组参数详解:
线程数: 虚拟用户数。 一个虚拟用户占用一个进程或线程。 设置多少虚拟用户数在这里也就是设置多少个线程数。
Ramp-Up时间: 200个线程需要多少秒内全部启动完成。 如果线程数为 10, 准备时长为 2, 那么需要 2 秒钟启动 10 个线程, 也就是每秒钟启动 5 个线程。
循环次数: 每个线程发送请求的次数。勾选“永远”会一直压力测试下去。 如果线程数为 10, 循环次数为 100, 那么每个线程发送 100 次请求。 总请求数为 10*100=1000 。 如果勾选了“永远”, 那么所有线程会一直发送请求, 一到选择停止运行脚本。
Delay Thread creation until needed: 直到需要时延迟线程的创建。
调度器: 设置线程组启动的开始时间和结束时间(配置调度器时, 需要勾选循环次数为永远)
持续时间(秒) : 测试持续时间, 会覆盖结束时间
启动延迟(秒) : 测试延迟启动时间, 会覆盖启动时间
启动时间: 测试启动时间, 启动延迟会覆盖它。 当启动时间已过, 手动只需测试时当前时间也会覆盖它。
结束时间: 测试结束时间, 持续时间会覆盖它。
在结果树和汇总报告中查看请求结果
添加 HTTP 请求
本机压测错误解决JMeter Address Already in use
出现原因
windows 本身提供的端口访问机制的问题。
Windows 提供给 TCP/IP 链接的端口为 1024-5000, 并且要四分钟来循环回收他们。 就导致我们在短时间内跑大量的请求时将端口占满了。
解决思路
扩大提供给 TCP/IP 链接的端口
缩短循环回收时间
通过修改windows配置文件解决
性能监控
概念讲解
影响性能考虑点包括:数据库、 应用程序、 中间件(tomact、 Nginx) 、 网络和操作系统等方面
首先考虑自己的应用属于 CPU 密集型(以空间换时间 大量计算数据)还是 IO 密集型(以时间换空间 大量读取数据)
JVM内存模型
程序计数器 Program Counter Register:
记录的是正在执行的虚拟机字节码指令的地址,
此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域
虚拟机栈: VM Stack
描述的是 JAVA 方法执行的内存模型, 每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表, 操作数栈, 动态链接, 方法接口等信息
局部变量表存储了编译期可知的各种基本数据类型、 对象引用
线程请求的栈深度不够会报 StackOverflowError 异常
栈动态扩展的容量不够会报 OutOfMemoryError 异常
虚拟机栈是线程隔离的, 即每个线程都有自己独立的虚拟机栈
本地方法: Native Stack
本地方法栈类似于虚拟机栈, 只不过本地方法栈使用的是本地方法
堆: Heap
几乎所有的对象实例都在堆上分配内存
Java源文件编译成.class字节码文件 ,.class字节码文件被JVM的类装载器装载到JVM里,所有数据都在“运行时数据区”。性能优化主要是优化“运行时数据区”中的“堆”。
Java垃圾清理主要在堆区,元数据区直接操控物理内存
堆
所有的对象实例以及数组都在堆上分配。 堆是垃圾收集器管理的主要区域, 也被称为“GC堆” ,也是我们优化最多考虑的地方。
新生代
Eden 空间
From Survivor 空间
To Survivor 空间
老年代
永久代/元空间
Java8 以前永久代, 受 jvm 管理, java8 以后元空间, 直接使用物理内存。 因此,默认情况下, 元空间的大小仅受本地内存限制。
创建对象放到堆内存的流程:
1.首先,任何新对象都分配到 eden 空间。两个幸存者空间开始时都是空的。
2.当 eden 空间填满时,将触发一个Minor GC(年轻代的垃圾回收),删除所有未引用的对象,大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年代。
3.所有被引用的对象作为存活对象,将移动到第一个幸存者空间S0,并标记年龄为1,即经历过一次Minor GC。之后每经过一次Minor GC,年龄+1。
4.当 eden 空间再次被填满时,会执行第二次Minor GC,将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1并年龄加1,此时S0变为空。
5.如此反复在S0和S1之间切换几次之后,还存活的年龄等于15的对象在下一次Minor GC时将放到老年代中。
6.当老年代满了时会触发FullGC(全GC),Full GC 清理整个堆 – 包括年轻代和老年代。
7.Major GC是老年代的垃圾回收,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上
Minor GC:清理年轻代空间(包括 Eden 和 Survivor 区域),释放在Eden中所有不活跃的对象,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区。
Survivor区:Survivor区被用来作为Eden及老年代的中间交换区域,当老年代空间足够时,Survivor区的对象会被移到老年代,否则会被保留在Survivor区。
Major GC:清理老年代空间,当老年代空间不够时,JVM会在老年代进行major gc。
Full GC:清理整个堆空间,包括年轻代和老年代空间。Full GC比Minor GC慢十倍,应该尽量避免。
详细流程图
使用 jvisualvm监控本地和远程应用
Jdk 的两个小工具 jconsole、 jvisualvm(升级版的 jconsole) ;通过命令行启动, 可监控本地和远程应用。 远程应用需要配置
这里使用jvisualvm
1)监控线程状态
线程状态:
运行: 正在运行的
休眠: sleep
等待: wait
驻留: 线程池里面的空闲线程
监视: 阻塞的线程, 正在等待锁
2)安装Visual GC插件监控GC状态
监控指标
GC
当前正在运行的线程数不能超过设定的最大值。 一般情况下系统性能较好的情况下, 线程数最小值设置 50 和最大值设置 200 比较合适。
当前运行的 JDBC 连接数不能超过设定的最大值。 一般情况下系统性能较好的情况下,JDBC 最小值设置 50 和最大值设置 200 比较合适。
GC频率不能频繁, 特别是 FULL GC 更不能频繁, 一般情况下系统性能较好的情况下,JVM 最小堆大小和最大堆大小分别设置 1024M 比较合适。
数据库
SQL 耗时越小越好, 一般情况下微秒级别。
命中率越高越好, 一般情况下不能低于 95%。
锁等待次数越低越好, 等待时间越短越好
压测与优化
压测
压测nginx
docker stats查看nginx的运行状况,比如CPU、内存占有率、IO情况等
JMeter 压力测试:
设置虚拟机地址以及nginx端口监控nginx
得出结论nginx比较浪费CPU
压测网关
JMeter添加请求后运行:
这里是服务器本机地址和端口88
查看jvisualvm 发现网关对CPU的占用率同样比较高
简单服务
@ResponseBody
@GetMapping("/hello")
public String hello() {
return "hello";
}
检测结果
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
Nginx | 50 | 2335 | 11 | 944 |
Gateway | 50 | 10367 | 8 | 31 |
简单服务 | 50 | 11341 | 8 | 17 |
Gateway+简单服务
两者结合之后吞吐量下降
检测结果
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
Gateway+简单服务 | 50 | 3124 | 30 | 125 |
中间件越多,性能损失越大,大多损失在网络交互 |
全链路(nginx+GateWay+简单服务)
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
Gateway+简单服务 | 50 | 3124 | 30 | 125 |
全链路 | 50 | 800 | 88 | 310 |
压测首页一级菜单
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页一级菜单渲染 | 50 | 270(慢的原因:db,thymeleaf) | 267 | 365 |
分析得出慢的原因可能是数据库和thymeleaf渲染
压测三级分类
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页一级菜单渲染 | 50 | 270(慢的原因:db,thymeleaf) | 267 | 365 |
三级分类数据获取 | 50 | 2(db) | … | … |
慢的原因是数据库
压测全量网页(包括静态资源)
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页一级菜单渲染 | 50 | 270(慢的原因:db,thymeleaf) | 267 | 365 |
三级分类数据获取 | 50 | 2(db) | … | … |
首页全量数据获取 | 50 | 7(静态资源) |
慢的原因是静态资源
优化方向
1.数据库
2.thymeleaf渲染
3.静态资源
首页一级菜单优化
1)开启thymeleaf缓存
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页一级菜单渲染 | 50 | 270(慢的原因:db,thymeleaf) | 267 | 365 |
首页渲染(开缓存) | 50 | 290 | 251 | 365 |
性能有一定提升
2)优化数据库
①一级分类根据parent_id查询 维奇加上索引
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页一级菜单渲染 | 50 | 270(慢的原因:db,thymeleaf) | 267 | 365 |
首页渲染(开缓存) | 50 | 290 | 251 | 365 |
首页渲染(开缓存、 优化数据库) | 50 | 700 | 105 | 183 |
全量数据获取优化
Nginx动静分离
为什么要动静分离
未分离的项目静态资源放在后端,无论是动态请求还是静态请求都会来到后台,这极大的损耗了后台Tomcat性能(大部分性能都用来处理静态请求)
动静分离后,后台只会处理动态请求,而静态资源直接由nginx返回。
具体步骤
1.静态资源存到Nginx里
1)在nginx的html目录下新建一个static目录用来存放静态资源
2)将gulimall-product的静态资源复制到该目录,并将本地静态资源删除
3)templates里文件静态资源路径前“static”
4)在nginx配置静态资源的路径映射
进入配置文件
vim /mydata/nginx/conf/conf.d/gulimall.conf
#监听gulimall.com:80/static
location /static {
root /usr/share/nginx/html; #资源在这个文件夹下匹配
}
优化结果
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
首页全量数据获取 | 50 | 7(静态资源) | ||
首页全量数据获取 | 50 | 11 |
发现老年代GC问题
解决方法
调大堆内存
-Xmx1024m -Xms1024m -Xmn512m
-Xms :初始堆大小
-Xmx :最大堆大小
-Xmn :堆中新生代初始及最大大小
三级分类数据获取优化
每次遍历一级分类列表里的元素,都要查一次数据库
解决方法1:将数据库的多次查询变为一次
将查询的分类结果放入list,以后每次在list中获取即可
private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList,Long parentCid) {
List<CategoryEntity> categoryEntities = selectList.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
return categoryEntities;
}
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
三级分类数据获取 | 50 | 2(db) | … | … |
三级分类数据获取 | 50 | 111 | 571 | 896 |