分散式系統已經存在一段時間,並且在設計這些系統時已經建立了一些知名的模式。今天,我們將討論其中一個流行的模式:「鎖」。
簡單來說,鎖是進程獲得對資源的獨佔訪問權以執行某個動作的方式。例如,假設在一個存儲帳戶中有一堆 Blob,您需要一個服務實例來處理每個 Blob,以避免重複處理。這樣做的方法是對 Blob 獲取鎖,完成處理後再釋放鎖。然而,如果在釋放鎖之前進程失敗,例如因為進程崩潰或網絡分區,則可能會出現潛在問題,導致資源被無限期鎖定。這可能導致死鎖和資源競爭。
為了防止死鎖,可以採用的一種策略是使用超時或租約基礎的鎖。
超時鎖
- 在這種情況下,進程請求鎖的時間是預先定義的超時。如果在超時之前未釋放鎖,系統將確保鎖最終被釋放。
租約鎖
-
對於基於租約的鎖定,提供了一個 續租 API 以及超時機制。持有鎖的進程必須在租約到期之前調用此 API 以保持對資源的獨占訪問。如果進程未能及時續租,鎖將自動釋放,允許其他進程獲取它。
超時和基於租約的鎖定的優缺點
Pros | Cons | |
---|---|---|
基於超時的鎖 | 實現簡單 | 需要仔細選擇超時時間 |
防止永久鎖定 | 如果處理未完成,則無法續租 | |
基於租約的鎖 | 降低提前 鎖過期的風險 |
需要租約續租的機制 |
進程可以持續請求租約直到工作完成。 |
上述兩種策略都是在 分佈式系統 中快速從進程失敗或網絡分區中恢復的一種方式。
租約鎖定策略與 Azure 儲存
讓我們來看看如何使用租約鎖定策略與 Azure 儲存。這也涵蓋了超時鎖定策略。
步驟 1: 導入儲存 Blob Nuget
“12.23.0” 是撰寫本文時的最新版本。最新版本可以在 Azure 儲存 Blob 找到。
XML
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
</ItemGroup>
步驟 2: 獲取租約
以下是獲取租約的代碼。
C#
public async Task<string> TryAcquireLeaseAsync(string blobName, TimeSpan durationInSeconds, string leaseId = default)
{
BlobContainerClient blobContainerClient = new BlobContainerClient(new Uri($"https://{storageName}.blob.core.windows.net/processors"), tokenCredential, blobClientOptions);
BlobLeaseClient blobLeaseClient = blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
try
{
BlobLease lease = await blobLeaseClient.AcquireAsync(durationInSeconds).ConfigureAwait(false);
return lease.LeaseId;
}
catch (RequestFailedException ex) when (ex.Status == 409)
{
return default;
}
}
- 首先,我們創建一個 Blob 容器客戶端 並檢索我們想要獲取租約的特定 blob 的 Blob 客戶端。
- 其次,”Acquire Async” 方法嘗試在特定持續時間內獲取租約。如果獲取成功,則返回租約 ID,否則會拋出 409(衝突的狀態碼)。
- “Acquire Async” 是這裡的關鍵方法。其餘代碼可以根據您的需求進行調整/編輯。
步驟 3: 續租
- “Renew Async” 是在 Storage .NET SDK 中用於續租的的方法。
- 如果續租不成功,則會拋出異常並附上失敗原因。
C#
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.RenewAsync().ConfigureAwait(false);
}
步驟 4: 協調獲取和續租方法
- 最初,我們呼叫「嘗試異步獲取租約」以從步驟 2 獲取租約識別碼。一旦成功,將啟動一個背景任務,每隔 X 秒呼叫步驟 3 的「異步續約租約」。請確保在超時和呼叫續約租約方法之間有足夠的時間。
C#
string leaseId = await this.blobReadProcessor.TryAcquireLeaseAsync(blobName, TimeSpan.FromSeconds(60)).ConfigureAwait(false);
Task leaseRenwerTask = this.taskFactory.StartNew(
async () =>
{
while (leaseId != default && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(renewLeaseMillis).ConfigureAwait(false);
await this.blobReadProcessor.RenewLeaseAsync(blobName, leaseId).ConfigureAwait(false);
}
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
- 取消令牌 用於 優雅地停止租約續約任務 當不再需要時。
步驟 5: 取消租約續約
- 當呼叫「取消異步」方法時,步驟 4 中的「是否請求取消」變為真,因此我們不再進入 while 循環並請求續約租約。
C#
await cancellationTokenSource.CancelAsync().ConfigureAwait(false);
await leaseRenwerTask.WaitAsync(Timeout.InfiniteTimeSpan).ConfigureAwait(false);
步驟 6: 釋放租約
最後,釋放租約只需呼叫「釋放異步」方法。
C#
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.ReleaseAsync().ConfigureAwait(false);
}
結論
鎖是分散式系統中獲得對資源的獨佔訪問的基本模式之一。在處理它們時,必須記住陷阱,以確保操作的順利進行。通過使用 Azure 儲存,我們可以實現這些高效的鎖定機制,防止無限阻塞,同時提供鎖定維護的彈性。
Source:
https://dzone.com/articles/locks-in-distributed-systems-timeout-lease-based