项目优化性能监控
目录
1. 性能平台搭建
1.1 影响性能的关键要素
1.2 压力测试
1.3 压力测试指标
1.4 Jmeter
1.5 Jmeter常用插件
1.6 性能关键指标
1.7 服务器硬件资源监控
1.8 系统负载:load average
1.9 搭建压测监控平台
1.10 梯度压测:分析接口性能瓶颈
2. 项目优化
2.1 Tomcat调优
2.2 容器优化Tomcat升级Undertow
2.3 数据库调优
2.4 OpenResty调优
2.5 缓存调优
2.6 JVM调优
3. 项目重构
3.1 重构原因
3.2 重构的要求
3.3 重构步骤
1. 性能平台搭建
1.1 影响性能的关键要素
- 产品设计
- 基础网络
- 代码质量跟架构
- 移动端环境
- 硬件及云服务
1.2 压力测试
1.3 压力测试指标
1.4 Jmeter
1.5 Jmeter常用插件
开启插件下载:
- 下载地址:http://jmeter-plugins.org/downloads/all/,官网上下载plugins-manager.jar直接在线下载, 然后执行在线下载即可。
1、PerfMon:监控服务器硬件,如CPU,内存,硬盘读写速度等
Allows collecting target server resource metrics
2、Basic Graphs:主要显示平均响应时间,活动线程数,成功/失败交易数等
Average Response Time 平均响应时间
Active Threads 活动线程数
Successful/Failed Transactions 成功/失败 事务数
3、Additional Graphs:主要显示吞吐量,连接时间,每秒的点击数等
Response Codes
Bytes Throughput
Connect Times
Latency
1.6 性能关键指标
-
1.响应时间
-
2.压力机活动线程数
压力机活动线程数表明压测过程中施加的压力的情况
-
3.TPS每秒事务数 数字愈大,代表性能越好
-
4.QPS每秒查询数
数字愈大,代表性能越好(1tps>=1QPS) -
5.吞吐量 吞吐量: 每秒的请求数量
1.7 服务器硬件资源监控
压测的时候,我们需要实时了解服务器【CPU、内存、网络、服务器Load】的状态如何,哪如何监控服 务器的资源占用情况呢?方法有很多种:
- 使用操作系统命令:top、vmstat、iostat、iotop、dstat、sar...
- 使用finalshell
- 使用JMeter压测工具perfmon
- 使用Grafana+Prometheus+node_exporter
1.8 系统负载:load average
Load Average含义
系统负载System Load是系统CPU繁忙程度的度量,即有多少进程在等待被CPU调度(进程等待队 列的长度)。
平均负载(Load Average)是一段时间内系统的平均负载,这个一段时间一般取1分钟、5分钟、 15分钟。
多核CPU和单核CPU的系统负载数据指标的理解还不一样。
top 指令可以查看系统负载
单核CPU三种Load情况
举例说明:把CPU比喻成一条(单核)马路,进程任务比喻成马路上跑着的汽车,Load则表示马 路的繁忙程度。 情况1-Load小于1:不堵车,汽车在马路上跑得游刃有余
情况2-Load等于1:马路已无额外的资源跑更多的汽车了
情况3-Load大于1:汽车都堵着等待进入马路
1.9 搭建压测监控平台
node_exporter:类似一一种代理工具,将机器压力信息数据push到Prometheus
Prometheus:是一个开源的系统监控和报警系统
InfluxDB:是一个开源的、高性能的时序型数据库
安装Docker+JMeter+InfluxDB+Grafana+node_exporter+Prometheus
- 配置Docker环境
yum 包更新到最新
sudo yum update -y
安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖 的
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
设置yum源为阿里云
配置yum源的代理,类似于maven镜像仓库,加速下载软件。
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装docker
sudo yum install docker-ce -y
# 启动
systemctl start docker
安装后查看docker版本
ocker -v
- 安装InfluxDB
下载InfluxDB的镜像:
docker pull influxdb:1.8
启动InfluxDB的容器,并将端口 8083 和 8086 映射出来:
docker run -d --name influxdb -p 8086:8086 -p 8083:8083 influxdb:1.8
进入容器内部,创建名为jmeter的数据库:
docker exec -it influxdb /bin/bash
输入 influx 命令,即可进入 influx 操作界面
输入 create database jmeter 命令,创建名为 jmeter 的数据库
输入 show databases 命令,查看数据库创建成功
root@517f57017d99:/# influx
Connected to http://localhost:8086 version 1.7.10
InfluxDB shell version: 1.7.10
> create database jmeter
> show databases
使用JMeter 库, select 查看数据,这个时候是没有数据的:
输入 use jmeter 命令,应用刚才创建的数据库
输入 select * from jmeter 命令,查询库中有哪些数据
> use jmeter
> select * from jmeter
- 设置JMeter脚本后置监听器
配置后置监听器
想要将 JMeter的测试数据导入 InfluxDB ,就需要在 JMeter中使用 Backend Listener 配置
主要配置说明
运行验证
运行 Jmeter 脚本,然后再次在 influxdb 中查看数据,发现类似下面的数据说明输入导入成功:
- 安装Grafana
下载Grafana镜像:
docker pull grafana/grafana
启动Grafana容器:
docker run -d --name grafana -p 3000:3000 grafana/grafana
验证部署成功
网页端访问http://101.200.146.199:3000验证部署成功 默认账号密码admin/admin
选择添加数据源
找到并选择 influxdb
配置数据源
数据源创建成功时会有绿色的提示
导入模板
模板导入分别有以下3种方式: 直接输入模板id号 直接上传模板json文件 直接输入模板json内容
找展示模板
在Grafana的官网找到我们需要的展示模板
Apache JMeter Dashboard dashboad-ID:5496
JMeter Dashboard(3.2 and up) dashboad-ID:3351
导入找到的模板,使用模板id
导入模板,我这里选择输入模板id号,导入后如下,配置好模板名称和对应的数据源,然后 import 即可
查看效果
展示设置,首先选择创建的application
注意: 如果我们修改过表名,也就是在jmeter的Backend Listener的measurement配置(默认为 jmeter),这个时候就需要去设置中进行修改,我这里使用的就是默认的,所以无需修改
以上这些我们就可以查看压测的响应时间,活动线程数等
- 安装node_exporter
# 下载
wget -c
https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_ex
porter-0.18.1.linux-amd64.tar.gz
# 解压
mkdir /usr/local/hero/
tar zxvf node_exporter-0.18.1.linux-amd64.tar.gz -C /usr/local/hero/
# 启动
cd /usr/local/hero/node_exporter-0.18.1.linux-amd64
nohup ./node_exporter > node.log 2>&1 &
注意:在被监控服务器中配置开启端口9100 http://101.200.87.86:9100/metrics
- 安装Prometheus
下载解压运行
# 下载
wget -c
https://github.com/prometheus/prometheus/releases/download/v2.15.1/prometheus
-2.15.1.linux-amd64.tar.gz
# 解压
tar zxvf prometheus-2.15.1.linux-amd64.tar.gz -C /usr/local/hero/
cd /usr/local/hero/prometheus-2.15.1.linux-amd64
# 运行
nohup ./prometheus > prometheus.log 2>&1 &
配置prometheus
在prometheus.yml中加入如下配置
- job_name: 'hero-Linux'
static_configs:
- targets:
['172.17.187.78:9100','172.17.187.79:9100','172.17.187.81:9100']
测试Prometheus
测试Prometheus是否安装配置成功 http://101.200.146.199:9090/targets
- 在Grafana中配置Prometheus的数据源
Grafana导入Linux展示模板
导入Linux系统dashboard
Node Exporter for Prometheus Dashboard EN 20201010
dashboard-ID: 11074
Node Exporter Dashboard
dashboard-ID: 16098
1.10 梯度压测:分析接口性能瓶颈
以上主要的四种性能指标【响应时间、并发用户数、吞吐量、资源使用率】它们之间存在一定的相关 性,共同反映出性能的不同方面。
情况01-模拟低延时场景,用户访问接口并发逐渐增加的过程。
预计接口的响应时间为20ms
线程梯度:5、10、15、20、25、30、35、40个线程
循环请求次数5000次
- 网络瓶颈
随着压力的上升,TPS不再增加,接口响应时间逐渐在增加,偶尔出现异常,瓶颈凸显。系统的 负载不高。CPU、内存正常,说明系统这部分资源利用率不高。带宽带宽显然已经触顶了。
优化方案:
方案01-降低接口响应数据包大小
方案02-提升带宽【或者在内网压测】
结论:
在低延时场景下,服务瓶颈主要在服务器带宽。
TPS数量等于服务端线程数 乘以 (1000ms/ RT均值)
RT=21ms,TPS=800,服务端线程数:= 800/ (1000ms/ RT均值) = 17
情况02-模拟高延时场景,用户访问接口并发逐渐增加的过程。接口的响应时间为500ms
线程梯度:100、200、300、400、500、600、700、800个线程;
循环请求次数200次
结论:
在高延时场景下,服务瓶颈主要在容器最大并发线程数。
RT=500ms,TPS=800,服务端线程数:= 800/ (1000ms/ RT) = 400
Tomcat默认的最大的线程数?200
观察服务容器最大线程数,发现处理能力瓶颈卡在容器端
TPS在上升到一定的值之后,异常率较高 (原因容器使用的IO是是阻塞式IO模型)
2. 项目优化
2.1 Tomcat调优
Springboot开发的服务使用嵌入式的Tomcat服务器,那么Tomcat配置使用的是默认配置,我们需要对 Tomcat配置进行适当的优化,让Tomcat性能大幅提升。
我们可以改变yml文件改变内置tomcat配置
# Tomcat的maxConnections、maxThreads、acceptCount三大配置,分别表示最大连接数,最大
线程数、最大的等待数,可以通过application.yml配置文件来改变这个三个值,一个标准的示例如
下:
server.tomcat.uri-encoding: UTF-8
server.tomcat.accept-count: 1000 # 等待队列最多允许1000个请求在队列中等待
server.tomcat.max-connections: 20000 # 最大允许20000个链接被建立
## 最大工作线程数,默认200, 4核8g内存,线程数经验值800
server.tomcat.threads.max: 800 # 并发处理创建的最大的线程数量
server.tomcat.threads.min-spare: 100 # 最大空闲连接数,防止突发流量
# 暴露所有的监控点
management.endpoints.web.exposure.include: '*'
# 定义Actuator访问路径
management.endpoints.web.base-path: /actuator
# 开启endpoint 关闭服务功能
management.endpoint.shutdown.enabled: true
- maxThreads:最大线程数
每一次HTTP请求到达Web服务,Tomcat都会创建一个线程来处理该请求。
最大线程数决定了Web服务容器可以同时处理多少个请求,默认值是200。
个人经验:
1C2G,线程数200
4C8G,线程数800
-
accept-count:最大等待连接数
当调用HTTP请求数达到Tomcat的最大线程数时,还有新的请求进来,这时Tomcat会将该剩余请 求放到等待队列中
默认值是100,如果等待队列超了,新的请求会被拒绝(connection refused) -
Max Connections:最大连接数
最大连接数是指在同一时间内,Tomcat能够接受的最大连接数。如果设置为-1,则表示不限制
默认值: 对BIO模式,默认值是Max Threads;如果使用定制的Executor执行器,哪默认值将是执行器 中Max Threads的值。
对NIO模式,Max Connections 默认值是10000
- IO模型
众所周知文件读写性能是影响应用程序性能的关键因素之一。NIO与原来的IO有同样的作用和目的,但 是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO以更加高效的方式进行文件 的读写操作。
Java的NIO【new io】是从Java 1.4版本开始引入的一套新的IO API用于替代标准的Java IO API。 JDK1.7之后,Java对NIO再次进行了极大的改进,增强了对文件处理和文件系统特性的支持。我们称之 为AIO,也可以叫NIO2。
优化:使用NIO2的Http协议实现,对请求连接器进行改写。
@Configuration
public class TomcatConfig {
//自定义SpringBoot嵌入式Tomcat
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new
TomcatServletWebServerFactory() {};
tomcat.addAdditionalTomcatConnectors(http11Nio2Connector());
return tomcat;
}
//配置连接器nio2
public Connector http11Nio2Connector() {
Connector connector=new
Connector("org.apache.coyote.http11.Http11Nio2Protocol");
Http11Nio2Protocol nio2Protocol = (Http11Nio2Protocol)
connector.getProtocolHandler();
//等待队列最多允许1000个线程在队列中等待
nio2Protocol.setAcceptCount(1000);
// 设置最大线程数
nio2Protocol.setMaxThreads(1000);
// 设置最大连接数
nio2Protocol.setMaxConnections(20000);
//定制化keepalivetimeout,设置30秒内没有请求则服务端自动断开keepalive链接
nio2Protocol.setKeepAliveTimeout(30000);
//当客户端发送超过10000个请求则自动断开keepalive链接
nio2Protocol.setMaxKeepAliveRequests(10000);
// 请求方式
connector.setScheme("http");
connector.setPort(9003); //自定义的端口,与源端口9001
可以共用,知识改了连接器而已
connector.setRedirectPort(8443);
return connector;
}
}
结论:可以发现服务响应时间大幅缩短,并且稳定
小结:将NIO升级为AIO之后,RT的毛刺大幅降低,异常数(超时3s)几乎为0。
2.2 容器优化Tomcat升级Undertow
Undertow是一个用Java编写的灵活的高性能Web服务器,提供基于NIO的阻塞和非阻塞API。
- 支持Http协议
- 支持Http2协议
- 支持Web Socket
- 最高支持到Servlet4.0
- 支持嵌入式
SpringBoot的web环境中默认使用Tomcat作为内置服务器,其实SpringBoot提供了另外2种内置的服务 器供我们选择,我们可以很方便的进行切换。
- Undertow红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器 。轻量级Servlet服务器, 比Tomcat更轻量级没有可视化操作界面,没有其他的类似jsp的功能,只专注于服务器部署,因此 undertow服务器性能略好于Tomcat服务器;
- Jetty开源的Servlet容器,它是Java的web容器。为JSP和servlet提供运行环境。Jetty也是使用Java 语言编写的。
配置操作过程:
- 在spring-boot-starter-web排除tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
- 导入其他容器的starter
<!--导入undertow容器依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
- 配置
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接
server.undertow.threads.io: 800
# 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线
程
# 默认值是IO线程数*8
server.undertow.threads.worker: 8000
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内
存管理
# 每块buffer的空间大小越小,空间就被利用的越充分,不要设置太大,以免影响其他应用,合
适即可
server.undertow.buffer-size: 1024
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
# 是否分配的直接内存(NIO直接分配的堆外内存)
server.undertow.direct-buffers: true
小结:
更换了服务容器之后,RT更加平稳,TPS的增长趋势更平稳,异常数(超时3s)几乎为0。
在低延时情况下,接口通吐量不及Tomcat。
稳定压倒一切,如果只是写json接口,且对接口响应稳定性要求高,可以选用Undertow
2.3 数据库调优
影响数据库性能:
- 服务器: OS、CPU、Memory、Network
- MySQL :
数据库表结构【对性能影响最大】
低下效率的SQL语句
超大的表
大事务
数据库配置
数据库整体架构
数据库调优途径:
- 优化SQL语句调:根据需求创建结构良好的SQL语句【实现同一个需求,SQL语句写法很多】
- 数据库表结构调优:索引,主键,外键,多表关系等等
- MySQL配置调优:最大连接数,连接超时,线程缓存,查询缓存,排序缓存,连接查询缓存等等
- 服务器硬件优化:多核CPU、更大内存
2.4 OpenResty调优
OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方 模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服 务和动态网关。OpenResty的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻 塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
小结:使用了OpenResty反向代理了服务,TPS会在原有的基础上再翻倍!
2.5 缓存调优
缓存预热
2.6 JVM调优
3. 项目重构
3.1 重构原因
- 代码排版风格不一致,函数、变量命名随意,错别字,拼音命令都有出现。
- 存在不少废弃代码(代码被注释,或者代码没有被使用)
- 包结构混乱,导致开发经常重复实现已有的功能
- 过滤器滥用并且没有设置优先级
- 日志打印千奇百怪,不注意控制日志级别
- 参数校验满天飞,一个函数中关于桉树校验的逻辑可能有许多行
- 错误码泛滥,散落在各个文件,工具类也泛滥
- 函数方法不封装,封装不合理
- 接口文档和代码不一致,接口文档无法及时刷新
3.2 重构的要求
重构可达可小,小到一个变量名和函数名的改动这类微重构,大到重写整个接口业务逻辑的系统性重构
对于微重构,可以做在平时,穿插在问题修改,需求开发过程中
需要制定完整的详细计划
熟悉代码结构,业务场景
确定重构范围
确认参与人员
3.3 重构步骤
- 第一阶段:利用工具批量修复问题
- 第二阶段:删除冗余代码
- 第三阶段:深入到代码进行重构
- 第四阶段:测试验证
- 第五阶段:代码检视