非常哇塞的 Spring Boot 性能优化长文

作者:微信小助手

发布时间:2022-09-30T11:37:47

Spring Boot 已经成为 Java 届的 No.1 框架,每天都在蹂躏着数百万的程序员们。当服务的压力上升,对 Spring Boot 服务的优化就会被提上议程。


本文将详细讲解 Spring Boot 服务优化的一般思路,并附上若干篇辅助文章作为开胃菜。


本文较长,最适合收藏之。


1 有监控才有方向


在开始对 Spring Boot 服务进行性能优化之前,我们需要做一些准备,把 Spring Boot 服务的一些数据暴露出来。


比如,你的服务用到了缓存,就需要把缓存命中率这些数据进行收集;用到了数据库连接池,就需要把连接池的参数给暴露出来。


我们这里采用的监控工具是 Prometheus,它是一个是时序数据库,能够存储我们的指标。Spring Boot 可以非常方便的接入到 Prometheus 中。


创建一个 Spring Boot 项目后,首先加入 Maven 依赖。


<dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency>     <groupId>io.micrometer</groupId>     <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <dependency>     <groupId>io.micrometer</groupId>     <artifactId>micrometer-core</artifactId> </dependency>


然后,我们需要在 application.properties 配置文件中,开放相关的监控接口。


management.endpoint.metrics.enabled=truemanagement.endpoints.web.exposure.include=*management.endpoint.prometheus.enabled=truemanagement.metrics.export.prometheus.enabled=true


启动之后,我们就可以通过访问 http://localhost:8080/actuator/prometheus 来获取监控数据。



想要监控业务数据也是比较简单的。你只需要注入一个 MeterRegistry 实例即可。下面是一段示例代码:


@AutowiredMeterRegistry registry;@GetMapping("/test")@ResponseBodypublic String test() {    registry.counter("test""from""127.0.0.1""method""test").increment();    return "ok";}

从监控连接中,我们可以找到刚刚添加的监控信息。


test_total{from="127.0.0.1",method="test",} 5.0


这里简单介绍一下流行的 Prometheus 监控体系。Prometheus 使用拉的方式获取监控数据,这个暴露数据的过程可以交给功能更加齐全的 telegraf 组件。



如图,我们通常使用 Grafana 进行监控数据的展示,使用 AlertManager 组件进行提前预警。这一部分的搭建工作不是我们的重点,感兴趣的同学可自行研究。下图便是一张典型的监控图,可以看到 Redis 的缓存命中率等情况。



2 Java 生成火焰图

火焰图是用来分析程序运行瓶颈的工具。在纵向,表示的是调用栈的深度;横向表明的是消耗的时间。所以格子的宽度越大,越说明它可能是一个瓶颈。


火焰图也可以用来分析 Java 应用。可以从 GitHub 上下载 async-profiler 的压缩包 进行相关操作。


比如,我们把它解压到 /root 目录。然后以 javaagent 的方式来启动 Java 应用。命令行如下:


java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar


运行一段时间后,停止进程,可以看到在当前目录下,生成了 profile.svg 文件,这个文件是可以用浏览器打开的,一层层向下浏览,即可找到需要优化的目标。


3 Skywalking


对于一个web服务来说,最缓慢的地方就在于数据库操作。所以,使用本地缓存和分布式缓存优化,能够获得最大的性能提升。


对于如何定位到复杂分布式环境中的问题,我这里想要分享另外一个工具:Skywalking。


Skywalking 是使用探针技术(Java Agent)来实现的。通过在 Java 的启动参数中,加入 javaagent 的 Jar 包,即可将性能数据和调用链数据封装、发送到 Skywalking 的服务器。


下载相应的安装包(如果使用 Elasticsearch 存储,需要下载专用的安装包),配置好存储之后,即可一键启动。


将 agent 的压缩包,解压到相应的目录。


tar xvf skywalking-agent.tar.gz  -C /opt/

在业务启动参数中加入 agent 的包,比如原来的启动命令是:


java  -jar /opt/test-service/spring-boot-demo.jar  --spring.profiles.active=dev


改造后的启动命令是:


java -javaagent:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=the-demo-name  -jar /opt/test-service/spring-boot-demo.ja  --spring.profiles.active=dev

访问一些服务的链接,打开 Skywalking 的 UI,即可看到下图的界面。我们可以从图中找到响应比较慢 QPS 又比较高的的接口,进行专项优化。



4 优化思路


对一个普通的 Web 服务来说,我们来看一下,要访问到具体的数据,都要经历哪些主要的环节。


如下图,在浏览器中输入相应的域名,需要通过 DNS 解析到具体的 IP 地址上。为了保证高可用,我们的服务一般都会部署多份,然后使用 Nginx 做反向代理和负载均衡。


Nginx 根据资源的特性,会承担一部分动静分离的功能。其中,动态功能部分,会进入我们的 Spring Boot 服务。



Spring Boot 默认使用内嵌的 Tomcat 作为 Web 容器,使用典型的 MVC 模式,最终访问到我们的数据。


5 HTTP 优化


下面我们举例来看一下,哪些动作能够加快网页的获取。为了描述方便,我们仅讨论 HTTP1.1 协议的。


使用 CDN 加速文件获取


比较大的文件,尽量使用 CDN(Content Delivery Network)分发。甚至是一些常用的前端脚本、样式、图片等,都可以放到 CDN 上。CDN 通常能够加快这些文件的获取,网页加载也更加迅速。


合理设置 Cache-Control 值


浏览器会判断 HTTP 头 Cache-Control 的内容,用来决定是否使用浏览器缓存,这在管理一些静态文件的时候,非常有用。相同作用的头信息还有 Expires。Cache-Control 表示多久之后过期,Expires 则表示什么时候过期。


这个参数可以在 Nginx 的配置文件中进行设置。


location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {   # 缓存1年  add_header Cache-Control: no-cache, max-age=31536000;}


减少单页面请求域名的数量