Egyidejűség kezelése a Blob Storage-ban
A modern alkalmazásokban gyakran több felhasználó tekinti meg és frissíti egyszerre az adatokat. Az alkalmazásfejlesztőknek alaposan át kell gondolniuk, hogyan biztosíthatnak kiszámítható élményt a végfelhasználóknak, különösen olyan esetekben, amikor több felhasználó is frissítheti ugyanazokat az adatokat. A fejlesztők általában három fő adat-egyidejűségi stratégiát alkalmaznak:
Optimista egyidejűség: A frissítést végrehajtó alkalmazások a frissítés részeként meghatározzák, hogy az adatok megváltoztak-e az alkalmazás legutóbbi olvasása óta. Ha például egy wikilapot megtekintő két felhasználó frissít erre a lapra, akkor a wikiplatformnak biztosítania kell, hogy a második frissítés ne írja felül az első frissítést. Azt is biztosítania kell, hogy mindkét felhasználó megértse, hogy a frissítés sikeres volt-e. Ezt a stratégiát leggyakrabban webalkalmazásokban használják.
Pesszimista egyidejűség: Egy frissítést végrehajtani kívánt alkalmazás zárol egy objektumot, amely megakadályozza, hogy a többi felhasználó frissítse az adatokat a zárolás feloldásáig. Például egy elsődleges/másodlagos adatreplikációs forgatókönyvben, amelyben csak az elsődleges hajt végre frissítéseket, az elsődleges általában hosszabb ideig kizárólagos zárolást tart az adatokon, hogy senki más ne tudja frissíteni azokat.
Az utolsó író nyer: Olyan megközelítés, amely lehetővé teszi a frissítési műveletek folytatását anélkül, hogy először megállapítanák, hogy egy másik alkalmazás frissítette-e az adatokat az olvasás óta. Ezt a módszert általában akkor használják, ha az adatok particionálása oly módon történik, hogy több felhasználó ne fér hozzá egyszerre ugyanazon adatokhoz. Az is hasznos lehet, ha rövid élettartamú adatfolyamokat dolgoznak fel.
Az Azure Storage mindhárom stratégiát támogatja, bár megkülönböztető képessége, hogy teljes mértékben támogatja az optimista és pesszimista egyidejűséget. Az Azure Storage úgy lett kialakítva, hogy egy erős konzisztenciamodellt használjon, amely garantálja, hogy miután a szolgáltatás végrehajt egy beszúrási vagy frissítési műveletet, a későbbi olvasási vagy listaműveletek a legújabb frissítést adják vissza.
A megfelelő egyidejűségi stratégia kiválasztása mellett a fejlesztőknek tisztában kell lenniük azzal is, hogy a tárolási platform hogyan elkülöníti a változásokat, különösen az ugyanazon objektum módosításait a tranzakciók során. Az Azure Storage pillanatkép-elkülönítéssel teszi lehetővé az olvasási műveleteket egyidejűleg az írási műveletekkel egy partíción belül. A pillanatképek elkülönítése garantálja, hogy az összes olvasási művelet egységes pillanatképet ad vissza az adatokról, még a frissítések végrehajtásakor is.
Optimista vagy pesszimista egyidejűségi modelleket is használhat a blobokhoz és tárolókhoz való hozzáférés kezeléséhez. Ha nem ad meg explicit módon stratégiát, akkor alapértelmezés szerint az utolsó író nyer.
Optimista párhuzamosság
Az Azure Storage minden tárolt objektumhoz hozzárendel egy azonosítót. Ez az azonosító minden alkalommal frissül, amikor írási műveletet hajt végre egy objektumon. Az azonosító a HTTP protokoll által definiált ETag fejlécben található HTTP GET-válasz részeként lesz visszaadva az ügyfélnek.
A frissítést végrehajtó ügyfél elküldheti az eredeti ETaget egy feltételes fejléccel együtt, hogy a frissítés csak egy bizonyos feltétel teljesülése esetén történjen. Ha például az If-Match fejléc van megadva, az Azure Storage ellenőrzi, hogy a frissítési kérelemben megadott ETag értéke megegyezik-e a frissíteni kívánt objektum ETag azonosítójának értékével. A feltételes fejlécekről további információt a Blob service-műveletek feltételes fejléceinek megadása című témakörben talál.
Ennek a folyamatnak a vázlata a következő:
- Blob lekérése az Azure Storage-ból. A válasz tartalmaz egy HTTP ETag-fejlécértéket, amely azonosítja az objektum aktuális verzióját.
- A blob frissítésekor adja meg az 1. lépésben kapott ETag értéket az írási kérelem If-Match feltételes fejlécében. Az Azure Storage összehasonlítja a kérelem ETag értékét a blob aktuális ETag-értékével.
- Ha a blob aktuális ETag értéke eltér a kérelemben megadott If-Match feltételes fejlécben megadott ETag értéktől, akkor az Azure Storage a 412-s HTTP-állapotkódot adja vissza (az előkondicionálás sikertelen). Ez a hiba azt jelzi az ügyfélnek, hogy az ügyfél első lekérése óta egy másik folyamat frissítette a blobot. Az ügyfélnek újra le kell kérnie a blobot a frissített tartalom és tulajdonságok lekéréséhez.
- Ha a blob aktuális ETag értéke megegyezik a kérelem If-Match feltételes fejlécében szereplő ETag verzióval, az Azure Storage végrehajtja a kért műveletet, és frissíti a blob aktuális ETag-értékét.
Az alábbi példakód bemutatja, hogyan hozhat létre if-match feltételt az írási kérelemben, amely ellenőrzi a blob ETag-értékét. Az Azure Storage kiértékeli, hogy a blob aktuális ETagje megegyezik-e a kérelemben megadott ETagtel, és csak akkor hajtja végre az írási műveletet, ha a két ETag-érték megegyezik. Ha egy másik folyamat időközben frissítette a blobot, akkor az Azure Storage EGY HTTP 412 (Az előkondicionálás sikertelen) állapotüzenetet ad vissza.
private static async Task DemonstrateOptimisticConcurrencyBlob(BlobClient blobClient)
{
Console.WriteLine("Demonstrate optimistic concurrency");
try
{
// Download a blob
Response<BlobDownloadResult> response = await blobClient.DownloadContentAsync();
BlobDownloadResult downloadResult = response.Value;
string blobContents = downloadResult.Content.ToString();
ETag originalETag = downloadResult.Details.ETag;
Console.WriteLine("Blob ETag = {0}", originalETag);
// This function simulates an external change to the blob after we've fetched it
// The external change updates the contents of the blob and the ETag value
await SimulateExternalBlobChangesAsync(blobClient);
// Now try to update the blob using the original ETag value
string blobContentsUpdate2 = $"{blobContents} Update 2. If-Match condition set to original ETag.";
// Set the If-Match condition to the original ETag
BlobUploadOptions blobUploadOptions = new()
{
Conditions = new BlobRequestConditions()
{
IfMatch = originalETag
}
};
// This call should fail with error code 412 (Precondition Failed)
BlobContentInfo blobContentInfo =
await blobClient.UploadAsync(BinaryData.FromString(blobContentsUpdate2), blobUploadOptions);
}
catch (RequestFailedException e) when (e.Status == (int)HttpStatusCode.PreconditionFailed)
{
Console.WriteLine(
@"Blob's ETag does not match ETag provided. Fetch the blob to get updated contents and properties.");
}
}
private static async Task SimulateExternalBlobChangesAsync(BlobClient blobClient)
{
// Simulates an external change to the blob for this example
// Download a blob
Response<BlobDownloadResult> response = await blobClient.DownloadContentAsync();
BlobDownloadResult downloadResult = response.Value;
string blobContents = downloadResult.Content.ToString();
// Update the existing block blob contents
// No ETag condition is provided, so original blob is overwritten and ETag is updated
string blobContentsUpdate1 = $"{blobContents} Update 1";
BlobContentInfo blobContentInfo =
await blobClient.UploadAsync(BinaryData.FromString(blobContentsUpdate1), overwrite: true);
Console.WriteLine("Blob update. Updated ETag = {0}", blobContentInfo.ETag);
}
Az Azure Storage más feltételes fejléceket is támogat, például if-modified-Since, If-Unmodified-Since és If-None-Match. További információ: Feltételes fejlécek megadása Blob Service-műveletekhez.
Pesszimista egyidejűség a blobok esetében
A blob kizárólagos használatra való zárolásához bérletet szerezhet rajta. A bérlet beszerzésekor meg kell adnia a bérlet időtartamát. A véges bérlet 15–60 másodperc között lehet érvényes. A bérlet is végtelen lehet, ami kizárólagos zárolást jelent. A hosszabbításhoz megújíthat egy véges bérletet, és a bérletet felengedheti, ha végzett vele. Az Azure Storage automatikusan kiadja a véges bérleteket, amikor lejárnak.
A bérletek lehetővé teszik a különböző szinkronizálási stratégiák támogatását, beleértve a kizárólagos írási/megosztott olvasási műveleteket, a kizárólagos írási/kizárólagos olvasási műveleteket és a megosztott írási/kizárólagos olvasási műveleteket. Ha létezik bérlet, az Azure Storage kizárólagos hozzáférést kényszerít ki a bérlettulajdonos írási műveleteihez. Az olvasási műveletek kizárólagosságának biztosításához azonban a fejlesztőnek gondoskodnia kell arról, hogy minden ügyfélalkalmazás bérletazonosítót használjon, és hogy egyszerre csak egy ügyfél rendelkezik érvényes bérletazonosítóval. Az olyan olvasási műveletek, amelyek nem tartalmaznak bérletazonosítót, megosztott olvasásokat eredményeznek.
Az alábbi példakód bemutatja, hogyan szerezhet be kizárólagos bérletet egy blobon, hogyan frissítheti a blob tartalmát a bérletazonosító megadásával, majd hogyan oldhatja fel a bérletet. Ha a bérlet aktív, és a bérletazonosító nincs megadva egy írási kérelemben, akkor az írási művelet a 412-s hibakóddal meghiúsul (az előkondicionálás sikertelen).
public static async Task DemonstratePessimisticConcurrencyBlob(BlobClient blobClient)
{
Console.WriteLine("Demonstrate pessimistic concurrency");
BlobContainerClient containerClient = blobClient.GetParentBlobContainerClient();
BlobLeaseClient blobLeaseClient = blobClient.GetBlobLeaseClient();
try
{
// Create the container if it does not exist.
await containerClient.CreateIfNotExistsAsync();
// Upload text to a blob.
string blobContents1 = "First update. Overwrite blob if it exists.";
byte[] byteArray = Encoding.ASCII.GetBytes(blobContents1);
using (MemoryStream stream = new MemoryStream(byteArray))
{
BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, overwrite: true);
}
// Acquire a lease on the blob.
BlobLease blobLease = await blobLeaseClient.AcquireAsync(TimeSpan.FromSeconds(15));
Console.WriteLine("Blob lease acquired. LeaseId = {0}", blobLease.LeaseId);
// Set the request condition to include the lease ID.
BlobUploadOptions blobUploadOptions = new BlobUploadOptions()
{
Conditions = new BlobRequestConditions()
{
LeaseId = blobLease.LeaseId
}
};
// Write to the blob again, providing the lease ID on the request.
// The lease ID was provided, so this call should succeed.
string blobContents2 = "Second update. Lease ID provided on request.";
byteArray = Encoding.ASCII.GetBytes(blobContents2);
using (MemoryStream stream = new MemoryStream(byteArray))
{
BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, blobUploadOptions);
}
// This code simulates an update by another client.
// The lease ID is not provided, so this call fails.
string blobContents3 = "Third update. No lease ID provided.";
byteArray = Encoding.ASCII.GetBytes(blobContents3);
using (MemoryStream stream = new MemoryStream(byteArray))
{
// This call should fail with error code 412 (Precondition Failed).
BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream);
}
}
catch (RequestFailedException e)
{
if (e.Status == (int)HttpStatusCode.PreconditionFailed)
{
Console.WriteLine(
@"Precondition failure as expected. The lease ID was not provided.");
}
else
{
Console.WriteLine(e.Message);
throw;
}
}
finally
{
await blobLeaseClient.ReleaseAsync();
}
}
Pesszimista egyidejűség a tárolók esetében
A tárolók bérletei ugyanazokat a szinkronizálási stratégiákat teszik lehetővé, amelyeket a blobok támogatnak, beleértve a kizárólagos írási/megosztott olvasási, kizárólagos írási/kizárólagos olvasási és megosztott írási/kizárólagos olvasási stratégiákat. Tárolók esetében azonban a kizárólagos zárolás csak törlési műveletek esetén lesz kényszerítve. Aktív bérlettel rendelkező tároló törléséhez az ügyfélnek tartalmaznia kell az aktív bérlet azonosítóját a törlési kérelemmel. Minden más tárolóművelet sikeres egy bérelt tárolón a bérletazonosító nélkül.
Következő lépések
Források
Az elavult .NET 11.x SDK-kkal kapcsolatos kódmintákért lásd a .NET 11.x verzióját használó kódmintákat.