02-项目性能优化-课程笔记

项目性能优化

授课目标:

目标01-了解分布式压力测试

目标02-掌握Web服务容器优化:Tomcat调优,网络IO调优,及服务容器更换

目标03-理解为什么进行数据库调优,以及数据库调优的调什么

目标04-掌握OpenResty调优

目标05-理解缓存调优和JVM调优必要性和价值点

2.6 分布式压测

使用JMeter做大并发压力测试的场景下,单机受限与内存、CPU、网络IO,会出现服务器压力还没有上

去,但是压测机压力太大已经死机!为了让JMeter拥有更强大的负载能力,JMeter提供分布式压测能

力。

单机网络带宽有限

高延时场景下,单机可模拟最大线程数有限

如下是分布式压测架构:
02-项目性能优化-课程笔记_第1张图片

**注意:**在JMeter Master节点配置线程数10,循环100次【共1000次请求样本】。如果有3台Salve 节

点。那么Master启动压测后,每台Salve都会对被测服务发起10x100次请求。因此,压测产生的总样本

数量是:10 x 100 x 3 = 3000次。

搭建JMeter Master控制机和JMeter Salve施压机

第一步:三台JMeter Salve搭建在Linux【Centos7】环境下

第二步:JMeter Master搭建在Windows Server环境下【当然也可以搭建在Linux里面,这里用win

是为了方便观看】

搭建注意事项:

需保证Salve和Server都在一个网络中。如果在多网卡环境内,则需要保证启动的网卡都在一个网

段。

需保证Server和Salve之间的时间是同步的。

需在内网配置JMeter主从通信端口【1个固定,1个随机】,简单的配置方式就是关闭防火墙,但存

在安全隐患。

2.6.1 Windows Server部署JMeter Master

与Window中安装JMeter一样简单,略

2.6.2 Linux部署JMeter Salve

(1)下载安装

1 wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.4.1.tgz

2 tar -zxvf apache-jmeter-5.4.1.tgz

3 mv apache-jmeter-5.4.1 ./apache-jmeter-5.4.1-salve

(2)配置修改rmi主机hostname

1 # 1.改ip

2 vim jmeter-server

3 # RMI_HOST_DEF=-Djava.rmi.server.hostname=本机ip

4

5 # 2.改端口

6 vim jmeter.properties

7 # RMI port to be used by the server (must start rmiregistry with same port)

8 server_port=1099

9 # To change the default port (1099) used to access the server:

10 server.rmi.port=1098

(3)配置关闭server.rmi.ssl

1 # Set this if you don’t want to use SSL for RMI

2 server.rmi.ssl.disable=true

(4)启动jmeter-server服务

1 nohup ./jmeter-server > ./jmeter.out 2>&1 &

2.6.3 分布式环境配置

(1)确保JMeter Master和Salve安装正确。

(2)Salve启动,并监听1099端口。

(3)在JMeter Master机器安装目录bin下,找到jmeter.properties文件,修改远程主机选项,添加3个

Salve服务器的地址。

1 remote_hosts=172.17.187.78:1099,172.17.187.79:1099,172.17.187.81:1099

(4)启动jmeter,如果是多网卡模式需要指定IP地址启动

1 jmeter -Djava.rmi.server.hostname=172.17.187.82

(5)验证分布式环境是否搭建成功:02-项目性能优化-课程笔记_第2张图片
在这里插入图片描述

3. 服务容器优化

3.1 Tomcat调优

为什么对SpringBoot嵌入式的Tomcat进行调优?

基于应用性能瓶颈分析得出结论,当响应时间比较长时,系统瓶颈主要存在与Tomcat中!

系统异常率较高,与Tomcat内部IO模型有关系【多线程&网络编程】

问题:可不可以基于RT与TPS算出服务端并发线程数?

服务端线程数计算公式:TPS/ (1000ms/ RT均值)

3.1.1 调优:嵌入式Tomcat配置

Springboot开发的服务使用嵌入式的Tomcat服务器,那么Tomcat配置使用的是默认配置,我们需要对

Tomcat配置进行适当的优化,让Tomcat性能大幅提升。


修改配置如下所示:可以使用外挂配置,也可以修改配置文件application.yml。

