結論先講
Spring Boot 在 CRUD 場景吃虧,在混合場景翻盤;.NET Core 在 CRUD 場景排名好看,但到高 VU 崩得最慘。 兩個企業級框架的故事不是「誰快誰慢」,而是「各自的軟肋在不同場景被放大」。
配置對比
| 項目 | Spring Boot | .NET Core |
|---|---|---|
| Runtime | JVM (Java 17) | CLR (.NET 8) |
| HTTP Server | Tomcat (embedded) | Kestrel |
| Workers | 50 threads (Tomcat) | Async thread pool (auto) |
| ORM | Hibernate (Spring Data JPA) | Entity Framework Core |
| DB Pool | 50 | 20 |
| Hash | jBCrypt (Java) | BCrypt.Net (C#) |
Spring Boot 的 DB Pool 50 比 .NET Core 的 20 大。Tomcat 的 50 threads 是刻意從預設 200 降低的——因為 2 核 CPU 上 200 threads 反而會因為 context switching 變慢。
Per-VU 數據
| VU | Spring Avg | Spring RPS | .NET Avg | .NET RPS |
|---|---|---|---|---|
| 10 | 127ms | 15 | 216ms | 17 |
| 50 | 2,582ms | 15 | 1,935ms | 21 |
| 100 | 堵塞 (5.5s) | 15 | 4,417ms | 20 |
| 500 | 堵塞 (22.9s) | 16 | 堵塞 (51.2s) | 5 |
| 5K | 堵塞 (22.2s) | 16 | Err 99.1% (5xx) | 5 |
Spring Boot:RPS 不會成長
Spring Boot 最詭異的地方:從 10 VU 到 10K VU,RPS 始終是 15-16。不會上也不會下。
這像是被一個硬上限卡住了。那個硬上限就是 bcrypt——2 核 CPU 上,jBCrypt 的 throughput 就是每秒 15-16 次。Tomcat 的 50 threads 全在排隊等 CPU 做 hash。
.NET Core:高 VU 崩得最慘
.NET Core 在 500 VU 時的表現是全場最差:
- avg 51.2 秒(是的,一個 API 請求平均要等 51 秒)
- 只完成 295 個 request
- RPS 只有 5
原因是 BCrypt.Net 的 C# 實作特別慢(第 26 篇有詳細分析),加上 async thread pool 在 CPU-bound 操作下的效率不如 Tomcat 的 thread pool。
到 5K VU 更慘:99.1% 的 request 回 5xx。Kestrel 接受了連線但 CLR thread pool 處理不過來,直接噴 Internal Server Error。
JVM Warm-up 效應
Spring Boot 在 10 VU 時 avg 127ms,但這包含了 JVM 的 warm-up 成本。JIT compiler 需要觀察一段時間的 hot path 才能做優化。
在我們的壓測中,10 VU 階段只跑 3 分鐘。 前 30 秒是 warm-up,後 2.5 分鐘才是穩定狀態。如果只看後 2 分鐘的數據,Spring Boot 的 avg 可能降到 100ms 左右。
這是 JVM 系框架在短期壓測中的劣勢。但在生產環境(持續運行),JIT 會把 hot path 優化到接近 native 速度——這就是為什麼 Spring Boot 在混合場景(長時間、大量讀取)中表現翻盤的原因。
Hibernate vs Entity Framework
Hibernate 的優勢:L2 Cache
Hibernate 有 Second Level Cache——跨 session 的查詢快取。如果 User A 和 User B 查詢同一個 Post,第二次查詢直接從 cache 拿,不打 DB。
在 CRUD 場景中這個優勢不明顯(每次都是新 user),但在混合場景(大量讀取同一批 Post)中效果顯著。
Entity Framework 的問題:Change Tracking
EF Core 預設啟用 change tracking——每次查詢都會把結果存進 context,追蹤有沒有被修改。在壓測場景中,大量 context 會吃記憶體、增加 GC 壓力。
// 可以關閉 tracking 提升讀取效能
var users = context.Users.AsNoTracking().ToList();但我們的壓測程式碼沒有特別加 AsNoTracking()——因為大部分開發者也不會加。
為什麼 CRUD 和混合場景排名反轉
CRUD: Spring Boot 8th / .NET Core 2nd
Mixed: Spring Boot 1st / .NET Core 4th
Spring Boot 的翻盤因素:
- JIT warm-up 在長時間運行中發揮效果
- Hibernate L2 Cache 在讀取密集場景中大幅減少 DB 查詢
- Tomcat 的 thread pool 在 I/O 密集場景中比 CPU 密集場景有效率
.NET Core 的下降因素:
- BCrypt.Net 的慢在任何包含 Auth 的場景都是瓶頸
- EF Core 的 change tracking 在大量查詢時吃記憶體
- CLR thread pool 的 auto-scaling 在突發流量下反應較慢
實務建議
Spring Boot 團隊
- 給足 JVM warm-up: 部署後先用 health check 或 warm-up script 跑幾分鐘
- 啟用 Hibernate L2 Cache: 讀取密集場景效果顯著
- 調低 Tomcat maxThreads: 2 核 CPU 上 50 就夠了,200 threads 反而慢
- 記憶體要給足: JVM heap 至少 512MB,建議 1GB
.NET Core 團隊
- 考慮替換 BCrypt.Net: 用
Isopoh.Cryptography.Argon2或自己 P/Invoke native bcrypt - 讀取操作加 AsNoTracking(): 減少 EF Core 的 tracking overhead
- 監控 thread pool:
ThreadPool.GetAvailableThreads()確認沒有 starvation - 不要被 CRUD 排名騙了: .NET Core 排第 2 是因為其他框架比它更慢,不是因為它特別快
下一篇
Laravel CRUD:Nginx 的快速拒絕是把雙刃劍 — Laravel 在 10K VU 時衝出 2.3K RPS(全場最高),但 99.2% 都是錯誤。PHP-FPM + Nginx 的架構讓它的「失敗模式」和其他框架完全不同。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:Python 雙雄 CRUD:Django vs FastAPI 的 GIL 困境 → 下一篇:Laravel CRUD:Nginx 的快速拒絕是把雙刃劍