很多大公司為什么禁止SpringBoot項(xiàng)目使用Tomcat?
前言
今天我們來(lái)聊聊一個(gè)很有意思的現(xiàn)象:為什么越來(lái)越多的大公司禁止SpringBoot項(xiàng)目使用默認(rèn)的Tomcat?
答:因?yàn)閁ndertow的性能更好,有些大公司強(qiáng)制要求使用Undertow。
有些小伙伴在工作中可能已經(jīng)發(fā)現(xiàn)了這個(gè)趨勢(shì),但背后的原因你真的清楚嗎?
一、SpringBoot的默認(rèn)選擇與現(xiàn)狀
SpringBoot作為Java領(lǐng)域最流行的開(kāi)發(fā)框架,其默認(rèn)內(nèi)嵌的Web容器是Tomcat。
這讓我們很多開(kāi)發(fā)者養(yǎng)成了"開(kāi)箱即用"的習(xí)慣,但大公司卻在生產(chǎn)環(huán)境中紛紛轉(zhuǎn)向Undertow。
這背后到底隱藏著什么秘密?
圖片
從上圖可以看出,雖然Tomcat是默認(rèn)選擇,但Undertow在高性能場(chǎng)景下更具優(yōu)勢(shì)。
二、性能對(duì)比
2.1 內(nèi)存占用對(duì)比
讓我們先看一組實(shí)際測(cè)試數(shù)據(jù)。在相同條件下部署SpringBoot應(yīng)用:
容器  | 啟動(dòng)內(nèi)存  | 堆內(nèi)存占用  | 非堆內(nèi)存占用  | 線程內(nèi)存  | 
Tomcat  | 120MB  | 80MB  | 25MB  | 15MB  | 
Undertow  | 85MB  | 60MB  | 15MB  | 10MB  | 
優(yōu)化比例  | -29%  | -25%  | -40%  | -33%  | 
從數(shù)據(jù)可以看出,Undertow在內(nèi)存占用方面有明顯優(yōu)勢(shì)。
對(duì)于大規(guī)模部署的微服務(wù)架構(gòu),這種內(nèi)存節(jié)省會(huì)累積成巨大的成本優(yōu)勢(shì)。
2.2 并發(fā)處理能力
在并發(fā)性能測(cè)試中,Undertow同樣表現(xiàn)優(yōu)異:
// 性能測(cè)試代碼示例
@SpringBootTest
class WebContainerPerformanceTest {
    
    @Test
    void testConcurrentPerformance() {
        // 模擬1000并發(fā)用戶持續(xù)請(qǐng)求30秒
        LoadTest loadTest = LoadTest.configure()
            .threads(1000)
            .duration(30, TimeUnit.SECONDS)
            .build();
            
        // Tomcat測(cè)試結(jié)果
        TomcatResult tomcatResult = loadTest.runWithTomcat();
        
        // Undertow測(cè)試結(jié)果  
        UndertowResult undertowResult = loadTest.runWithUndertow();
        
        // 結(jié)果對(duì)比
        System.out.println("QPS - Tomcat: " + tomcatResult.getQps());
        System.out.println("QPS - Undertow: " + undertowResult.getQps());
        System.out.println("平均響應(yīng)時(shí)間 - Tomcat: " + tomcatResult.getAvgResponseTime());
        System.out.println("平均響應(yīng)時(shí)間 - Undertow: " + undertowResult.getAvgResponseTime());
    }
}典型測(cè)試結(jié)果:
- Tomcat:QPS 8500,平均響應(yīng)時(shí)間 15ms
 - Undertow:QPS 12000,平均響應(yīng)時(shí)間 8ms
 
三、底層架構(gòu)差異
3.1 Tomcat的架構(gòu)設(shè)計(jì)
Tomcat采用傳統(tǒng)的BIO/NIO連接器架構(gòu):
圖片
Tomcat的架構(gòu)相對(duì)重量級(jí),每個(gè)層次都有明確的職責(zé)劃分,但也帶來(lái)了額外的開(kāi)銷。
3.2 Undertow的架構(gòu)設(shè)計(jì)
Undertow采用更加現(xiàn)代的XNIO基礎(chǔ)架構(gòu):
圖片
Undertow的核心特點(diǎn):
- IO線程與工作線程分離:IO線程處理網(wǎng)絡(luò)IO,工作線程處理業(yè)務(wù)邏輯
 - 事件驅(qū)動(dòng)架構(gòu):基于回調(diào)的事件處理機(jī)制
 - 零拷貝能力:支持直接緩沖區(qū),減少內(nèi)存拷貝
 