注意,做了任何修改一定要确认配置生效,否则干的再久也是白搭!

1 # Tomcat的maxConnections、maxThreads、acceptCount三大配置,分别表示最大连接数,最大

线程数、最大的等待数,可以通过application.yml配置文件来改变这个三个值,一个标准的示例如

下:

2 server.tomcat.uri-encoding: UTF-8

3 # 思考问题:一台服务器配置多少线程合适?

4 server.tomcat.accept-count: 1000 # 等待队列最多允许1000个请求在队列中等待

5 server.tomcat.max-connections: 20000 # 最大允许20000个链接被建立

6 ## 最大工作线程数,默认200, 4核8g内存,线程数经验值800

7 server.tomcat.threads.max: 800 # 并发处理创建的最大的线程数量

8 server.tomcat.threads.min-spare: 100 # 最大空闲连接数,防止突发流量

9

10

11 # 暴露所有的监控点

12 management.endpoints.web.exposure.include: ‘*’

13 # 定义Actuator访问路径

14 management.endpoints.web.base-path: /actuator

15 # 开启endpoint 关闭服务功能

16 management.endpoint.shutdown.enabled: true

1)maxThreads:最大线程数

每一次HTTP请求到达Web服务,Tomcat都会创建一个线程来处理该请求。

最大线程数决定了Web服务容器可以同时处理多少个请求,默认值是200。

最大线程数并不是越大越好!

最大线程数只是TPS的影响因素之一,还不是最关键的哪个。

增加线程是有成本的,不能无限制增大。线程过多不仅仅会带来线程上下文切换的成本,而且线程

也需要消耗内存资源。

JVM默认Xss堆栈大小1M

**什么是线程上下文切换呢?**我们在并发编程章节详细介绍。

那么最大线程数的值应该设置多少合适呢?

这个需要基于业务系统的监控结果来定。RT均值很低,可以不用设置,RT均值很高,可以考虑加

线程数

当然,如果接口响应时间低于100毫秒,足以产生足够的TPS。系统瓶颈不在于此,则不建议设置

最大线程数。

我个人的最大线程数设置的经验值为:

1C2G,线程数200

4C8G,线程数800

2)accept-count:最大等待连接数

当调用HTTP请求数达到Tomcat的最大线程数时,还有新的请求进来,这时Tomcat会将该剩余请

求放到等待队列中

acceptCount就是指队列能够接受的最大的等待连接数

默认值是100,如果等待队列超了,新的请求会被拒绝(connection refused)

3)Max Connections:最大连接数

最大连接数是指在同一时间内,Tomcat能够接受的最大连接数。如果设置为-1,则表示不限制

默认值:

对BIO模式,默认值是Max Threads;如果使用定制的Executor执行器,哪默认值将是执行器

中Max Threads的值。

对NIO模式,Max Connections 默认值是10000

Max Connections和accept-count关系

当连接数达到最大值Max Connections后系统会继续接收连接,但不会超过acceptCount限制

注意:在做优化的时候,一定要确定你的修改是生效【JVM、Tomcat、OpenResty…】

3.1.2 调优配置生效确认

3.1.3 调优前后的性能对比

调优前:压力机Active,RT、TPS、系统进程运行状态【应用活动线程数】

调优后:压力机Active,RT、TPS、系统进程运行状态【应用活动线程数】

结论:提升Tomcat最大线程数,在高负载场景下,TPS提升接近1倍,同时RT大幅降低;

3.2 网络IO模型调优

3.2.1 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协议实现,对请求连接器进行改写。

结论:可以发现服务响应时间大幅缩短,并且稳定

1 @Configuration

2 public class TomcatConfig {

3

4

5

6

//自定义SpringBoot嵌入式Tomcat

@Bean

public TomcatServletWebServerFactory servletContainer() {

TomcatServletWebServerFactory tomcat = new

TomcatServletWebServerFactory() {};

7

8

tomcat.addAdditionalTomcatConnectors(http11Nio2Connector());

9

return tomcat;

}

10

11

12

13

14

//配置连接器nio2

public Connector http11Nio2Connector() {

Connector connector=new

Connector(“org.apache.coyote.http11.Http11Nio2Protocol”);

Http11Nio2Protocol nio2Protocol = (Http11Nio2Protocol)

connector.getProtocolHandler();

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//等待队列最多允许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

29

connector.setRedirectPort(8443);

return connector;

30

31

}

32 }

