身為 Java 後端開發者,我們都愛 Java 的強大生態與穩定,但把 Java 丟上 AWS Lambda 時,那個動輒 5 到 10 秒的冷啟動(Cold Start) 總是讓我們在 Serverless 的大門前卻步。
雖然 GraalVM Native Image 是一個解法,但編譯時間長、反射(Reflection)設定複雜,對現有的 Spring Boot 專案並不友善。直到 AWS Lambda SnapStart 出現,它透過 MicroVM 快照技術,讓啟動速度提升了 10 倍以上。
今天這篇文章,我將分享在生產環境啟用 SnapStart 的實測數據,以及那些「官方文件沒告訴你」的踩坑經驗。
一、 為什麼 Java 在 Lambda 那麼慢?
Java 的冷啟動延遲主要來自三個階段:
- JVM 啟動:載入 Runtime 環境(如載入 JRE)。
- 類別載入 (Class Loading):這在 Spring Boot 這種依賴注入(DI)框架中尤其嚴重,需要掃描數千個類別與 Bean。
- 框架初始化:建立 Bean 實例、初始化資料庫連線池等。
SnapStart 的原理:它在發布版本時,會先幫你把 Lambda 執行到「初始化完成」的狀態,然後將整個實體(含記憶體與磁碟狀態)拍一張快照(Snapshot)。當請求進來時,它不再從頭啟動 JVM,而是直接從快照恢復(Restore),繞過了最耗時的載入與初始化階段。
二、 支援的 Runtime
SnapStart 目前只支援以下 Java managed runtime:
- Java 11 (Corretto)
- Java 17 (Corretto)
- Java 21 (Corretto)
以下情況不支援 SnapStart:
- Custom Runtime(
provided.al2、provided.al2023) - Docker Image 部署(Container Image)
- 其他語言(Python、Node.js 等):SnapStart 目前僅支援 Java managed runtime,不適用其他語言
在套用之前,請先確認你的 Lambda 使用的是上述其中一個 Runtime。
三、 如何開啟 SnapStart
AWS Console
- 進入 Lambda 函數頁面
- 點選 Configuration → General configuration → Edit
- 找到 SnapStart,選擇
PublishedVersions - 儲存後,重新發布一個 Version,SnapStart 才會生效
注意:SnapStart 只作用在已發布的 Version 上,對
$LATEST無效。
SAM Template
1Resources:
2 MyFunction:
3 Type: AWS::Serverless::Function
4 Properties:
5 Runtime: java21
6 SnapStart:
7 ApplyOn: PublishedVersions
8 AutoPublishAlias: live
CDK (TypeScript)
1const fn = new lambda.Function(this, 'MyFunction', {
2 runtime: lambda.Runtime.JAVA_21,
3 snapStart: lambda.SnapStartConf.ON_PUBLISHED_VERSIONS,
4});
5
6fn.addAlias('live');
四、 實測數據:SnapStart 真的有用嗎?
我們以一個標準的 Spring Cloud Function + Java 21 專案為例,分配 1024MB 記憶體進行測試:
| 階段 | 標準 Lambda (Cold Start) | 啟用 SnapStart (Restore) | 提升幅度 |
|---|---|---|---|
| Init Phase | ~4,800 ms | N/A (已預處理) | - |
| Restore Phase | - | ~280 ms | - |
| Invoke Phase | ~850 ms | ~150 ms | 82% |
| 總計延遲 | ~5,650 ms | ~430 ms | 約 13 倍快 |
五、 生產環境的三大坑:請務必小心!
雖然 SnapStart 很強,但在生產環境直接開啟可能會遇到嚴重的副作用。這是因為「快照」會把當時的狀態完全凍結。
1. 隨機數重複問題 (Uniqueness)
如果你的代碼使用了 java.util.Random,快照會連同隨機數種子一起凍結。恢復出來的每一個 Lambda 實體,產生的第一個隨機數可能完全一樣。
- 解法:請改用
java.security.SecureRandom,它在 Linux 環境下會自動處理快照恢復後的「熵源」更新。
2. 資料庫連線失效 (Network Connections)
快照發生時,資料庫連線可能是建立好的。但當快照被恢復時,該 TCP 連線早就被資料庫端(如 RDS)超時中斷了。
- 解法:利用 CRaC (Coordinated Restore at Checkpoint) 介面在恢復時重新建立連線(詳見第六章)。
3. 狀態一致性
如果初始化過程會抓取「當下的時間」或讀取動態配置,這些值會被固定在快照中,導致恢復後的資料過時。
- 解法:在
afterRestore()中重新呼叫配置中心(如 AWS AppConfig、Parameter Store),刷新快取的動態設定。時間戳記則避免在 init 階段快取,改為在每次 invoke 時動態讀取。
六、 如何正確實作:使用 CRaC 鉤子
為了處理上述連線問題,我們必須實作 Resource 介面。
首先引入 org.crac:crac 依賴:
Maven
1<dependency>
2 <groupId>org.crac</groupId>
3 <artifactId>crac</artifactId>
4 <version>1.4.0</version>
5</dependency>
Gradle
1implementation 'org.crac:crac:1.4.0'
接著實作 Resource 介面:
1import org.crac.Context;
2import org.crac.Resource;
3import org.crac.Core;
4
5public class DatabaseHandler implements Resource {
6 public DatabaseHandler() {
7 // 向 CRaC 註冊此資源
8 Core.getGlobalContext().register(this);
9 }
10
11 @Override
12 public void beforeCheckpoint(Context<? extends Resource> context) {
13 System.out.println("準備拍快照,正在關閉連線池...");
14 // 這裡要優雅地關閉 HikariCP 或其他連線資源
15 }
16
17 @Override
18 public void afterRestore(Context<? extends Resource> context) {
19 System.out.println("快照恢復,正在重新建立連線...");
20 // 重新初始化資料庫連線或刷配置
21 }
22}
七、 總結:該不該用 SnapStart?
SnapStart 是目前 Java 在 AWS Lambda 上的最佳平衡點。 它不需要像 Native Image 那樣大改專案架構,只要加上簡單的 Runtime 鉤子,就能獲得近乎秒開的效能。
計費說明
SnapStart 並非免費。Restore 階段的執行時間(Duration)會照常計費,但因為 Restore 通常只需約 200-300 ms,實際費用影響極小。快照本身的儲存不額外收費。
我的建議:
- 如果你是 Spring Boot / Micronaut 用戶:必開,這能大幅提升使用者體驗。
- 如果你對冷啟動極度敏感(如面向用戶的 API):必開。
- 如果你只是跑背景非同步任務(如 SQS 消費者):可以不開,影響有限。

評論