冷啟動概述
AWS Lambda 冷啟動(Cold Start)是指當 Lambda 函數在一段時間內沒有被調用,或當需要擴展新的執行環境時,AWS 需要初始化新的執行環境來處理請求的過程。這個初始化過程會導致額外的延遲,對於需要低延遲回應的應用程式來說是一個重要的效能考量。
冷啟動的典型延遲時間範圍:
- Node.js / Python:100ms - 500ms
- Java / .NET:500ms - 5000ms
- 容器映像:可能更長
冷啟動原因分析
冷啟動發生在以下情況:
- 首次調用:函數第一次被調用時
- 閒置超時:執行環境閒置一段時間後被回收(通常 5-15 分鐘)
- 擴展事件:並發請求超過現有執行環境數量
- 程式碼更新:部署新版本的函數程式碼
- 設定變更:修改記憶體、超時等設定
冷啟動過程包含:
1
2
3
4
5
6
7
8
9
| ┌─────────────────────────────────────────────────────────────┐
│ 冷啟動階段 │
├─────────────────────────────────────────────────────────────┤
│ 1. 下載程式碼套件 │
│ 2. 啟動執行環境 │
│ 3. 初始化執行時期(Runtime) │
│ 4. 執行初始化程式碼(Handler 外的程式碼) │
│ 5. 執行 Handler 函數 │
└─────────────────────────────────────────────────────────────┘
|
測量冷啟動時間
使用 CloudWatch Logs Insights
1
2
3
4
5
6
| filter @type = "REPORT"
| stats avg(@initDuration) as avg_init,
max(@initDuration) as max_init,
min(@initDuration) as min_init,
count(*) as invocations
| sort invocations desc
|
使用 AWS CLI 查詢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 取得函數的調用統計
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=my-function \
--start-time 2024-04-13T00:00:00Z \
--end-time 2024-04-14T00:00:00Z \
--period 3600 \
--statistics Average Maximum
# 查詢冷啟動相關日誌
aws logs filter-log-events \
--log-group-name /aws/lambda/my-function \
--filter-pattern "Init Duration"
|
使用 X-Ray 追蹤
在 Lambda 設定中啟用 X-Ray 追蹤:
1
2
3
| aws lambda update-function-configuration \
--function-name my-function \
--tracing-config Mode=Active
|
程式語言選擇影響
不同程式語言對冷啟動時間有顯著影響:
| 程式語言 | 平均冷啟動時間 | 建議使用場景 |
|---|
| Python | 100-300ms | API Gateway、資料處理 |
| Node.js | 100-300ms | Web API、事件處理 |
| Go | 50-150ms | 高效能、低延遲需求 |
| Java | 500-3000ms | 企業應用、Spring Boot |
| .NET | 400-1500ms | 企業應用、C# 專案 |
輕量級執行時期範例(Python)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 避免在 handler 外進行不必要的初始化
import json
# 僅導入必要的模組
from boto3 import client
# 在 handler 外初始化客戶端(會被重用)
dynamodb = client('dynamodb')
def lambda_handler(event, context):
response = dynamodb.get_item(
TableName='my-table',
Key={'id': {'S': event['id']}}
)
return {
'statusCode': 200,
'body': json.dumps(response.get('Item', {}))
}
|
減少程式碼套件大小
最小化依賴套件
1
2
3
4
5
6
7
8
9
10
11
| # 使用 pip 僅安裝必要套件
pip install --target ./package boto3 --no-deps
# 移除不必要的檔案
find ./package -type d -name "__pycache__" -exec rm -rf {} +
find ./package -type f -name "*.pyc" -delete
find ./package -type f -name "*.pyo" -delete
# 建立部署套件
cd package && zip -r9 ../deployment.zip .
cd .. && zip -g deployment.zip lambda_function.py
|
使用 Lambda Layers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 建立 Layer
mkdir -p python/lib/python3.11/site-packages
pip install requests -t python/lib/python3.11/site-packages
zip -r layer.zip python
# 發布 Layer
aws lambda publish-layer-version \
--layer-name my-dependencies \
--zip-file fileb://layer.zip \
--compatible-runtimes python3.11
# 將 Layer 附加到函數
aws lambda update-function-configuration \
--function-name my-function \
--layers arn:aws:lambda:us-east-1:123456789012:layer:my-dependencies:1
|
Provisioned Concurrency
Provisioned Concurrency 可以預先初始化執行環境,消除冷啟動:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 設定 Provisioned Concurrency
aws lambda put-provisioned-concurrency-config \
--function-name my-function \
--qualifier prod \
--provisioned-concurrent-executions 10
# 查詢設定狀態
aws lambda get-provisioned-concurrency-config \
--function-name my-function \
--qualifier prod
# 刪除 Provisioned Concurrency
aws lambda delete-provisioned-concurrency-config \
--function-name my-function \
--qualifier prod
|
使用 Application Auto Scaling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 註冊可擴展目標
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id function:my-function:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--min-capacity 5 \
--max-capacity 100
# 設定擴展策略
aws application-autoscaling put-scaling-policy \
--service-namespace lambda \
--resource-id function:my-function:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--policy-name my-scaling-policy \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 0.7,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
}
}'
|
Lambda SnapStart(Java)
SnapStart 是專為 Java 執行時期設計的功能,可大幅減少冷啟動時間:
1
2
3
4
5
6
7
8
| # 啟用 SnapStart
aws lambda update-function-configuration \
--function-name my-java-function \
--snap-start ApplyOn=PublishedVersions
# 發布新版本以套用 SnapStart
aws lambda publish-version \
--function-name my-java-function
|
Java 程式碼最佳化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.crac.Core;
import org.crac.Resource;
public class Handler implements RequestHandler<Map<String, Object>, String>, Resource {
private final DynamoDbClient dynamoDb;
public Handler() {
// 初始化時建立客戶端
this.dynamoDb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build();
// 註冊 CRaC 資源以處理快照還原
Core.getGlobalContext().register(this);
}
@Override
public void beforeCheckpoint(org.crac.Context<? extends Resource> context) {
// 快照前的清理工作
}
@Override
public void afterRestore(org.crac.Context<? extends Resource> context) {
// 還原後的初始化工作
}
@Override
public String handleRequest(Map<String, Object> event, Context context) {
// 處理請求
return "Success";
}
}
|
連線重用技巧
保持連線存活(Python)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import boto3
from botocore.config import Config
# 設定連線重用
config = Config(
connect_timeout=5,
read_timeout=30,
retries={'max_attempts': 3},
tcp_keepalive=True
)
# 在 handler 外建立客戶端
dynamodb = boto3.resource('dynamodb', config=config)
table = dynamodb.Table('my-table')
def lambda_handler(event, context):
# 重用已建立的連線
response = table.get_item(Key={'id': event['id']})
return response.get('Item')
|
HTTP Keep-Alive(Node.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| const https = require('https');
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
// 建立保持連線的 Agent
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50
});
// 在 handler 外初始化客戶端
const client = new DynamoDBClient({
requestHandler: {
httpAgent: agent
}
});
exports.handler = async (event) => {
// 使用重用的連線
const result = await client.send(new GetItemCommand({
TableName: 'my-table',
Key: { id: { S: event.id } }
}));
return result.Item;
};
|
最佳實踐
1. 記憶體配置優化
較高的記憶體配置會獲得更多 CPU 資源,可能減少冷啟動時間:
1
2
3
4
5
6
7
8
9
10
11
12
| # 測試不同記憶體配置的效能
for mem in 128 256 512 1024 2048; do
aws lambda update-function-configuration \
--function-name my-function \
--memory-size $mem
# 執行測試調用
aws lambda invoke \
--function-name my-function \
--payload '{}' \
response.json
done
|
2. 預熱策略
使用 CloudWatch Events 定期調用函數:
1
2
3
4
5
6
7
8
9
| # 建立預熱規則
aws events put-rule \
--name lambda-warmup \
--schedule-expression "rate(5 minutes)"
# 新增 Lambda 作為目標
aws events put-targets \
--rule lambda-warmup \
--targets Id=1,Arn=arn:aws:lambda:us-east-1:123456789012:function:my-function
|
3. 架構建議
- 將大型函數拆分為多個小型函數
- 使用非同步調用減少延遲影響
- 考慮使用 Step Functions 編排工作流程
- 對於極低延遲需求,考慮使用 EC2 或 ECS
4. 監控儀表板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 建立 CloudWatch 儀表板
aws cloudwatch put-dashboard \
--dashboard-name LambdaColdStartMonitor \
--dashboard-body '{
"widgets": [
{
"type": "metric",
"properties": {
"metrics": [
["AWS/Lambda", "Duration", "FunctionName", "my-function"],
[".", "ConcurrentExecutions", ".", "."]
],
"period": 60,
"title": "Lambda Performance"
}
}
]
}'
|
參考資料