Hantera samtidighet i Blob Storage

Moderna program har ofta flera användare som visar och uppdaterar data samtidigt. Programutvecklare måste tänka igenom hur de ska ge slutanvändarna en förutsägbar upplevelse, särskilt i scenarier där flera användare kan uppdatera samma data. Det finns tre huvudsakliga strategier för samtidighet av data som utvecklare vanligtvis överväger:

  • Optimistisk samtidighet: Ett program som utför en uppdatering avgör, som en del av uppdateringen, om data har ändrats sedan programmet senast läste dessa data. Om två användare som visar en wiki-sida till exempel gör en uppdatering av sidan måste wiki-plattformen se till att den andra uppdateringen inte skriver över den första uppdateringen. Den måste också se till att båda användarna förstår om deras uppdatering lyckades. Den här strategin används oftast i webbprogram.

  • Pessimistisk samtidighet: Ett program som vill utföra en uppdatering låser ett objekt som hindrar andra användare från att uppdatera data tills låset släpps. I ett primärt/sekundärt datareplikeringsscenario där endast den primära utför uppdateringar har den primära vanligtvis ett exklusivt lås på data under en längre tid för att säkerställa att ingen annan kan uppdatera dem.

  • Senaste skrivaren vinner: En metod som gör att uppdateringsåtgärder kan fortsätta utan att först avgöra om ett annat program har uppdaterat data sedan det lästes. Den här metoden används vanligtvis när data partitioneras på ett sådant sätt att flera användare inte kommer åt samma data samtidigt. Det kan också vara användbart när kortlivade dataströmmar bearbetas.

Azure Storage stöder alla tre strategierna, även om det är utmärkande för dess förmåga att ge fullt stöd för optimistisk och pessimistisk samtidighet. Azure Storage har utformats för att omfatta en stark konsekvensmodell som garanterar att efterföljande läs- eller liståtgärder returnerar den senaste uppdateringen när tjänsten utför en infognings- eller uppdateringsåtgärd.

Förutom att välja en lämplig samtidighetsstrategi bör utvecklare också vara medvetna om hur en lagringsplattform isolerar ändringar, särskilt ändringar i samma objekt mellan transaktioner. Azure Storage använder ögonblicksbildisolering för att tillåta läsåtgärder samtidigt med skrivåtgärder inom en enda partition. Ögonblicksbildisolering garanterar att alla läsåtgärder returnerar en konsekvent ögonblicksbild av data även när uppdateringar sker.

Du kan välja att använda antingen optimistiska eller pessimistiska samtidighetsmodeller för att hantera åtkomst till blobar och containrar. Om du inte uttryckligen anger en strategi vinner den senaste skrivaren som standard.

Optimistisk samtidighet

Azure Storage tilldelar en identifierare till varje objekt som lagras. Den här identifieraren uppdateras varje gång en skrivåtgärd utförs på ett objekt. Identifieraren returneras till klienten som en del av ett HTTP GET-svar i ETag-huvudet som definieras av HTTP-protokollet.

En klient som utför en uppdatering kan skicka den ursprungliga ETag tillsammans med ett villkorsstyrt huvud för att säkerställa att en uppdatering endast sker om ett visst villkor har uppfyllts. Om till exempel If-Match-huvudet anges kontrollerar Azure Storage att värdet för ETag som anges i uppdateringsbegäran är detsamma som ETag för objektet som uppdateras. Mer information om villkorsstyrda rubriker finns i Ange villkorsstyrda rubriker för Blob Service-åtgärder.

Konturen av den här processen är följande:

  1. Hämta en blob från Azure Storage. Svaret innehåller ett HTTP ETag-huvudvärde som identifierar den aktuella versionen av objektet.
  2. När du uppdaterar bloben inkluderar du det ETag-värde som du fick i steg 1 i villkorsrubriken If-Match i skrivbegäran. Azure Storage jämför ETag-värdet i begäran med det aktuella ETag-värdet för bloben.
  3. Om blobens aktuella ETag-värde skiljer sig från det ETag-värde som anges i villkorsrubriken If-Match som anges i begäran returnerar Azure Storage HTTP-statuskod 412 (förhandsvillkoret misslyckades). Det här felet anger för klienten att en annan process har uppdaterat bloben sedan klienten först hämtade den. Klienten bör hämta bloben igen för att hämta det uppdaterade innehållet och egenskaperna.
  4. Om det aktuella ETag-värdet för bloben är samma version som ETag i villkorsrubriken If-Match i begäran, utför Azure Storage den begärda åtgärden och uppdaterar blobens aktuella ETag-värde.

Följande kodexempel visar hur du skapar ett If-Match-villkor för skrivbegäran som kontrollerar ETag-värdet för en blob. Azure Storage utvärderar om blobens aktuella ETag är samma som den ETag som angavs i begäran och utför skrivåtgärden endast om de två ETag-värdena matchar. Om en annan process har uppdaterat bloben under tiden returnerar Azure Storage ett http 412-statusmeddelande (förutsättningen misslyckades).

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 har även stöd för andra villkorsstyrda rubriker, inklusive If-Modified-Since, If-Unmodified-Since och If-None-Match. Mer information finns i Ange villkorsstyrda rubriker för Blob Service-åtgärder.

Pessimistisk samtidighet för blobar

Om du vill låsa en blob för exklusiv användning kan du skaffa ett lån på den. När du skaffar lånet anger du lånets varaktighet. Ett ändliga lån kan vara giltigt mellan 15 och 60 sekunder. Ett lån kan också vara oändligt, vilket innebär ett exklusivt lås. Du kan förnya ett ändliga lån för att utöka det och du kan frigöra lånet när du är klar med det. Azure Storage släpper automatiskt begränsade lån när de upphör att gälla.

Lån gör att olika synkroniseringsstrategier kan stödjas, inklusive exklusiva skriv-/delade läsåtgärder, exklusiva skrivåtgärder/exklusiva läsåtgärder och delade skrivåtgärder/exklusiva läsåtgärder. När det finns ett lån tillämpar Azure Storage exklusiv åtkomst till skrivåtgärder för låneinnehavaren. För att säkerställa exklusivitet för läsåtgärder måste utvecklaren dock se till att alla klientprogram använder ett låne-ID och att endast en klient i taget har ett giltigt låne-ID. Läsåtgärder som inte innehåller ett låne-ID resulterar i delade läsningar.

Följande kodexempel visar hur du hämtar ett exklusivt lån för en blob, uppdaterar innehållet i bloben genom att ange låne-ID:t och släpper sedan lånet. Om lånet är aktivt och låne-ID:t inte anges i en skrivbegäran misslyckas skrivåtgärden med felkoden 412 (förhandsvillkoret misslyckades).

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

Pessimistisk samtidighet för containrar

Lån på containrar möjliggör samma synkroniseringsstrategier som stöds för blobar, inklusive exklusiv skrivning/delad läsning, exklusiv skrivning/exklusiv läsning och delad skrivning/exklusiv läsning. För containrar framtvingas dock det exklusiva låset endast vid borttagningsåtgärder. Om du vill ta bort en container med ett aktivt lån måste en klient inkludera det aktiva låne-ID:t med borttagningsbegäran. Alla andra containeråtgärder lyckas på en hyrd container utan låne-ID:t.

Nästa steg

Resurser

Relaterade kodexempel med inaktuell .NET version 11.x SDK:er finns i Kodexempel med .NET version 11.x.