3.2.2 调优前后的性能对比

调优前:RT、TPS

调优后:RT、TPS

小结:将NIO升级为AIO之后,RT的毛刺大幅降低,异常数(超时3s)几乎为0。稳如老狗~

3.3 容器优化Tomcat升级Undertow

https://undertow.io/

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

语言编写的。

3.3.1 配置操作过程:

1. 在spring-boot-starter-web排除tomcat

1

2

3

4

5

6

7

8

9

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

10

2. 导入其他容器的starter

1

2

3

4

org.springframework.boot

spring-boot-starter-undertow

5

3. 配置

1 # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接

2 server.undertow.threads.io: 800

3 # 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线

4 # 默认值是IO线程数*8

5 server.undertow.threads.worker: 8000

6 # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内

存管理

7 # 每块buffer的空间大小越小,空间就被利用的越充分,不要设置太大,以免影响其他应用,合

适即可

8 server.undertow.buffer-size: 1024

9 # 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region

10 # 是否分配的直接内存(NIO直接分配的堆外内存)

11 server.undertow.direct-buffers: true

4. 压测

3.3.2 调优前后的性能对比

调优前:RT、TPS【这个是没有进行NIO2优化的Tomcat】

调优后:RT、TPS

小结:

更换了服务容器之后,RT更加平稳,TPS的增长趋势更平稳,异常数(超时3s)几乎为0。

在低延时情况下,接口通吐量不及Tomcat。

稳定压倒一切,如果只是写json接口,且对接口响应稳定性要求高,可以选用Undertow

还有很多可以优化的空间就不一一举例,这里我们更多的是教大家分析应用性能问题的方法,最直观的

感受应用性能到底卡在什么地方。虽然算不上很全面,但是作为高级开发工程师,到这个程度就够了。

其他可以优化的地方:

ARP-IO模型

Jetty 容器

KeepAliveTimeout【默认】

MaxKeepAliveRequests

4. 数据库调优

**请问这两条SQL语句有什么区别呢?**你来猜一猜,那条SQL语句执行查询效果会更好!

1 # SQL01 - 效率更好

2 select id from tb_sku where name='华为 HUAWEI 麦芒7 6G+64G 魅海蓝 全网通 前置智慧

双摄 移动联通电信4G手机 双卡双待’;

3 # SQL02 - 效率更好

4 select id from tb_sku where spu_id=10000018913700;

4.1 为什么要进行MySQL数据库调优?

响应时间打下去,最重要的在数据库!

提升网站整体通吐量,优化用户体验数据库是关键之一

流畅页面的访问速度,良好的网站功能体验

避免网站页面出现访问错误

由于数据库连接timeout产生页面5xx错误

由于慢查询造成页面无法加载

由于阻塞造成数据无法提交

增加数据库的稳定性:很多数据库问题都是由于低效的查询引起的

4.2 什么影响数据库性能?

服务器: OS、CPU、Memory、Network

MySQL :

数据库表结构【对性能影响最大】

低下效率的SQL语句

超大的表

大事务

数据库配置

数据库整体架构

4.3 数据库调优到底调什么?

优化SQL语句调:根据需求创建结构良好的SQL语句【实现同一个需求,SQL语句写法很多】

数据库表结构调优:索引,主键,外键,多表关系等等

MySQL配置调优:最大连接数,连接超时,线程缓存,查询缓存,排序缓存,连接查询缓存等等

服务器硬件优化:多核CPU、更大内存

为什么使用索引就能加快查询速度呢?

场景一:面试官问你,有没有做过数据库优化呀,我来问你一个特别简单的问题:查询的时候有没有给

查询字段加过索引?为什么要加,加上之后怎么就会变快呢?请您简单描述一下原因

索引就是事先排好顺序,从而在查询的时候可以使用二分法等高效率的算法查找

除了索引查找,就是一般顺序查找,这两者的差异是数量级的差异。

二分法查找的时间复杂度是O(log n),而一般顺序查找的时间复杂度是O(n)

索引的数据结构是B+树,是一种比二分法查找时间复杂度更好的数据结构。

