Spring Boot中Tomcat、Jetty、Undertow哪個嵌入式服務器最好?
當我們使用 Spring Boot 創建一個 Web 應用程序時,Apache Tomcat 是默認的嵌入式 Web 服務器。然而,我們也可以選擇其他選項,如 Jetty 和 Undertow。但哪一個才是最佳選擇呢?一起來探討一下!
為此,我們將構建一個名為 Greetings API 的簡單 Spring Boot 應用程序。我們將在應用程序的 pom.xml 中添加 Maven 配置文件,以便通過選擇適當的配置文件在不同的嵌入式 Web 服務器之間切換。Tomcat 將是默認選項,但我們還將為 Jetty 和 Undertow 設置配置文件。
設置好配置文件后,我們將為每個 Web 服務器創建 Docker 鏡像,并在單獨的容器中運行它們。然后,我們將根據啟動時間、CPU 和內存使用情況以及請求處理性能對它們進行比較。
測試應用
Greetings API 是一個簡單的應用程序,它公開了一個端點 /greetings?name=?,該端點返回一個問候語。如果沒有提供name,它將返回“[greeting-word] World!”。否則,它將返回“[greeting-word] [name]!”。
它的實現如下:
@Service
public class GreetingsService {
private static final String[] greetingWord = new String[]{"Hello", "Hi", "Olá", "Oi", "Hallo"};
public String randomGreetings(String name) {
try {
String word = greetingWord[ThreadLocalRandom.current().nextInt(greetingWord.length)];
Thread.sleep(1000);
return "%s %s!".formatted(word, name);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@RestController
@RequestMapping("/greetings")
public class GreetingsController {
private final GreetingsService greetingsService;
public GreetingsController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
@GetMapping
public String greeting(@RequestParam(required = false, defaultValue = "World") String name) {
return greetingsService.randomGreetings(name);
}
}
@Service
public class GreetingsService {
private static final String[] greetingWord = new String[]{"Hello", "Hi", "Olá", "Oi", "Hallo"};
public String randomGreetings(String name) {
try {
String word = greetingWord[ThreadLocalRandom.current().nextInt(greetingWord.length)];
Thread.sleep(1000);
return "%s %s!".formatted(word, name);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@RestController
@RequestMapping("/greetings")
public class GreetingsController {
private final GreetingsService greetingsService;
public GreetingsController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
@GetMapping
public String greeting(@RequestParam(required = false, defaultValue = "World") String name) {
return greetingsService.randomGreetings(name);
}
}
@Service
public class GreetingsService {
private static final String[] greetingWord = new String[]{"Hello", "Hi", "Olá", "Oi", "Hallo"};
public String randomGreetings(String name) {
try {
String word = greetingWord[ThreadLocalRandom.current().nextInt(greetingWord.length)];
Thread.sleep(1000);
return "%s %s!".formatted(word, name);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@RestController
@RequestMapping("/greetings")
public class GreetingsController {
private final GreetingsService greetingsService;
public GreetingsController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
@GetMapping
public String greeting(@RequestParam(required = false, defaultValue = "World") String name) {
return greetingsService.randomGreetings(name);
}
}
Jetty和Undertow的配置
在pom.xml中增加Jetty和Undertow的配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
...
<dependencies>
...
</dependencies>
<profiles>
<!-- Jetty Profile -->
<profile>
<id>jetty</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<!-- Undertow Profile -->
<profile>
<id>undertow</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Undertow instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
...
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
...
<dependencies>
...
</dependencies>
<profiles>
<!-- Jetty Profile -->
<profile>
<id>jetty</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<!-- Undertow Profile -->
<profile>
<id>undertow</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Undertow instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
...
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
...
<dependencies>
...
</dependencies>
<profiles>
<!-- Jetty Profile -->
<profile>
<id>jetty</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<!-- Undertow Profile -->
<profile>
<id>undertow</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Undertow instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
...
</build>
</project>
application.properties 配置
分別配置Tomcat、Jetty和Undertow線程配置,具體如下:
# Maximum amount of worker threads. 200 is the default.
server.tomcat.threads.max=200
# Maximum number of threads. 200 is the default.
server.jetty.threads.max=200
# Number of worker threads. The default is 8 times the number of I/O threads.
# The number of I/O threads is set by the property "server.undertow.threads.io" whose default is derived from the number of available processors.
server.undertow.threads.worker=200
# Maximum amount of worker threads. 200 is the default.
server.tomcat.threads.max=200
# Maximum number of threads. 200 is the default.
server.jetty.threads.max=200
# Number of worker threads. The default is 8 times the number of I/O threads.
# The number of I/O threads is set by the property "server.undertow.threads.io" whose default is derived from the number of available processors.
server.undertow.threads.worker=200
# Maximum amount of worker threads. 200 is the default.
server.tomcat.threads.max=200
# Maximum number of threads. 200 is the default.
server.jetty.threads.max=200
# Number of worker threads. The default is 8 times the number of I/O threads.
# The number of I/O threads is set by the property "server.undertow.threads.io" whose default is derived from the number of available processors.
server.undertow.threads.worker=200
這里我們將所有Web服務器的最大線程數設置為200
我們創建了一個高質量的技術交流群,與優秀的人在一起,自己也會優秀起來,趕緊點擊加群,享受一起成長的快樂。
更多關于Spring的技術學習,可關注 https://spring4all.com,專注討論關于Spring的一切。
構建Docker鏡像
上面我們已經為 Jetty 和 Undertow 配置了 Maven 配置文件,所以我們可以為每個嵌入式 Web 服務器構建 Docker 鏡像。
# 構建Tomcat的鏡像
./mvnw clean -DskipTests spring-boot:build-image \
-Dspring-boot.build-image.imageName=greetings-api-tomcat:latest
# 構建Jetty的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pjetty \
-Dspring-boot.build-image.imageName=greetings-api-jetty:latest
# 構建Undertow的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pundertow \
-Dspring-boot.build-image.imageName=greetings-api-undertow:latest
# 構建Tomcat的鏡像
./mvnw clean -DskipTests spring-boot:build-image \
-Dspring-boot.build-image.imageName=greetings-api-tomcat:latest
# 構建Jetty的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pjetty \
-Dspring-boot.build-image.imageName=greetings-api-jetty:latest
# 構建Undertow的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pundertow \
-Dspring-boot.build-image.imageName=greetings-api-undertow:latest
# 構建Tomcat的鏡像
./mvnw clean -DskipTests spring-boot:build-image \
-Dspring-boot.build-image.imageName=greetings-api-tomcat:latest
# 構建Jetty的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pjetty \
-Dspring-boot.build-image.imageName=greetings-api-jetty:latest
# 構建Undertow的鏡像
./mvnw clean -DskipTests spring-boot:build-image -Pundertow \
-Dspring-boot.build-image.imageName=greetings-api-undertow:latest
基準測試
為了從我們的 Docker 容器中收集基準測試的重要數據,我們將使用這個開源項目:https://github.com/ivangfr/api-oha-benchmarker ,
基準測試將包括對每個嵌入式 Web 服務器的 Docker 容器進行負載測試。每次迭代,我們將進行如下操作:
- 啟動 Docker 容器
- 使用 100、300、900 和最終 2700 個并發請求進行 OHA 測試
- 關閉 Docker 容器。
下面,我們在使用 OHA 對 Docker 容器進行 100、300、900 和最終 2700 個并發請求的負載測試后,獲得的結果數據:
Application | numRequests | Concurrency | Success rate(%) | Total(secs) | Slowest(secs) | Fastest(secs) | Average(secs) | Requests/sec |
---------------------- + ----------- + ----------- + --------------- + ----------- + ------------- + ------------- + ------------- + ------------ |
greetings-api-tomcat | 100 | 100 | 100.00 | 1.2949 | 1.2942 | 1.2701 | 1.2822 | 77.2256 |
greetings-api-tomcat | 300 | 300 | 100.00 | 2.1069 | 2.0835 | 1.0311 | 1.4158 | 142.3913 |
greetings-api-tomcat | 900 | 900 | 100.00 | 5.1386 | 5.0797 | 1.0283 | 2.8856 | 175.1446 |
greetings-api-tomcat | 2700 | 2700 | 100.00 | 14.3752 | 14.2423 | 1.0296 | 7.4867 | 187.8241 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-jetty | 100 | 100 | 100.00 | 2.0717 | 2.0708 | 1.2639 | 1.5895 | 48.2692 |
greetings-api-jetty | 300 | 300 | 100.00 | 3.1081 | 3.1056 | 1.0307 | 1.7496 | 96.5223 |
greetings-api-jetty | 900 | 900 | 100.00 | 5.1943 | 5.1762 | 1.0381 | 3.0216 | 173.2659 |
greetings-api-jetty | 2700 | 2700 | 100.00 | 15.3022 | 15.2278 | 1.0160 | 7.9127 | 176.4455 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-undertow | 100 | 100 | 100.00 | 1.3076 | 1.3066 | 1.2665 | 1.2833 | 76.4765 |
greetings-api-undertow | 300 | 300 | 100.00 | 2.0819 | 2.0776 | 1.0413 | 1.4058 | 144.0982 |
greetings-api-undertow | 900 | 900 | 100.00 | 5.1443 | 5.1121 | 1.0780 | 2.8783 | 174.9493 |
greetings-api-undertow | 2700 | 2700 | 100.00 | 14.1748 | 14.0861 | 1.0758 | 7.3721 | 190.4783 |
Application | numRequests | Concurrency | Success rate(%) | Total(secs) | Slowest(secs) | Fastest(secs) | Average(secs) | Requests/sec |
---------------------- + ----------- + ----------- + --------------- + ----------- + ------------- + ------------- + ------------- + ------------ |
greetings-api-tomcat | 100 | 100 | 100.00 | 1.2949 | 1.2942 | 1.2701 | 1.2822 | 77.2256 |
greetings-api-tomcat | 300 | 300 | 100.00 | 2.1069 | 2.0835 | 1.0311 | 1.4158 | 142.3913 |
greetings-api-tomcat | 900 | 900 | 100.00 | 5.1386 | 5.0797 | 1.0283 | 2.8856 | 175.1446 |
greetings-api-tomcat | 2700 | 2700 | 100.00 | 14.3752 | 14.2423 | 1.0296 | 7.4867 | 187.8241 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-jetty | 100 | 100 | 100.00 | 2.0717 | 2.0708 | 1.2639 | 1.5895 | 48.2692 |
greetings-api-jetty | 300 | 300 | 100.00 | 3.1081 | 3.1056 | 1.0307 | 1.7496 | 96.5223 |
greetings-api-jetty | 900 | 900 | 100.00 | 5.1943 | 5.1762 | 1.0381 | 3.0216 | 173.2659 |
greetings-api-jetty | 2700 | 2700 | 100.00 | 15.3022 | 15.2278 | 1.0160 | 7.9127 | 176.4455 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-undertow | 100 | 100 | 100.00 | 1.3076 | 1.3066 | 1.2665 | 1.2833 | 76.4765 |
greetings-api-undertow | 300 | 300 | 100.00 | 2.0819 | 2.0776 | 1.0413 | 1.4058 | 144.0982 |
greetings-api-undertow | 900 | 900 | 100.00 | 5.1443 | 5.1121 | 1.0780 | 2.8783 | 174.9493 |
greetings-api-undertow | 2700 | 2700 | 100.00 | 14.1748 | 14.0861 | 1.0758 | 7.3721 | 190.4783 |
Application | numRequests | Concurrency | Success rate(%) | Total(secs) | Slowest(secs) | Fastest(secs) | Average(secs) | Requests/sec |
---------------------- + ----------- + ----------- + --------------- + ----------- + ------------- + ------------- + ------------- + ------------ |
greetings-api-tomcat | 100 | 100 | 100.00 | 1.2949 | 1.2942 | 1.2701 | 1.2822 | 77.2256 |
greetings-api-tomcat | 300 | 300 | 100.00 | 2.1069 | 2.0835 | 1.0311 | 1.4158 | 142.3913 |
greetings-api-tomcat | 900 | 900 | 100.00 | 5.1386 | 5.0797 | 1.0283 | 2.8856 | 175.1446 |
greetings-api-tomcat | 2700 | 2700 | 100.00 | 14.3752 | 14.2423 | 1.0296 | 7.4867 | 187.8241 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-jetty | 100 | 100 | 100.00 | 2.0717 | 2.0708 | 1.2639 | 1.5895 | 48.2692 |
greetings-api-jetty | 300 | 300 | 100.00 | 3.1081 | 3.1056 | 1.0307 | 1.7496 | 96.5223 |
greetings-api-jetty | 900 | 900 | 100.00 | 5.1943 | 5.1762 | 1.0381 | 3.0216 | 173.2659 |
greetings-api-jetty | 2700 | 2700 | 100.00 | 15.3022 | 15.2278 | 1.0160 | 7.9127 | 176.4455 |
...................... + ........... + ........... + ............... + ........... + ............. + ............. + ............. + ............ |
greetings-api-undertow | 100 | 100 | 100.00 | 1.3076 | 1.3066 | 1.2665 | 1.2833 | 76.4765 |
greetings-api-undertow | 300 | 300 | 100.00 | 2.0819 | 2.0776 | 1.0413 | 1.4058 | 144.0982 |
greetings-api-undertow | 900 | 900 | 100.00 | 5.1443 | 5.1121 | 1.0780 | 2.8783 | 174.9493 |
greetings-api-undertow | 2700 | 2700 | 100.00 | 14.1748 | 14.0861 | 1.0758 | 7.3721 | 190.4783 |
下圖顯示了啟動時間以及最大 CPU 和內存消耗:
Application | StartUpTime(sec) | Max CPU(%) | Max Memory(MB) |
---------------------- + ---------------- + ---------- + -------------- |
greetings-api-tomcat | 1.8920 | 101.26 | 269.10 |
greetings-api-jetty | 1.9790 | 100.40 | 217.70 |
greetings-api-undertow | 2.0450 | 124.15 | 237.50 |
Application | StartUpTime(sec) | Max CPU(%) | Max Memory(MB) |
---------------------- + ---------------- + ---------- + -------------- |
greetings-api-tomcat | 1.8920 | 101.26 | 269.10 |
greetings-api-jetty | 1.9790 | 100.40 | 217.70 |
greetings-api-undertow | 2.0450 | 124.15 | 237.50 |
Application | StartUpTime(sec) | Max CPU(%) | Max Memory(MB) |
---------------------- + ---------------- + ---------- + -------------- |
greetings-api-tomcat | 1.8920 | 101.26 | 269.10 |
greetings-api-jetty | 1.9790 | 100.40 | 217.70 |
greetings-api-undertow | 2.0450 | 124.15 | 237.50 |
響應時間
我們可以看到,在處理 100 個并發請求時,使用 Tomcat 的應用程序最快,時間為 1.2949 秒,緊隨其后的是使用 Undertow 的應用程序,時間為 1.3076 秒。使用 Jetty 的應用程序最慢,時間為 2.0717 秒。
在處理 300 個并發請求時,使用 Undertow 的應用程序略快,時間為 2.0819 秒,緊隨其后的是使用 Tomcat 的應用程序,時間為 2.1069 秒。使用 Jetty 的應用程序最慢,時間為 3.1081 秒。
對于 900 個并發請求,使用 Tomcat 的應用程序略快,時間為 5.1386 秒,緊隨其后的是使用 Undertow 的應用程序,時間為 5.1443 秒,以及使用 Jetty 的應用程序,時間為 5.1943 秒。
最后,在處理 2700 個并發請求時,使用 Undertow 的應用程序最快,時間為 14.1748 秒,緊隨其后的是使用 Tomcat 的應用程序,時間為 14.3752 秒。使用 Jetty 的應用程序最慢,時間為 15.3022 秒。
啟動時間
可以看到,使用 Tomcat 的應用程序啟動時間最快,為 1.8920 秒,緊隨其后的是使用 Jetty 的應用程序,時間為 1.9790 秒。啟動最慢的是使用 Undertow 的應用程序,耗時 2.0450 秒。
內存占用
可以看到,使用 Jetty 的應用程序最大內存消耗最低,為 217.70 MB,緊隨其后的是使用 Undertow 的應用程序,為 237.50 MB。最大內存消耗最高的是使用 Tomcat 的應用程序,為 269.10 MB。
CPU占用
以看到,使用 Jetty 的應用程序最大 CPU 使用率最低,為 100.40%,緊隨其后的是使用 Tomcat 的應用程序,為 101.26%。最高 CPU 使用率的是使用 Undertow 的應用程序,為 124.15%。
結論
根據上面的測試,可以看到Tomcat、Jetty 和 Undertow 各有優勢。使用 Tomcat 的應用程序啟動速度最快,并且能夠很好地處理不同級別的流量,盡管它使用更多的內存。使用 Undertow 的應用程序在處理大量流量時表現最佳,但它的 CPU 使用率更高,啟動時間稍長。使用 Jetty 的應用程序在響應流量方面較慢,但它使用的內存和 CPU 更少,如果你希望節省資源,這是一個不錯的選擇。
選擇最佳的 Web 服務器取決于你的需求。如果你的應用程序將處理大量流量,Undertow 可能是最佳選擇。如果你想要一個全能的選擇,Tomcat 是一個可靠的選擇。而如果節省內存和 CPU 是最重要的,Jetty 是一個不錯的選擇。
來源:https://spring4all.com/forum-post/7674.html