四、內(nèi)存管理
4.1 直接內(nèi)存使用
Undertow在內(nèi)存管理上更加高效,大量使用直接內(nèi)存(Direct Buffer):
// Undertow的內(nèi)存管理示例
publicclass UndertowMemoryManagement {
    
    // 使用直接緩沖區(qū)處理請(qǐng)求
    public void handleRequest(HttpServerExchange exchange) {
        // 獲取直接緩沖區(qū)
        ByteBuffer buffer = exchange.getConnection().getBufferPool().allocate();
        
        try {
            // 直接操作緩沖區(qū),避免拷貝
            readRequestData(exchange, buffer);
            processRequest(buffer);
            writeResponse(exchange, buffer);
        } finally {
            // 釋放緩沖區(qū)
            exchange.getConnection().getBufferPool().free(buffer);
        }
    }
    
    // Tomcat通常需要多次內(nèi)存拷貝
    public void tomcatHandleRequest(Request request, Response response) {
        // 從輸入流讀取數(shù)據(jù)(內(nèi)存拷貝)
        byte[] inputData = readInputStream(request.getInputStream());
        
        // 處理數(shù)據(jù)(可能再次拷貝)
        byte[] outputData = processData(inputData);
        
        // 寫入輸出流(又一次拷貝)
        response.getOutputStream().write(outputData);
    }
}這種零拷貝的設(shè)計(jì)在大文件傳輸和高并發(fā)場(chǎng)景下優(yōu)勢(shì)明顯。
4.2 連接池優(yōu)化
Undertow的連接管理更加精細(xì):
# Undertow配置示例
server:
undertow:
    # 線程池配置
    threads:
      worker:16
      io:4
    # 緩沖區(qū)配置
    buffer-size:1024
    direct-buffers:true
    # 連接配置
    max-connections:10000
    max-http-post-size:10485760對(duì)比Tomcat的配置:
# Tomcat配置示例
server:
tomcat:
    # 連接器配置
    max-connections:10000
    max-threads:200
    min-spare-threads:10
    # 其他配置
    max-http-post-size:10485760
    connection-timeout:20000五、并發(fā)模型
5.1 Undertow的XNIO架構(gòu)
Undertow基于JBoss的XNIO庫(kù),采用更加現(xiàn)代的并發(fā)模型:
// XNIO工作線程模型示例
publicclass XNIOWorkerModel {
    
    public void demonstrateWorkerModel() {
        // 創(chuàng)建Worker實(shí)例
        XnioWorker worker = Xnio.getInstance().createWorker(
            OptionMap.create(Options.THREAD_DAEMON, true)
        );
        
        // IO線程處理網(wǎng)絡(luò)事件
        worker.getIoThread().execute(() -> {
            // 處理IO就緒事件
            handleIOReadyEvents();
        });
        
        // 工作線程處理業(yè)務(wù)邏輯
        worker.getWorkerThreadPool().execute(() -> {
            // 執(zhí)行業(yè)務(wù)處理
            executeBusinessLogic();
        });
    }
}這種設(shè)計(jì)的優(yōu)勢(shì)在于:
- IO線程專注網(wǎng)絡(luò):不被業(yè)務(wù)邏輯阻塞
 - 工作線程池彈性:根據(jù)業(yè)務(wù)需求動(dòng)態(tài)調(diào)整
 - 事件驅(qū)動(dòng)高效:基于事件回調(diào),減少線程切換
 
5.2 Tomcat的線程模型對(duì)比
Tomcat的傳統(tǒng)線程模型:
圖片
Tomcat的線程模型在極高并發(fā)下會(huì)出現(xiàn):
- 大量的線程上下文切換開(kāi)銷
 - 線程阻塞等待資源
 - 內(nèi)存占用隨線程數(shù)線性增長(zhǎng)
 
六、配置靈活性
6.1 精細(xì)化配置能力
Undertow提供了極其細(xì)致的配置選項(xiàng),滿足各種復(fù)雜場(chǎng)景:
@Configuration
publicclass UndertowConfig {
    
    @Bean
    public UndertowServletWebServerFactory undertowServletWebServerFactory() {
        UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
        
        factory.addBuilderCustomizers(builder -> {
            // 配置HTTP/2
            builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true);
            
            // 配置緩沖區(qū)
            builder.setSocketOption(Options.RECEIVE_BUFFER, 1024 * 16);
            builder.setSocketOption(Options.SEND_BUFFER, 1024 * 64);
            
            // 配置線程池
            builder.setIoThreads(Runtime.getRuntime().availableProcessors());
            builder.setWorkerThreads(200);
            
            // 配置連接數(shù)限制
            builder.setServerOption(UndertowOptions.MAX_CONNECTIONS, 10000);
        });
        
        return factory;
    }
}6.2 處理器鏈機(jī)制
Undertow的處理器鏈機(jī)制允許深度定制請(qǐng)求處理流程:
public class CustomHandler implements HttpHandler {
    
    privatefinal HttpHandler next;
    
    public CustomHandler(HttpHandler next) {
        this.next = next;
    }
    
    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        long startTime = System.currentTimeMillis();
        
