Správa souběžnosti ve službě Blob Storage

Moderní aplikace často mají více uživatelů, kteří si prohlížejí a aktualizují data současně. Vývojáři aplikací musí pečlivě zvážit, jak koncovým uživatelům poskytnout předvídatelné prostředí, zejména ve scénářích, kdy může stejná data aktualizovat více uživatelů. Vývojáři obvykle zvažují tři hlavní strategie souběžnosti dat:

  • Optimistická souběžnost: Aplikace provádějící aktualizaci v rámci aktualizace určí, jestli se data od posledního čtení těchto dat změnila. Pokud například dva uživatelé prohlížející si stránku wikiwebu aktualizují, musí platforma wikiwebu zajistit, aby druhá aktualizace nepřepsala první aktualizaci. Musí také zajistit, aby oba uživatelé pochopili, jestli byla aktualizace úspěšná. Tato strategie se nejčastěji používá ve webových aplikacích.

  • Pesimistická souběžnost: Aplikace, která chce provést aktualizaci, převezme zámek u objektu, který ostatním uživatelům brání v aktualizaci dat, dokud nebude zámek uvolněn. Například ve scénáři replikace primárních a sekundárních dat, ve kterém provádí aktualizace pouze primární data, primární server obvykle drží výhradní zámek dat po delší dobu, aby zajistil, že je nikdo jiný nebude moct aktualizovat.

  • Poslední zapisovač vyhrává: Přístup, který umožňuje, aby operace aktualizace pokračovaly, aniž by se napřed řídilo, jestli data od jejich čtení aktualizovala jiná aplikace. Tento přístup se obvykle používá, když jsou data rozdělená tak, aby více uživatelů nepřistupovala ke stejným datům současně. Může být také užitečný v případě, že se zpracovávají krátkodobé datové proudy.

Azure Storage podporuje všechny tři strategie, i když se vyznačuje schopností poskytovat plnou podporu optimistické a pesimistické souběžnosti. Služba Azure Storage byla navržena tak, aby zahrnovala model silné konzistence, který zaručuje, že po operaci vložení nebo aktualizace služba vrátí nejnovější aktualizaci.

Kromě výběru vhodné strategie souběžnosti by vývojáři měli také vědět, jak platforma úložiště izoluje změny, zejména změny stejného objektu napříč transakcemi. Azure Storage pomocí izolace snímků umožňuje operace čtení souběžně s operacemi zápisu v rámci jednoho oddílu. Izolace snímků zaručuje, že všechny operace čtení vrátí konzistentní snímek dat, i když probíhají aktualizace.

Ke správě přístupu k objektům blob a kontejnerů můžete použít optimistické nebo pesimistické modely souběžnosti. Pokud strategii explicitně nezadáte, ve výchozím nastavení vyhraje poslední zapisovatel.

Optimistická metoda souběžného zpracování

Azure Storage přiřadí každému uloženému objektu identifikátor. Tento identifikátor se aktualizuje při každém provedení operace zápisu na objektu. Identifikátor se vrátí klientovi jako součást odpovědi HTTP GET v hlavičce ETag definované protokolem HTTP.

Klient, který provádí aktualizaci, může odeslat původní značku ETag společně s podmíněnou hlavičkou, aby se zajistilo, že k aktualizaci dojde pouze v případě splnění určité podmínky. Pokud je například zadána hlavička If-Match , Azure Storage ověří, že hodnota značky ETag zadaná v žádosti o aktualizaci je stejná jako značka ETag pro objekt, který se aktualizuje. Další informace o podmíněných hlavičkách najdete v tématu Určení podmíněných hlaviček pro operace služby Blob Service.