说算法的时间复杂度有部分小伙伴可能感知不到差距。那么我举个栗子告诉大家差异多大:

假如表中有100w条数据,如果需要根据id查找:

一般顺序查找平均需要找50万条数据,对比50万次才能找到

如果采用索引二分法查找,最多不超过20次对比即可找到。

二者效率相差2.5万倍,当然电脑不差这点算力,但是你总不能用大炮去轰蚊子吧

数据库优化是一个很大的话题,里面涉及数据库架构,事务,索引,锁…等原理。这个部分要想弄明

白,得铺垫很多底层理论。具体的内容咱们在数据库专题中详细和大家交代。

绝大多数小伙伴并不能正确的使用索引!

全值匹配我最爱,最左前缀要遵守;

带头大哥不能死,中间兄弟不能断;

索引列上不计算,范围之后全失效;

Like百分写最右,覆盖索引不写星;

不等空值还有OR,索引失效要少用。

5. OpenResty调优

OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方

模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服

务和动态网关。OpenResty的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻

塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached

以及 Redis 等都进行一致的高性能响应。

什么是Nginx?

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-

like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中

表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

安装OpenResty到Linux服务器

5.1 配置域名映射

hero01

访问主机名映射配置:

访问地址:http://hero01

雄哥按:这里不要加入.com结尾,未备案的带com后缀域名会被阿里云拦截

5.2 压测配置

1 # 指定你要代理的tomcat服务器列表

2 upstream hero-server{

3

4

# 内网ip:端口号

server 172.17.187.78:9003;

5

server 172.17.187.79:9003;

6 }

7 server {

8

9

listen

80;

server_name hero01;

location / {

10

11

# 反向代理小英雄服务集群

proxy_pass http://hero-server;

}

12

13

14 }

5.3 测试结果

JMeter:聚合报告、TPS、RT

Nginx-CPU、内存、网络

资料:压力测试\07-低延时20ms-Nginx

数据库服务-CPU、内存、网络

资料:压力测试\07-低延时20ms-Nginx

小英雄服务-CPU、内存、网络

资料:压力测试\07-低延时20ms-Nginx

小结:使用了OpenResty反向代理了服务,TPS会在原有的基础上再翻倍!

6. 缓存调优

6.1 需求分析

需要在页面上显示广告的信息。

表结构分析:

tb_ad (广告表)

字段名称

id

字段含义

ID

字段类型

INT

字段长度

备注

name

广告名称

广告位置

开始时间

到期时间

状态

VARCHAR

VARCHAR

DATETIME

DATETIME

CHAR

position

start_time

end_time

status

系统定义

0:无效 1:有效

image

url

图片地址

URL

VARCHAR

VARCHAR

VARCHAR

remarks

备注

position:系统定义的广告位置

1. web_index_lb

首页轮播图

2. web_index_mobile_lb

3. web_index_book_lb

手机分类轮播图

图书分类轮播图

6.2 直接查询数据库

http://hero01/ad_read?position=web_index_lb

数据库查询:

之前接口都是查询数据库,这里本接口压力测试省略

6.3 多级缓存

缓存预热与二级缓存 查询思路分析

步骤一:编写Lua脚本实现缓存预热(将mysql里的数据查询出来存入redis)

步骤二:编写Lua脚本实现二级缓存读取

6.3.1 缓存预热

实现思路:定义请求:用于查询数据库中的数据更新到redis中。

(1)连接mysql ,按照广告分类ID读取广告列表,转换为json字符串。

(2)连接redis,将广告列表json字符串存入redis 。

定义请求:

1

2

3

4

5

6

请求:

/ad_load

参数:

position --指定广告位置

返回值:

json

在/root/lua目录下创建ad_load.lua ,实现连接mysql 查询数据 并存储到redis中。

http://hero01/ad_load?position=xxx

1 – 设置请求

2 ngx.header.content_type=“application/json;charset=utf8”

3 – 引入json第三方依赖

4 local cjson = require(“cjson”)

5 local mysql = require(“resty.mysql”)

6 local uri_args = ngx.req.get_uri_args()

7 local position = uri_args[“position”]

8

9 local db = mysql:new()

10 db:set_timeout(1000)

