第 4 版 (V4) 適用於 .NET 的 AWS SDK 已發行!
如需有關中斷變更和遷移應用程式的資訊,請參閱遷移主題。
本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
的效能最佳實務 適用於 .NET 的 AWS SDK
建立用戶端、處理回應和設定應用程式的方式,會對輸送量、延遲和記憶體使用量產生很大的影響。本主題說明可協助應用程式有效且可靠地執行的用量和組態模式。這些模式在高負載或資源受限的環境中最為重要,例如容器和無伺服器函數。遵循這些實務可以改善效能,並防止常見問題,例如回應緩慢、懸置和高記憶體使用量。
最具影響力的做法如下:
-
重複使用單一、長期的服務用戶端,而不是為每個請求建立一個。
-
處置回應和串流,以便將網路連線釋放回連線集區。
-
正確使用 async/await 且絕不會封鎖非同步 SDK 呼叫。
-
為 AWS Lambda 和 Amazon ECS 等受限環境設定 .NET 垃圾回收。
-
在高輸送量下管理 HTTP 連線和連線限制。
重複使用單一、長期的服務用戶端
AmazonS3Client 和 AmazonDynamoDBClient 等服務用戶端具有執行緒安全性,建構成本相對較高,且壽命較長。建構用戶端可解析區域和端點資訊,並建立基礎 HTTP 基礎設施。為每個服務建立一個用戶端,並在應用程式的生命週期內重複使用它。如果您呼叫多個區域,請為每個區域建立單獨的用戶端。
警告
請勿為每個請求或在迴圈中建立新的服務用戶端。建立用戶端會重複流失基礎 HTTP 基礎設施,這會增加可測量的延遲。它也可以耗盡通訊端或處理,並導致請求在載入時失敗或停滯。
在使用相依性插入的應用程式中,將用戶端註冊為單一用戶端。AWSSDK.Extensions.NETCore.Setup NuGet 套件的AddAWSService延伸方法會將用戶端註冊為預設的 生命週期ServiceLifetime.Singleton。用戶端會在第一次請求時建立,並在程序的生命週期內重複使用相同的執行個體。如需使用相依性注入註冊 AWS 服務以及從組態讀取選項的詳細資訊,請參閱 AWSSDK.Extensions.NETCore.Setup 和 IConfiguration。
您也可以手動將用戶端註冊為單一用戶端。
builder.Services.AddSingleton<IAmazonS3>(_ => new AmazonS3Client());
注意
由於 預設會將用戶端AddAWSService註冊為單一用戶端,因此請勿處置其提供的用戶端。如果您需要非預設生命週期,請將不同的ServiceLifetime值傳遞給 的選用lifetime參數AddAWSService。
重複使用用戶端並不表示您應該停止處理每個操作的回應。在應用程式的生命週期內重複使用用戶端,但繼續處置個別操作傳回的回應和串流,如 中所述處置回應和串流。
處置回應和串流以釋出連線
有些 SDK 回應物件具有即時網路串流。最常見的範例是 GetObjectResponse,它會透過其ResponseStream屬性實作IDisposable和公開物件的內容。回應會保留開放的 HTTP 連線,直到串流完全讀取或回應遭到處置為止。如果您洩漏這些回應 (例如,透過在迴圈GetObject中呼叫而不捨棄每個結果),開啟的連線會累積到連線集區耗盡為止,以及下一個呼叫區塊。這是「隨機」或停止在 Nth 物件上的下載報告背後的原因。
一律將串流回應包裝在using陳述式中,並立即讀取或複製其串流。
using Amazon.S3; using Amazon.S3.Model; // s3Client is a reused, long-lived client. var request = new GetObjectRequest { BucketName = bucketName, Key = key }; using var response = await s3Client.GetObjectAsync(request); await response.WriteResponseStreamToFileAsync(filePath, append: false, CancellationToken.None);
處置回應並處置其ResponseStream相當;其中一個會關閉基礎網路串流,並傳回與集區的連線。
提示
如果您只需要物件中繼資料,例如大小、上次修改的時間、內容類型或 ETag,請呼叫 GetObjectMetadata或 ,GetObjectMetadataAsync而非 GetObject。中繼資料操作會發出 HTTP HEAD請求,且不會傳輸任何物件內文,因此沒有要管理的內容串流。
var metadata = await s3Client.GetObjectMetadataAsync(bucketName, key); Console.WriteLine($"Size: {metadata.ContentLength} bytes");
警告
回應和串流類型,例如 GetObjectResponse不會實作定稿器,因此您無法依賴垃圾收集來為您釋出其連線。您必須使用 using或明確Dispose呼叫來決定性地處置回應和串流。
正確使用非同步/等待
在現代 .NET 上, 中的服務操作 適用於 .NET 的 AWS SDK 是非同步的,並傳回 Task。在 .NET Framework 上,同步方法也存在,但建議使用更好的非同步呼叫擴展。使用 操作await,並將程式碼async的每一層傳播到進入點。如需使用 SDK 進行非同步程式設計的詳細資訊,請參閱 非同步程式設計。
警告
請勿以 .Result、 .Wait()或 封鎖非同步 SDK 呼叫.GetAwaiter().GetResult()。此sync-over-async模式是應用程式停止運作的常見原因:
-
在負載下,封鎖呼叫會耗用比執行緒集區成長更快的執行緒,因此無法執行接續。此執行緒集區匱乏顯示為無限期停止,是現代 .NET 上的主要失敗模式 (其中 ASP.NET Core 沒有預設的
SynchronizationContext)。 -
有些內容會擷取
SynchronizationContext,例如傳統 ASP.NET、Windows Forms、Blazor WebAssembly、 WPF和 .NET Framework 應用程式。在這些內容中,封鎖呼叫執行緒,而接續需要相同的執行緒產生死鎖。開發套件中的操作會在ConfigureAwait(false)內部使用 ,因此不會將其接續發佈回擷取的內容。死結源自於您呼叫鏈中其他位置擷取死結的async程式碼。await在整個過程中使用 SDK 呼叫可完全避免問題發生。
下列方法會在非同步呼叫上封鎖 ,並可能使執行緒集區死結或挹注。
// Anti-pattern: do not do this. public GetObjectResponse Get(GetObjectRequest request) { return s3Client.GetObjectAsync(request).Result; }
請改為進行 方法async和 await 呼叫。
public async Task<GetObjectResponse> GetAsync(GetObjectRequest request) { return await s3Client.GetObjectAsync(request); }
非同步程式碼的其他建議:
-
將 傳遞
CancellationToken至每個 操作,以便可以取消緩慢或停滯的呼叫,而不是掛斷。如需詳細資訊,請參閱針對逾時使用 CancellationToken 參數。 -
請勿在空白
catch區塊中吞下例外狀況。這樣做會隱藏實際故障,並使懸置與錯誤不區分。擷取特定例外狀況並加以記錄。 -
還原擷取的例外狀況時,請使用
throw;而非 ,throw ex;以保留原始堆疊追蹤。 -
如果您必須從同步界限呼叫 ,請將它視為最後手段,並將工作與擷取的內容隔離,而不是讓封鎖呼叫成為預設模式。
設定 AWS Lambda 和 Amazon ECS 的 .NET 垃圾回收
當容器出現「記憶體洩漏」時,.NET 垃圾收集器 (GC) 可能會保留回收的記憶體以供重複使用。因此,即使受管堆積未增長,程序記憶體也會顯示高且穩定。此外,GC 不會自動偵測容器的記憶體限制 (其 cgroup 限制)。在受限的環境中,堆積可能會增加到主機的記憶體,而不是容器的限制。這可能會導致 OutOfMemoryException或容器終止。
為了協助 GC 在受限的環境中正常運作:
-
在容器上設定明確的記憶體限制,讓 GC 遵守 cgroup 限制,和/或設定
DOTNET_GCHeapHardLimit(絕對位元組值,以十六進位為單位) 或DOTNET_GCHeapHardLimitPercent環境變數來限制受管堆積。在數個報告的 Amazon ECS out-of-memory案例中,設定硬記憶體限制可解決當機問題。 -
在小型 AWS Lambda 和 Amazon ECS 主機上,請考慮停用並行 (背景) 垃圾收集,讓收集器不會保留額外的記憶體;例如,將
DOTNET_gcConcurrent環境變數設定為0,或在<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>專案檔案中設定 。 -
繫結並行。一次啟動許多操作,例如
Task.WhenAll透過大型集合呼叫 ,會膨脹程序記憶體,並可能使連線集區停止運作。改為限制平行處理的程度。例如,避免此無限制模式:// Anti-pattern: starts one task per item with no limit. await Task.WhenAll(keys.Select(key => s3Client.GetObjectMetadataAsync(bucket, key)));反之,上限會與 並行
Parallel.ForEachAsync:var options = new ParallelOptions { MaxDegreeOfParallelism = 10 }; await Parallel.ForEachAsync(keys, options, async (key, token) => { await s3Client.GetObjectMetadataAsync(bucket, key, token); });
如需這些設定的詳細資訊,請參閱 https:// 上的垃圾回收執行時間組態選項
管理 HTTP 連線和連線限制
在高輸送量下,兩個與連線相關的問題很常見。第一個是開啟太多短期連線。這會耗盡暫時性連接埠、在 中保留通訊端TIME_WAIT,並新增 TCP 和 TLS 交握延遲。第二個連線數量太少,造成平行處理瓶頸。重複使用單一長期用戶端 (請參閱重複使用服務用戶端) 是正常運作連線集區的基礎,因為集區取決於要重複使用的用戶端。
若要調整每個端點的並行連線數,請在用戶端組態上設定 MaxConnectionsPerServer 屬性。當此屬性為 null(預設值) 時,會套用基礎HttpClientHandler預設值,這實際上在現代 .NET 上不受限制。只有當對相同端點的許多並行請求在連線上遇到瓶頸時,才會引發此錯誤。良好的起點是每個端點預期的並行請求尖峰數量。將其設定為遠高於工作負載需要浪費通訊端,而不會改善輸送量。
using Amazon.S3; var config = new AmazonS3Config { MaxConnectionsPerServer = 50 }; var s3Client = new AmazonS3Client(config);
如果您已使用相依性插入,請透過 設定您的用戶端AWSSDK.Extensions.NETCore.Setup。當您使用 DI 或註冊數個服務用戶端時,建議使用此方法。它集中了組態,並使用戶端易於注入和測試。您可以從應用程式的組態設定組態值,而不是在程式碼中設定。如需詳細資訊,請參閱AWSSDK.Extensions.NETCore.Setup 和 IConfiguration。
最後,將自己的平行處理綁定,這樣您就不會啟動比連線限制允許的更多並行操作。例如,大小符合您連線限制SemaphoreSlim的閘道呼叫:
var throttle = new SemaphoreSlim(50); // match MaxConnectionsPerServer await throttle.WaitAsync(token); try { await s3Client.GetObjectAsync(request, token); } finally { throttle.Release(); }
設定逾時和重試
逾時和重試對感知效能有直接影響。值Timeout太高可讓停滯的請求區塊長時間。當服務已傳回限流錯誤時,積極的重試政策會新增更多請求,並可能使限流惡化。選擇符合您應用程式延遲與失敗容錯能力的重試政策,並讓真正的例外狀況傳播,而不是以遮罩懸置的方式重試。
注意
Timeout 屬性不會影響非同步呼叫。如果您使用的是非同步呼叫,請針對逾時使用 CancellationToken 參數改為參閱 。
如需重試模式、 MaxErrorRetry和 Timeout 屬性 (ReadWriteTimeout 僅適用於 .NET Framework) 的詳細資訊,以及如何設定這些模式的範例,請參閱 重試和逾時。
最佳化串流和大型物件傳輸 (Amazon S3)
若要上傳和下載大型物件或多個物件,請使用 Amazon.S3.Transfer 命名空間中的 TransferUtility 類別。它使用分段傳輸並行上傳和下載,並為您管理串流、組件和連線。這比單一串流傳輸更快,而且是移動大型物件的建議方式。
-
平行分段下載。舊版 SDK 會將物件下載為單一串流,而不是平行下載組件。從 4.0.17
AWSSDK.S3版開始, 會透過DownloadWithResponseAsync、OpenStreamWithResponseAsync和DownloadDirectoryWithResponseAsync方法TransferUtility提供分段 (平行) 下載。當您使用 時OpenStreamWithResponseAsync,當您使用傳回的串流時,物件的組件會緩衝在記憶體中。使用 TransferUtilityOpenStreamRequest 的MaxInMemoryParts屬性控制緩衝的組件數量。對於大多數傳輸,偏好TransferUtility。只有在您需要特定範圍或自訂平行處理方案時,才能使用 GetObjectRequestByteRange屬性自行下載位元組範圍。如需詳細資訊,請參閱《 開發人員工具部落格》中的 AWS 介紹適用於 .NET Transfer Manager 的 AWS SDK 的分段下載支援。 -
上傳的內容長度。Amazon S3
PUT需要已知的內容長度,而且 SDK 預設會計算請求內文上的檢查總和。當長度已知且可搜尋串流時,軟體開發套件可以執行此操作,而不會緩衝記憶體中的整個物件。會視需要緩衝,為您TransferUtility處理不可查看的串流。如果您PutObjectAsync直接呼叫不可查看的串流 (例如 中的原始請求內文ASP.NET Core),則請求可能會失敗。提供可尋求的串流、明確設定內容長度,或設定 SDK 計算檢查總和的方式。如需詳細資訊,請參閱《 AWS SDKs和工具參考指南》中的資料完整性保護。 -
部分大小。Amazon S3 每次分段上傳最多允許 10,000 個組件。當總長度已知時,開發套件會自動計算保持在此限制內的部分大小。您主要需要
PartSize為長度無法事先得知的串流自行設定。否則,小型預設組件可能會超過非常大型上傳的限制。較大的組件大小也會降低每個組件的額外負荷,而成本是每個組件的更多記憶體。
診斷效能問題
當您調查緩慢、懸置或明顯洩漏時,下列訊號可協助您快速找到原因:
-
處於
CLOSE_WAIT或TIME_WAIT狀態的通訊端數量不斷增加 (以 可見netstat) 是未處置回應的指紋,或每個請求建立和捨棄的用戶端的指紋。請參閱 處置回應和串流 和 重複使用服務用戶端。 -
啟用請求指標和回應記錄,以確認請求確實正在分派,並測量延遲。在建立服務用戶端之前,在 AWSConfigs 上設定 LoggingConfig 物件的屬性。用戶端會擷取設定,例如建構
LogMetrics時。using Amazon; AWSConfigs.LoggingConfig.LogMetrics = true; AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.OnError; -
調查記憶體時,請區分整個程序的記憶體與受管堆積。高但穩定的程序記憶體通常是 GC 保留回收記憶體,而不是洩漏。請參閱設定垃圾回收。