Osnova tohoto procesu je následující:

  1. Načtení objektu blob ze služby Azure Storage Odpověď obsahuje hodnotu hlavičky HTTP ETag, která identifikuje aktuální verzi objektu.
  2. Při aktualizaci objektu blob zahrňte hodnotu ETag, kterou jste obdrželi v kroku 1, do podmíněné hlavičky žádosti o zápis if-match . Azure Storage porovná hodnotu značky ETag v požadavku s aktuální hodnotou značky ETag objektu blob.
  3. Pokud se aktuální hodnota značky ETag objektu blob liší od hodnoty ETag zadané v podmíněné hlavičce If-Match zadané v požadavku, vrátí Azure Storage stavový kód HTTP 412 (Předběžná podmínka selhala). Tato chyba značí klientovi, že od prvního načtení objektu blob aktualizoval jiný proces. Klient by měl objekt blob znovu načíst, aby získal aktualizovaný obsah a vlastnosti.
  4. Pokud je aktuální hodnota značky ETag objektu blob stejná jako značka ETag v podmíněné hlavičce If-Match v požadavku, Azure Storage provede požadovanou operaci a aktualizuje aktuální hodnotu značky ETag objektu blob.

Následující příklady kódu ukazují, jak vytvořit podmínku if-match pro žádost o zápis, která kontroluje hodnotu ETag objektu blob. Azure Storage vyhodnotí, jestli je aktuální značka ETag objektu blob stejná jako značka ETag zadanou v požadavku, a provede operaci zápisu pouze v případě, že se tyto dvě hodnoty ETag shodují. Pokud objekt blob mezitím aktualizoval jiný proces, azure Storage vrátí stavovou zprávu HTTP 412 (Předběžná podmínka selhala).

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);
}

Azure Storage podporuje také další podmíněné hlavičky, například If-Modified-Since, If-Unmodified-Since a If-None-Match. Další informace najdete v tématu Určení podmíněných hlaviček pro operace služby Blob Service.

Pesimistická souběžnost objektů blob

Pokud chcete objekt blob uzamknout pro výhradní použití, můžete na něj získat zapůjčení. Při získání zapůjčení určíte dobu trvání zapůjčení. Konečné zapůjčení může být platné od 15 do 60 sekund. Zapůjčení může být také nekonečné, což představuje výhradní zámek. Prodloužením platnosti konečného zapůjčení ho můžete prodloužit, a až ho dokončíte, můžete ho uvolnit. Azure Storage po vypršení jejich platnosti automaticky uvolní konečná zapůjčení.

Zapůjčení umožňuje podporu různých strategií synchronizace, včetně výhradních operací zápisu/sdíleného čtení, výhradních operací zápisu/výhradního čtení a sdílených operací zápisu/výhradního čtení. Pokud existuje zapůjčení, Azure Storage vynucuje pro držitele zapůjčení výhradní přístup k operacím zápisu. Zajištění exkluzivity pro operace čtení však vyžaduje, aby vývojář zajistil, že všechny klientské aplikace používají ID zapůjčení a že platné ID zapůjčení má vždy jen jeden klient. Operace čtení, které neobsahují ID zapůjčení, mají za následek sdílené čtení.

Následující příklady kódu ukazují, jak získat výhradní zapůjčení objektu blob, aktualizovat obsah objektu blob zadáním ID zapůjčení a pak zapůjčení uvolnit. Pokud je zapůjčení aktivní a ID zapůjčení není k dispozici v žádosti o zápis, operace zápisu selže s kódem chyby 412 (Předběžná podmínka selhala).

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();
    }
}

Pesimistická souběžnost kontejnerů

Zapůjčení kontejnerů umožňuje stejné strategie synchronizace, které jsou podporované pro objekty blob, včetně výhradního zápisu/sdíleného čtení, výhradního zápisu/výhradního čtení a sdíleného zápisu/výhradního čtení. U kontejnerů se však výhradní zámek vynucuje pouze při operacích odstranění. Pokud chcete odstranit kontejner s aktivním zapůjčením, musí klient do žádosti o odstranění zahrnout ID aktivního zapůjčení. Všechny ostatní operace kontejneru jsou u zapůjčeného kontejneru úspěšné bez ID zapůjčení.

Další kroky

Zdroje informací

Související ukázky kódu s využitím zastaralých sad SDK .NET verze 11.x najdete v tématu Ukázky kódu pomocí rozhraní .NET verze 11.x.