Original: Taste of Little Sister (wechat official ID: XjjDog), welcome to share, please reserve the source.
SpringBoot has become the No.1 framework in the Java world, ravaging millions of programmers every day. As service pressures rise, optimization of SpringBoot services is on the agenda.
This article will cover the general ideas for SpringBoot service optimization in detail, along with several supporting articles as appetizers.
This article is long, most suitable for collection.
1. Where there is monitoring, there is direction
Before we start to optimize the Performance of the SpringBoot service, we need to do some preparation to expose some data of the SpringBoot service.
For example, if your service uses caching, you need to collect data such as cache hit ratio. To use a database connection pool, you need to expose the connection pool parameters.
The monitoring tool we used here is Prometheus, which is a timing database that stores our metrics. SpringBoot can be easily plugged into Prometheus.
After creating a SpringBoot project, first, add maven dependencies.
<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>
Copy the code
We then need to open the relevant monitoring interface in the application.properties configuration file.
management.endpoint.metrics.enabled=true
management.endpoints.web.exposure.include=*
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
Copy the code
After launch, we can visit http://localhost:8080/actuator/prometheus to obtain monitoring data.
It is also relatively easy to monitor business data. All you need to do is inject a MeterRegistry instance. Here is a sample code:
@Autowired MeterRegistry registry; @responseBody public String test() {registry. Counter ("test", "from", "127.0.0.1", "method", "test" ).increment(); return "ok"; }Copy the code
From the monitoring connection, you can find the monitoring information you just added.
Test_total {the from = "127.0.0.1", method = "test",} 5.0Copy the code
This is a brief introduction to the popular Prometheus monitoring system, which uses a pull method to obtain monitoring data, a process that could be handed over to the more full-featured Telegraf component.
As shown in the figure, we usually use Grafana for displaying monitoring data and AlertManager component for early warning. This part of the construction work is not our focus, interested students can do their own research. The following is a typical monitor, showing things like Redis cache hit ratio.
2.Java generates flame charts
A flame chart is a tool used to analyze bottlenecks in a program. Vertically, it represents the depth of the call stack; Horizontal shows the elapsed time. So the wider the lattice, the more likely it is to be a bottleneck.
Flame charts can also be used to analyze Java applications. You can download the Async-Profiler package from Github to perform related operations.
For example, we unzip it to the /root/ directory. The Java application is then launched as a JavaAgent. The command line is as follows:
java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar Spring petclinic - 2.3.1. BUILD - the SNAPSHOT. The jarCopy the code
After running for some time, stop the process and you can see that the profile.svg file is generated in the current directory. This file can be opened with a browser.
3.Skywalking
The slowest part of a Web service is the database operation. Therefore, local cache and distributed cache optimization are used for maximum performance gains.
I’d like to share another tool for locating in a complex distributed environment: Skywalking.
Skywalking is implemented using probe technology (JavaAgent). Performance data and call chain data can be encapsulated and sent to Skywalking’s server by adding the JavaAgent Jar package to Java’s startup parameters.
Download the corresponding installation package (if ES storage is used, you need to download the dedicated installation package). After configuring the storage, you can start with one click.
Decompress the agent package to the corresponding directory.
tar xvf skywalking-agent.tar.gz -C /opt/
Copy the code
Add the agent package to the service startup parameters. For example, the original startup command was:
java -jar /opt/test-service/spring-boot-demo.jar --spring.profiles.active=dev
Copy the code
The modified startup command is:
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
Copy the code
Access links to some of the services and open Skywalking’s UI to see the interface shown below. We can find the interface with slow response and high QPS from the figure for special optimization.
15723404104715
4. Optimize your thinking
For an ordinary Web service, let’s take a look at the major steps that go through to access specific data.
In the following figure, enter the corresponding domain name in the browser. The domain name needs to be resolved to the specific IP address through the DNS. To ensure high availability, we typically deploy multiple copies of our services and use Nginx for reverse proxy and load balancing.
Depending on the nature of the resource, Nginx assumes part of the dynamic/static separation function. The dynamic part of it will go into our SpringBoot service.
SpringBoot defaults to using embedded Tomcat as the Web container, using the typical MVC pattern, and ultimately accessing our data.
5. HTTP optimization
Let’s take a look at some examples of actions that can speed up web page retrieval. For the sake of description, we will only discuss the HTTP1.1 protocol.
1. Use CDN to accelerate file acquisition
Use the Content Delivery Network (CDN) to distribute large files. Even some common front-end scripts, styles, images, etc., can be put on the CDN. CDN usually speeds up the acquisition of these files, and web pages load more quickly.
2. Set the cache-control value properly
The browser determines the contents of the HTTP header cache-Control to determine whether or not to use the browser Cache, which is useful when managing static files. A header that works similarly is Expires. Cache-control indicates how long it will expire, and Expires indicates when.
This parameter can be set in the Nginx configuration file.
The location ~ * ^. + \. (ico | | GIF JPG | jpeg | PNG) ${# 1 year add_header Cache cache-control: no - Cache, Max - age = 31536000; }Copy the code
3. Reduce the number of domain names requested on a single page
Reduce the number of domain names requested per page and try to keep it under 4. This is because every time the browser accesses the resources on the backend, it needs to query the DNS, find the IP address corresponding to the DNS, and then make the actual call.
DNS has multiple layers of caching. For example, the browser caches a copy, the local host caches, and the ISP caches. The transition from DNS to IP address typically takes 20-120ms. Reducing the number of domain names accelerates resource acquisition.
4. Open the gzip
With gzip enabled, you can compress the content before the browser decompresses it. As the transmission size is reduced, bandwidth usage is reduced and transmission efficiency is improved.
It’s easy to start in Nginx. The configuration is as follows:
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_types text/plain application/javascript text/css;
Copy the code
5. Compress resources
Compress JavaScript and CSS, and even HTML. Similarly, the popular front and back end separation mode is generally compressed for these resources.
6. Use keepalive
As connections are created and closed, resources are required. After a user visits our service, there will be more subsequent interactions, so maintaining a long connection can significantly reduce network interactions and improve performance.
Nginx enables keep Avlide support for clients by default. You can adjust its behavior with the following two parameters.
http {
keepalive_timeout 120s 120s;
keepalive_requests 10000;
}
Copy the code
To manually enable the connection between nginx and the upstream, see the following configuration:
location ~ /{
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
Copy the code
6. Tomcat optimization
The optimization of Tomcat itself is also very important. You can refer directly to the following article.
Fix the tomcat important parameters tuning!
7. Customize the Web container
If your project has high concurrency and you want to modify the maximum number of threads, maximum number of connections and other configuration information, you can customize the Web container, as shown in the code below.
@SpringBootApplication(proxyBeanMethods = false) public class App implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { public static void main(String[] args) { SpringApplication.run(PetClinicApplication.class, args); } @Override public void customize(ConfigurableServletWebServerFactory factory) { TomcatServletWebServerFactory f = (TomcatServletWebServerFactory) factory; f.setProtocol("org.apache.coyote.http11.Http11Nio2Protocol"); f.addConnectorCustomizers(c -> { Http11NioProtocol protocol = (Http11NioProtocol) c.getProtocolHandler(); protocol.setMaxConnections(200); protocol.setMaxThreads(200); protocol.setSelectorTimeout(3000); protocol.setSessionTimeout(3000); protocol.setConnectionTimeout(3000); }); }}Copy the code
Pay attention to the code above, we set up its agreement for org. Apache. Coyote. Http11. Http11Nio2Protocol, which means that opens the Nio2. This parameter is available after Tomcat8.0 and will increase performance. The comparison is as follows:
The default.
[root @ localhost wrk2 - master] #. / WRK - t2 - c100 d30s - R2000 http://172.16.1.57:8080/owners? The lastName = 30 s test Running @ http://172.16.1.57:8080/owners? Average lat.: 4588.131ms, Rate sampling interval: 16277ms Thread calibration: mean Lat.: 4647.927ms, Rate Sampling Interval: 16285ms Thread Stats Avg Stdev Max +/- Stdev Latency 16.49s 4.98s 27.34s 63.90% Req/Sec 106.50 1.50 108.00 100.00% 6471 Read Socket errors: connect 0, read 0, write 0, timeout 60 requests/SEC: 215.51 Transfer/SEC: 1.31 MBCopy the code
Nio2.
[root @ localhost wrk2 - master] #. / WRK - t2 - c100 d30s - R2000 http://172.16.1.57:8080/owners? The lastName = 30 s test Running @ http://172.16.1.57:8080/owners? Lat.: 4358.805 MS, Rate sampling interval: 15835MS Thread calibration: mean Lat.: 4622.087 MS, Rate Sampling Interval: 16293ms Thread Stats Avg Stdev Max +/- Stdev Latency 17.47s 4.98s 26.90s 57.69% Req/Sec 125.50 2.50 128.00 100.00% 7469 Read Socket errors: connect 0, read 0, write 0, timeout 4 requests/SEC: 248.64 Transfer/SEC: 1.51 MBCopy the code
You can even replace Tomcat with undertow. Undertow is also a Web container, which is lighter, takes up less content, and starts fewer daemons. The changes are as follows:
<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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>Copy the code
8. Optimization direction of each level
The Controller layer
The Controller layer receives the query parameters from the front end and constructs the query results. A lot of projects now have a separate back-end architecture, so the Controller layer approach, using the @responseBody annotation, parses the results of the query back to JSON data (both efficient and readable).
Since the controller only acts as a kind of combination of functions and routing, the performance impact of this part is mainly reflected in the size of the data set. If the result set is very large, the JSON parsing component takes a lot of time to parse.
Large result sets not only affect parsing time, but also waste memory. If the result set takes up 10MB of memory before being parsed into JSON, it is possible that 20M or more memory will be used to do the work during parsing. I have seen many cases where memory usage has skyrocketed because the nesting level of returned objects is too deep and refers to objects that shouldn’t be referred to (such as very large byte[] objects).
Therefore, for general services, it is very necessary to keep the result set simple, which is also necessary for the existence of DTO(Data Transfer Object). If your project returns a complex result structure, a transformation of the result set is necessary.
In addition, the Controller layer can be optimized using asynchronous servlets. The Servlet receives the request and forwards it to an asynchronous thread for processing. The thread itself returns to the container. After processing the request, the asynchronous thread can either generate the response data directly or forward the request to other servlets.
The Service layer
The Service layer is used to handle the specific business, where most of the functional requirements are fulfilled. The Service layer generally uses a singleton pattern (Prototype), rarely saves state and can be reused by controllers.
The code organization of the Service layer has a great impact on the readability and performance of the code. Most of the design patterns we talk about are for the Service layer.
One of the things I want to focus on here is distributed transactions.
As shown in the figure above, four operations are scattered across three different resources. To achieve consistency, three different resources need to be coordinated. The underlying protocols, and how they are implemented, are different. That can’t be done with Transaction annotations provided by Spring, but with external components.
Many of you have experienced this, adding some code to ensure consistency, a pressure test, and performance drops off the jaw. Distributed transactions are a performance killer because they require additional steps to ensure consistency. Common methods include two-phase commit schemes, TCC, local message tables, MQ transaction messages, distributed transaction middleware, and so on.
As shown in the figure above, distributed transactions should be considered in terms of transformation cost, performance, and effectiveness. There is a term between distributed transactions and non-transactions called flexible transactions. The idea behind flexible transactions is to move business logic and mutually exclusive operations from the resource layer up to the business layer.
Let’s make a quick comparison between traditional and flexible transactions.
ACID
Relational database, the biggest characteristic is transaction processing, namely meet ACID.
-
Atomicity: Either all or none of the operations in a transaction are done.
-
Consistency: The system must always be in a strongly consistent state.
-
Isolation: The execution of a transaction cannot be interrupted by other transactions.
-
Durability: Changes to data in the database by a committed transaction are permanent.
BASE
The BASE approach improves availability and system performance by sacrificing consistency and isolation.
BASE is Basically Available, soft-state, and Eventually consistent, where BASE stands for:
-
Basically Available: The system runs Basically and provides services all the time.
-
Soft-state: The system does not need to maintain strong and consistent state all the time.
-
Eventual consistency: The system needs to achieve consistency at a certain time.
For Internet services, it is recommended to use compensation transactions to achieve final consistency. For example, through a series of scheduled tasks, to complete the restoration of data. Specific can refer to the following article.
What are the common distributed transactions? Which one should I use?
Dao layer
With proper data caching, we all try to avoid requests penetrating into the Dao layer. Unless you are particularly familiar with the caching features provided by ORM itself, it is recommended that you use a more general approach to caching data.
Dao layer, mainly on the use of ORM framework. For example, in JPA, if one-to-many or many-to-many mapping is added and lazy loading is not enabled, it is easy to cause deep searching when cascading queries, resulting in high memory overhead and slow execution.
In some business with large amount of data, the method of sub-database and sub-table is mostly adopted. In these sub-database and sub-table components, a lot of simple query statements will be re-parsed and dispersed to each node for calculation, and finally the results are merged.
For example, a simple count statement such as select count(*) from A can route requests to dozens of tables, and finally count them at the coordination node. At present, split database and table middleware, more representative are ShardingJdbc of the driver layer and MyCat of the agent layer, they both have such problems. These components provide the same view to the consumer, but we must pay attention to these differences when coding.
End
So let’s sum it up.
We took a quick look at common optimization ideas for SpringBoot. We introduced three new performance analysis tools. One is Prometheus, a monitoring system that looks at specific metrics; One is a flame map, where you can see specific code hot spots. One is Skywalking, which analyzes call chains in a distributed environment. When in doubt about performance, we all use the same method as Shennong’s taste of 100 herbs, combining the results of various evaluation tools to analyze.
SpringBoot’s own Web container is Tomcat, so we can tune Tomcat to get performance gains. Of course, we also provide a series of optimization ideas for Nginx load balancing.
Finally, we look at some optimization directions of Controller, Service and Dao under the classic MVC architecture, and focus on the distributed transaction problem of Service layer.
Here is an example of a specific optimization.
5 seconds to 1 second, note a “very” significant performance optimization
As a widely used service framework, SpringBoot has done a lot of work in terms of performance optimization, choosing many high-speed components. For example, database connection pool uses hikarICP by default, Redis cache framework uses lettuce by default, local cache provides Caffeine, etc. For a common Web service that interacts with a database, caching is the primary optimization tool. But the devil is in the details, and if you want to optimize your system to the hilt, you’ll need to refer to this article below.
Arsenal of Excellent Performance (Non-advertising)
Finished!
Xjjdog is a public account that doesn’t allow programmers to get sidetracked. Focus on infrastructure and Linux. Ten years architecture, ten billion daily flow, and you discuss the world of high concurrency, give you a different taste. My personal wechat xjjdog0, welcome to add friends, further communication.
Recommended reading:
2. What does the album taste like
3. Bluetooth is like a dream 4. 5. Lost architect, leaving only a script 6. Bugs written by architect, unusual 7. Some programmers, nature is a flock of sheep!
Taste of little sister
Not envy mandarin duck not envy fairy, a line of code half a day
322 original content