        try {
            // 前置處理:認(rèn)證、日志等
            preHandle(exchange);
            
            // 調(diào)用下一個(gè)處理器
            next.handleRequest(exchange);
            
        } finally {
            // 后置處理:統(tǒng)計(jì)、清理等
            postHandle(exchange, startTime);
        }
    }
    
    private void preHandle(HttpServerExchange exchange) {
        // 認(rèn)證檢查
        if (!checkAuthentication(exchange)) {
            exchange.setStatusCode(401);
            exchange.endExchange();
            return;
        }
        
        // 請(qǐng)求日志記錄
        logRequest(exchange);
    }
}這種靈活的處理器鏈機(jī)制讓Undertow在定制化需求面前游刃有余。
七、實(shí)戰(zhàn)案例
7.1 某電商平臺(tái)的容器遷移實(shí)踐
某大型電商平臺(tái)在高峰期面臨嚴(yán)重的性能瓶頸,遷移到Undertow后的效果:
遷移前(Tomcat):
- 單機(jī)QPS:8000
 - 平均響應(yīng)時(shí)間:25ms
 - 內(nèi)存占用:2GB
 - CPU使用率:85%
 
遷移后(Undertow):
- 單機(jī)QPS:15000(+87%)
 - 平均響應(yīng)時(shí)間:12ms(-52%)
 - 內(nèi)存占用:1.2GB(-40%)
 - CPU使用率:65%(-23%)
 
7.2 配置優(yōu)化示例
# 生產(chǎn)環(huán)境Undertow優(yōu)化配置
server:
undertow:
    # IO線程數(shù)(通常為CPU核心數(shù))
    io-threads:8
    # 工作線程數(shù)(根據(jù)業(yè)務(wù)調(diào)整)
    worker-threads:200
    # 直接緩沖區(qū)
    direct-buffers:true
    buffer-size:16384
    # 連接配置
    max-connections:10000
    max-http-post-size:10485760
    # 優(yōu)雅關(guān)閉
    no-request-timeout:60000
    drain-wait-time:20000
    
# JVM優(yōu)化配合
port:8080
compression:
    enabled:true
    mime-types:text/html,text/xml,text/plain,application/json八、如何遷移?
8.1 Maven配置調(diào)整
<!-- 排除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>
<!-- 引入U(xiǎn)ndertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>8.2 遷移注意事項(xiàng)
有些小伙伴在遷移過(guò)程中可能會(huì)遇到以下問(wèn)題:
- Servlet API兼容性:確保代碼使用標(biāo)準(zhǔn)Servlet API
 - WebSocket配置:Undertow的WebSocket配置與Tomcat不同
 - SSL配置:證書和SSL配置可能需要調(diào)整
 - 會(huì)話管理:如果使用分布式會(huì)話,需要驗(yàn)證兼容性
 
總結(jié)
通過(guò)上面的詳細(xì)分析,我們可以總結(jié)出大公司選擇Undertow的主要原因:
1 性能優(yōu)勢(shì)明顯
- 更高的并發(fā)處理能力:XNIO架構(gòu)更適應(yīng)高并發(fā)場(chǎng)景
 - 更低的內(nèi)存占用:直接內(nèi)存和緩沖區(qū)優(yōu)化減少內(nèi)存使用
 - 更好的響應(yīng)時(shí)間:事件驅(qū)動(dòng)模型減少處理延遲
 
2 資源利用高效
- 精細(xì)化的資源控制:線程池、緩沖區(qū)等可精細(xì)配置
 - 更好的可擴(kuò)展性:適應(yīng)云原生和容器化部署
 - 更低的運(yùn)維成本:減少服務(wù)器數(shù)量和資源消耗
 
3 技術(shù)架構(gòu)先進(jìn)
- 現(xiàn)代化的并發(fā)模型:更適應(yīng)現(xiàn)代硬件架構(gòu)
 - 靈活的擴(kuò)展機(jī)制:處理器鏈支持深度定制
 - 更好的未來(lái)發(fā)展:為HTTP/2、Quic等新協(xié)議做好準(zhǔn)備
 
4 業(yè)務(wù)需求驅(qū)動(dòng)
- 大規(guī)模部署需求:微服務(wù)架構(gòu)下容器性能至關(guān)重要
 - 成本控制壓力:性能提升直接轉(zhuǎn)化為成本降低
 - 技術(shù)競(jìng)爭(zhēng)力:保持技術(shù)棧的先進(jìn)性和競(jìng)爭(zhēng)力
 
有些小伙伴可能會(huì)說(shuō):"我的項(xiàng)目并發(fā)量不大,用Tomcat也挺好"。
確實(shí),對(duì)于小型項(xiàng)目或個(gè)人項(xiàng)目,Tomcat完全夠用。
但對(duì)于大公司來(lái)說(shuō),技術(shù)選型要考慮的是規(guī)模化效應(yīng)。
當(dāng)你有成千上萬(wàn)個(gè)微服務(wù)實(shí)例時(shí),每個(gè)實(shí)例節(jié)省幾十MB內(nèi)存,總體節(jié)省的資源就是天文數(shù)字。















 
 
 





 
 
 
 