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

Moderní aplikace často mají více uživatelů, kteří si data prohlížejí a aktualizují 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 pro scénáře, ve kterých může více uživatelů aktualizovat stejná data. Existují tři hlavní strategie souběžnosti dat, které vývojáři obvykle zvažují:

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

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

  • Poslední zapisovač vyhraje: Přístup, který umožňuje, aby operace aktualizace pokračovaly, aniž by bylo nutné určit, zda jiná aplikace aktualizovala data od jeho čtení. Tento přístup se obvykle používá, když jsou data rozdělená tak, aby k stejným datům současně nepřistupoval více uživatelů. Může být také užitečné, když se zpracovávají krátkodobé datové proudy.

Azure Storage podporuje všechny tři strategie, i když je výrazný ve své schopnosti poskytovat plnou podporu optimistické a pesimistické souběžnosti. Služba Azure Storage byla navržena tak, aby přijala model silné konzistence, který zaručuje, že po provedení operace vložení nebo aktualizace vrátí následné operace čtení nebo seznamu 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 využívá izolaci snímků k tomu, aby umožňovala operace čtení souběžně s operacemi zápisu v rámci jednoho oddílu. Izolace snímků zaručuje, že všechny operace čtení vrací konzistentní snímek dat i v době, kdy dochází k aktualizacím.

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

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

Azure Storage přiřadí identifikátor každému uloženému objektu. Tento identifikátor se aktualizuje při každé operaci zápisu u objektu. Identifikátor se klientovi vrátí 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 spolu s podmíněnou hlavičkou, aby se zajistilo, že aktualizace proběhne 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 požadavku aktualizace 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.

Přehled 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 značky ETag, kterou jste obdrželi v kroku 1, do podmíněné hlavičky žádosti o zápis. Azure Storage porovnává 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 se nezdařila). Tato chyba značí klientovi, že od prvního načtení klienta došlo k aktualizaci objektu blob jiným procesem. Klient by měl znovu načíst objekt blob, aby získal aktualizovaný obsah a vlastnosti.
  4. Pokud je aktuální hodnota značky ETag objektu blob stejná jako ETag v hlavičce podmíněné shody 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 v požadavku na zápis vytvořit podmínku If-Match , která kontroluje hodnotu značky ETag objektu blob. Azure Storage vyhodnotí, jestli je aktuální značka ETag objektu blob stejná jako ETag poskytnutá v požadavku, a provádí operaci zápisu pouze v případě, že se shodují dvě hodnoty značky ETag. Pokud mezitím došlo k aktualizaci objektu blob jiným procesem, azure Storage vrátí stavovou zprávu HTTP 412 (Předběžná podmínka se nezdařila).

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 také podporuje další podmíněné hlavičky, včetně chyb 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 pro objekty blob

Pokud chcete objekt blob uzamknout pro výhradní použití, můžete k němu získat zapůjčení. Při získání zapůjčení zadá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ž se rovná výhradnímu zámku. Můžete prodloužit konečný zapůjčení, abyste ho rozšířili, a zapůjčení můžete uvolnit, až s ním skončíte. Azure Storage automaticky uvolní konečné zapůjčení, když vyprší jejich platnost.

Zapůjčení umožňuje podporu různých strategií synchronizace, včetně operací výhradního 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 výhradní přístup k operacím zápisu pro vlastníka zapůjčení. Zajištění exkluze operací č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 jenom 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 poskytnutím ID zapůjčení a pak uvolnit zapůjčení. Pokud je zapůjčení aktivní a ID zapůjčení není zadané v žádosti o zápis, operace zápisu selže s kódem chyby 412 (Předběžná podmínka se nezdařila).

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 pro kontejnery

Zapůjčení kontejnerů umožňuje stejné strategie synchronizace, které jsou podporovány 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í. Pro kontejnery se ale výhradní zámek vynucuje jenom 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 aktivní ID zapůjčení. Všechny ostatní operace kontejneru jsou úspěšné u zapůjčeného kontejneru bez ID zapůjčení.

Další kroky

Zdroje informací

Související ukázky kódu s využitím zastaralých sad .NET verze 11.x najdete v ukázkách kódu pomocí .NET verze 11.x.