11 local props = {

12

host = “172.17.187.78”,

port = 3307,

13

14

database = “hero_all”,

user = “root”,

15

16

password = “hero@202207”

17 }

18

19 local res = db:connect(props)

20 local select_sql = "select url,image from tb_ad where status =‘1’ and

position=‘“…position…”’ and start_time<= NOW() AND end_time>= NOW()"

21 res = db:query(select_sql)

22 db:close()

23

24 local redis = require(“resty.redis”)

25 local red = redis:new()

26 red:set_timeout(2000)

27

28 local ip =“172.17.187.78”

29 local port = 6379

30 red:connect(ip,port)

31

32 red:set(“ad_”…position,cjson.encode(res))

33 red:close()

34

35 ngx.say(“{flag:true}”)

修改/usr/local/hero/openresty/nginx/conf/nginx.conf文件:

代码如下:

1 #user nobody;

2 user root root;

3

4 # 添加

5 location /ad_load {

6

content_by_lua_file /root/lua/ad_load.lua;

7 }

重新加载nginx配置

1 # 启动nginx

2 /usr/local/openresty/nginx/sbin/nginx -c

/usr/local/openresty/nginx/conf/nginx.conf

3 ps -ef | grep nginx

4

5 # 停止nginx

6 /usr/local/openresty/nginx/sbin/nginx -s stop #停止服务

7

8 # 重新加载配置,修改配置后执行

9 /usr/local/openresty/nginx/sbin/nginx -s reload

测试:http://hero01/ad_load?position=web_index_lb

6.3.2 广告缓存读取

实现思路:

定义请求,用户根据广告分类的ID 获取广告的列表。通过lua脚本直接从redis中获取数据即可。

定义请求:

1

2

3

请求:/ad_read

参数:position

返回值:json

在/root/lua目录下创建ad_read.lua

1 --设置响应头类型

2 ngx.header.content_type=“application/json;charset=utf8”

3 --获取请求中的参数ID

4 local uri_args = ngx.req.get_uri_args();

5 local position = uri_args[“position”];

6 --引入redis库

7 local redis = require(“resty.redis”);

8 --创建redis对象

9 local red = redis:new()

10 --设置超时时间

11 red:set_timeout(2000)

12 --连接

13 local ok, err = red:connect(“172.17.187.78”, 6379)

14 --获取key的值

15 local rescontent=red:get(“ad_”…position)

16 --输出到返回响应中

17 ngx.say(rescontent)

18 --关闭连接

19 red:close()

在/usr/local/openresty/nginx/conf/nginx.conf中server下添加配置

1 location /ad_read {

2

content_by_lua_file /root/lua/ad_read.lua;

3 }

测试 http://hero01/ad_read?position=web_index_lb 输出

1 [{“url”:“img/banner1.jpg”,“image”:“img/banner1.jpg”},

{“url”:“img/banner2.jpg”,“image”:“img/banner2.jpg”}]

6.3.3 测试结果

JMeter:聚合报告、TPS、RT

Nginx-CPU、内存、网络

资料:压力测试\08-Nginx-Redis

数据库服务-CPU、内存、网络

资料:压力测试\08-Nginx-Redis

小英雄服务-CPU、内存、网络

资料:压力测试\08-Nginx-Redis

小结:TPS大幅提升,RT显著降低

6.4 进程内缓存

如上的方式没有问题,但是如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式

来减少下游系统的服务压力。

6.4.1 配置进程内缓存

先查询openresty本地缓存 如果没有再查询redis中的数据

修改/root/lua目录下ad_read_nginx_cache文件, 内容如下:

1 --设置响应头类型

2 ngx.header.content_type=“application/json;charset=utf8”

3 --获取请求中的参数ID

4 local uri_args = ngx.req.get_uri_args();

5 local position = uri_args[“position”];

6

7 --获取本地缓存

8 local cache_ngx = ngx.shared.dis_cache;

9 --根据ID 获取本地缓存数据

10 local adCache = cache_ngx:get(‘ad_cache_’…position);

11

12 if adCache == “” or adCache == nil then

13

14

–引入redis库

15

local redis = require(“resty.redis”);

16

–创建redis对象

17

local red = redis:new()

18

–设置超时时间

19

red:set_timeout(2000)

