Share via


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íti az adott lapot, akkor a wikiplatformnak gondoskodnia kell arról, hogy a második frissítés ne írja felül az első frissítést. Arról is gondoskodnia kell, hogy mindkét felhasználó tisztában legyen azzal, hogy a frissítés sikeres volt-e. Ezt a stratégiát leggyakrabban webalkalmazásokban használják.

  • Pesszimista egyidejűség: A frissítést végrehajtani kívánó alkalmazások zárolnak 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 olyan 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.

  • 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 ugyanazokhoz az 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 körű támogatást nyújt az optimista és pesszimista egyidejűséghez. Az Azure Storage-t úgy tervezték, 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 is tisztában kell lenniük azzal, hogy a tároló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 egy adott partíción belüli írási műveletekkel egyidejűleg. 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 frissítések közben is.

Választhat, hogy optimista vagy pesszimista egyidejűségi modelleket használ 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 egyidejűsé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 hajtanak végre egy objektumon. Az azonosító a HTTP-protokoll által meghatározott ETag fejlécben található HTTP GET-válasz részeként lesz visszaadva az ügyfélnek.

A frissítést végző ügyfél elküldheti az eredeti ETaget egy feltételes fejlécmel együtt, hogy a frissítés csak akkor történjen meg, ha teljesült egy adott feltétel. 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-címével. További információ a feltételes fejlécekről: Feltételes fejlécek megadása Blob service-műveletekhez.

Ennek a folyamatnak a körvonala a következő:

  1. Blob lekérése az Azure Storage-ból. A válasz tartalmaz egy HTTP ETag Header értéket, amely azonosítja az objektum aktuális verzióját.
  2. 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.
  3. 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őfeltétel sikertelen). Ez a hiba azt jelzi az ügyfélnek, hogy egy másik folyamat frissítette a blobot az ügyfél első lekérése óta. Az ügyfélnek újra le kell kérnie a blobot a frissített tartalom és tulajdonságok lekéréséhez.
  4. 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ódok bemutatják, 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-á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

Ha kizárólagos használatra szeretne zárolni egy blobot, bérletet szerezhet rajta. A bérlet beszerzésekor meg kell adnia a bérlet időtartamát. A véges bérlet 15 és 60 másodperc között lehet érvényes. A bérlet is végtelen lehet, ami kizárólagos zárolást jelent. Megújíthat egy véges bérletet, hogy meghosszabbítsa azt, é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övetel meg 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. A bérletazonosítót nem tartalmazó olvasási műveletek 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, az írási művelet a 412-s hibakóddal meghiúsul (az előfeltétel 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 tárolókhoz

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 érvényesí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-verziójú 11.x SDK-kkal kapcsolatos kódmintákért lásd: Kódminták a .NET 11.x-es verziójával.