20

–连接

21

local ok, err = red:connect(“172.17.187.78”, 6379)

–获取key的值

22

23

local rescontent=red:get(“ad_”…position)

–输出到返回响应中

24

25

ngx.say(rescontent)

26

–关闭连接

27

red:close()

28

–将redis中获取到的数据存入nginx本地缓存,缓存有效期10分钟

cache_ngx:set(‘ad_cache_’…position, rescontent, 10*60);

29

30 else

31

–nginx本地缓存中获取到数据直接输出

32

ngx.say(adCache)

33 end

修改nginx配置文件vi /usr/local/hero/openresty/nginx/conf/nginx.conf ,http节点下添加配置:

1 #包含redis初始化模块

2 lua_shared_dict dis_cache 5m; #共享内存开启

3

4 location /ad_read_nginx_cache {

5

content_by_lua_file /root/lua/ad_read_nginx_cache.lua;

6 }

http://hero01/ad_read_nginx_cache?position=web_index_lb

6.4.2 测试报告

JMeter:聚合报告、TPS、RT

Nginx-CPU、内存、网络

小结:

TPS继续提升,RT响应时间触底12-20ms之间

网络带宽占用大幅下降

7. JVM调优

那种配置性能更好?当下的两个配置,完全不能反应性能。内存大小合适才好,不是越大越好!给多了

反而影响性能!

1 # 配置01-用大内存,反而会适得其反

2 JAVA_OPT=“-Xms4096M -Xmx4096M -Xmn1024M”

3

4 # 配置02-用大内存,反而会适得其反

5 JAVA_OPT=“-Xms512M -Xmx512M -Xmn256M”

6 # 初始堆大小Xms

7 # 最大堆大小Xmx

8 # 年轻代大小Xmn

今年的升职加薪就靠它了!

7.1 为什么JVM调优?

**调优的最终目的都是为了应用程序使用最小的硬件消耗来承载更大的吞吐量。**JVM调优主要是针对垃圾

收集器的收集性能进行优化,减少GC的频率和Full GC的次数。从而令运行在虚拟机上的应用,能够使用

更少的内存,用更低的延迟,获得更大吞吐量,和减少应用的GC暂停时间!GC暂停会在高并发场景

下,P99百分位的响应时间会产生影响。

7.2 什么时候JVM调优?

遇到以下情况,就需要考虑进行JVM调优:

1. 系统吞吐量与响应性能不高或下降

2. Heap内存(老年代)持续上涨达到设置的最大内存值

3. Full GC 次数频繁

4. GC 停顿时间过长,运行P99百分位响应时间;

5. 应用出现OutOfMemory 等内存异常;

6. 应用中有使用本地缓存,且占用了大量的内存空间;

7.3 调优调什么?

JVM调优核心是什么?

内存分配 + 垃圾回收!

1. 合理使用堆内存

2. GC高效回收垃圾对象,释放内存空间

是否可以把内存空间设置足够大,那么就不需要回收垃圾呢?

问题背景:JVM回收垃圾时机,当JVM内存占满触发垃圾回收!

不可以原因如下:

1. 不回收垃圾,内存增长巨快,再大的空间都不够用;10w请求,3GB垃圾对象

2. 物理层面:64位操作系统可以支持非常大的内存,但不是无限

1. 32位操作系统: 2~32 = 4GB

2. 64位操作系统: 2~64 =16384PB

3. 内存设置既不能太大,也不能太小需要基于业务场景平衡考量:内存空间设置过大,一旦内存空间

触发垃圾回收,就会非常危险,寻找这个垃圾非常耗时,由于内存空间足够大,寻找这个垃圾的时

候,极其的消耗时间,因此导致程序停顿;

举个栗子:房子足够大,是不是就可以不用打扫卫生!显然是不行的

7.4 调优原则及目标:

优先原则:优先架构调优和代码调优,JVM优化是不得已的手段

大多数的Java应用不需要进行JVM优化

观测性原则:发现问题解决问题,没有问题不找问题

调优目标:

下面展示了一些JVM调优的量化目标的参考。注意:不同应用的JVM调优量化目标是不一样的。

堆内存使用率 <= 70%;

老年代内存使用率<= 70%;

avg pause <= 1秒;

Full GC 次数0 或 avg pause interval >= 24小时 ;

创建更多的线程

再写一个稍微复杂一点的调优配置:看的出来这两者配置之间的区别吗?

JVM配置01

1 JAVA_OPT=“${JAVA_OPT} -Xms836m -Xmx836m -XX:MetaspaceSize=128m -Xss512k”

2

3 JAVA_OPT="${JAVA_OPT} -XX:+UseParallelGC -XX:+UseParallelOldGC "

4

5 JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -

XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-ps-

po.log"

JVM配置02

1 JAVA_OPT=“${JAVA_OPT} -Xms1024m -Xmx1024m -XX:MetaspaceSize=256m -Xss512k”

2

3 JAVA_OPT="${JAVA_OPT} -XX:+UseParNewGC -XX:+UseConcMarkSweepGC "

4

5 JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -

XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-parnew-

cms.log"

雄哥按:JVM虚拟机讲完,我们再压测,再优化大家就可以看明白这里的门道了。

到目前为止,压测和调优做完了。我带你掌握了项目优化的方法和一部分技巧。项目性能问题涉及的知

识面很宽广,如果再往深入走,几乎就进入了另一个领域。哪又是另一个课程要说的事情了。我们的项

目性能优化就到这里了。

**项目性能测试报告怎么写?**我给大家分享一个我曾经用过的性能报告模板。

最后,虽然我们作为研发不需要去编写性能报告,或者说对开发来讲,只要可以用JMeter测出自己核心

重保的接口的TPS和RT就可以了。我们掌握性能优化和压测的技能,其实是为了更好的理解接下来学习

的内容。项目的性能是一把很好的尺子,可以用来丈量技术的"性价比"。

测试报告分享:

查看参考资料:项目性能测试报告.md

今日总结:

01-分布式压测:

在Master节点线程数10,循环100次。有3台Salve 节点。产生总样本量:10 x 100 x 3 = 3000次

三个注意:

需保证Salve和Server都在一个网络中。如果在多网卡环境内,则需要保证启动的网卡都在一

个网段。

需保证Server和Salve之间的时间是同步的。

需在内网配置JMeter主从通信端口【1个固定,1个随机】,简单的配置方式就是关闭防火

墙,但存在安全隐患。

02-服务容器优化

Tomcat优化:最大线程数提升1000

结论:提升Tomcat最大线程数,在高负载场景下,TPS提升接近1倍,同时RT大幅降低;

IO网络模型优化:NIO改为NIO2

结论:将NIO升级为AIO之后,RT的毛刺大幅降低,异常数(超时3s)几乎为0。稳如老狗~

Web容器优化:切换Web容器

结论:更换了服务容器之后,RT更加平稳,TPS的增长趋势更平稳,异常数(超时3s)几乎

为0。在低延时情况下,接口通吐量不及Tomcat。

03-其他优化

3.1 数据库优化灵魂三问:

理解为主,后面还会深入讲解

为什么要调优数据库?提升网站整体通吐量,优化用户体验数据库是关键之一

什么影响数据库性能:主要是不当使用(效率低下的SQL、不当数据库设计、大事务等等)

调优调啥?优化SQL、优化表结构、优化数据库配置、优化数据库架构、优化数据库硬件等等

3.2 OpenResty优化:

反向代理,三个臭皮匠赛过诸葛亮

结论:使用了Nginx反向代理了服务,TPS会在原有的基础上再翻倍!

3.3 多级缓存优化:

Redis缓存优化:结论:TPS大幅提升,RT显著降低

OpenResty缓存字典优化:TPS继续提升,RT响应时间触底12-20ms之间,网络带宽占用大幅下降

举例用户越近的地方,缓存的价值就越大

3.4 JVM调优灵魂三问

为什么JVM调优?降本增效

什么时候JVM调优?升职谈加薪的时候

调优调啥?内存分配 + 垃圾回收!

重点:

梯度压测案例,测出接口性能瓶颈

Web服务容器优化:Tomcat调优,网络IO调优,及服务容器更换

OpenResty调优

难点:

数据库调优

JVM调优

易错点:

搭建分布式压测案例的时候注意关闭 server.rmi.ssl

你可能感兴趣的:(性能优化,笔记)