Share via


Gelijktijdigheid beheren in Blob Storage

Moderne toepassingen hebben vaak meerdere gebruikers die gegevens tegelijk bekijken en bijwerken. Toepassingsontwikkelaars moeten zorgvuldig nadenken over het bieden van een voorspelbare ervaring aan hun eindgebruikers, met name voor scenario's waarin meerdere gebruikers dezelfde gegevens kunnen bijwerken. Er zijn drie belangrijke strategieën voor gelijktijdigheid van gegevens die ontwikkelaars doorgaans overwegen:

  • Optimistische gelijktijdigheid: een toepassing die een update uitvoert, bepaalt, als onderdeel van de update, of de gegevens zijn gewijzigd sinds de toepassing die gegevens voor het laatst hebben gelezen. Als bijvoorbeeld twee gebruikers die een wikipagina bekijken, een update naar die pagina maken, moet het wikiplatform ervoor zorgen dat de tweede update de eerste update niet overschrijft. Het moet er ook voor zorgen dat beide gebruikers begrijpen of hun update is geslaagd. Deze strategie wordt meestal gebruikt in webtoepassingen.

  • Pessimistische gelijktijdigheid: een toepassing die een update wil uitvoeren, vergrendelt een object dat verhindert dat andere gebruikers de gegevens bijwerken totdat de vergrendeling is vrijgegeven. In een scenario met primaire/secundaire gegevensreplicatie waarin alleen de primaire updates worden uitgevoerd, heeft de primaire doorgaans een exclusieve vergrendeling voor de gegevens gedurende een langere periode om ervoor te zorgen dat niemand anders het kan bijwerken.

  • Last Writer wint: Een benadering waarmee updatebewerkingen kunnen worden voortgezet zonder eerst te bepalen of de gegevens door een andere toepassing zijn bijgewerkt sinds deze zijn gelezen. Deze methode wordt doorgaans gebruikt wanneer gegevens zodanig worden gepartitioneerd dat meerdere gebruikers niet tegelijkertijd toegang hebben tot dezelfde gegevens. Het kan ook handig zijn wanneer gegevensstromen met korte levensduur worden verwerkt.

Azure Storage ondersteunt alle drie de strategieën, hoewel het onderscheidend is in de mogelijkheid om volledige ondersteuning te bieden voor optimistische en pessimistische gelijktijdigheid. Azure Storage is ontworpen om een sterk consistentiemodel te omarmen dat garandeert dat nadat de service een invoeg- of updatebewerking heeft uitgevoerd, volgende lees- of lijstbewerkingen de meest recente update retourneren.

Naast het selecteren van een geschikte gelijktijdigheidsstrategie moeten ontwikkelaars zich ook bewust zijn van hoe een opslagplatform wijzigingen isoleert, met name wijzigingen in hetzelfde object tussen transacties. Azure Storage maakt gebruik van isolatie van momentopnamen om leesbewerkingen gelijktijdig met schrijfbewerkingen binnen één partitie toe te staan. Isolatie van momentopnamen garandeert dat alle leesbewerkingen een consistente momentopname van de gegevens retourneren, zelfs wanneer er updates plaatsvinden.

U kunt ervoor kiezen om optimistische of pessimistische gelijktijdigheidsmodellen te gebruiken om de toegang tot blobs en containers te beheren. Als u niet expliciet een strategie opgeeft, wint de laatste schrijver standaard.

Optimistische gelijktijdige uitvoering

Azure Storage wijst een id toe aan elk object dat is opgeslagen. Deze id wordt bijgewerkt telkens wanneer een schrijfbewerking wordt uitgevoerd op een object. De id wordt geretourneerd naar de client als onderdeel van een HTTP GET-antwoord in de ETag-header die is gedefinieerd door het HTTP-protocol.

Een client die een update uitvoert, kan de oorspronkelijke ETag samen met een voorwaardelijke header verzenden om ervoor te zorgen dat er alleen een update plaatsvindt als aan een bepaalde voorwaarde is voldaan. Als de If-Match-header bijvoorbeeld is opgegeven, controleert Azure Storage of de waarde van de ETag die is opgegeven in de updateaanvraag hetzelfde is als de ETag voor het object dat wordt bijgewerkt. Zie Voorwaardelijke headers opgeven voor blobservicebewerkingen voor meer informatie over voorwaardelijke headers.

Het overzicht van dit proces is als volgt:

  1. Een blob ophalen uit Azure Storage. Het antwoord bevat een HTTP ETag-headerwaarde waarmee de huidige versie van het object wordt geïdentificeerd.
  2. Wanneer u de blob bijwerkt, neemt u de ETag-waarde op die u hebt ontvangen in stap 1 in de voorwaardelijke header If-Match van de schrijfaanvraag. Azure Storage vergelijkt de ETag-waarde in de aanvraag met de huidige ETag-waarde van de blob.
  3. Als de huidige ETag-waarde van de blob verschilt van de ETag-waarde die is opgegeven in de voorwaardelijke header If-Match die is opgegeven in de aanvraag, retourneert Azure Storage HTTP-statuscode 412 (voorwaarde is mislukt). Deze fout geeft aan de client aan dat een ander proces de blob heeft bijgewerkt sinds de client deze voor het eerst heeft opgehaald. De client moet de blob opnieuw ophalen om de bijgewerkte inhoud en eigenschappen op te halen.
  4. Als de huidige ETag-waarde van de blob dezelfde versie is als de ETag in de voorwaardelijke header If-Match in de aanvraag, voert Azure Storage de aangevraagde bewerking uit en werkt de huidige ETag-waarde van de blob bij.

In de volgende codevoorbeelden ziet u hoe u een If-Match-voorwaarde maakt voor de schrijfaanvraag waarmee de ETag-waarde voor een blob wordt gecontroleerd. Azure Storage evalueert of de huidige ETag van de blob hetzelfde is als de ETag die is opgegeven in de aanvraag en voert de schrijfbewerking alleen uit als de twee ETag-waarden overeenkomen. Als in een ander proces de blob in de tussentijd is bijgewerkt, retourneert Azure Storage een http 412-statusbericht (voorwaarde mislukt).

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 biedt ook ondersteuning voor andere voorwaardelijke headers, zoals If-Modified-Since, If-Unmodified-Since en If-None-Match. Zie Voorwaardelijke headers opgeven voor blobservicebewerkingen voor meer informatie.

Pessimistische gelijktijdigheid voor blobs

Als u een blob wilt vergrendelen voor exclusief gebruik, kunt u er een lease op verkrijgen. Wanneer u de lease aanschaft, geeft u de duur van de lease op. Een eindige lease kan tussen 15 en 60 seconden geldig zijn. Een lease kan ook oneindig zijn, wat neerkomt op een exclusief slot. U kunt een eindige lease verlengen om deze uit te breiden en u kunt de lease vrijgeven wanneer u klaar bent. Azure Storage publiceert automatisch eindige leases wanneer ze verlopen.

Met leases kunnen verschillende synchronisatiestrategieën worden ondersteund, waaronder exclusieve schrijf-/gedeelde leesbewerkingen, exclusieve schrijf-/exclusieve leesbewerkingen en gedeelde schrijf-/exclusieve leesbewerkingen. Wanneer er een lease bestaat, dwingt Azure Storage exclusieve toegang af tot schrijfbewerkingen voor de leasehouder. Het garanderen van exclusiviteit voor leesbewerkingen vereist echter dat de ontwikkelaar ervoor zorgt dat alle clienttoepassingen een lease-id gebruiken en dat slechts één client tegelijk een geldige lease-id heeft. Leesbewerkingen die geen lease-id bevatten, resulteren in gedeelde leesbewerkingen.

In de volgende codevoorbeelden ziet u hoe u een exclusieve lease op een blob verkrijgt, de inhoud van de blob bijwerkt door de lease-id op te geven en vervolgens de lease vrij te geven. Als de lease actief is en de lease-id niet is opgegeven voor een schrijfaanvraag, mislukt de schrijfbewerking met foutcode 412 (Voorwaarde mislukt).

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

Pessimistische gelijktijdigheid voor containers

Leases voor containers maken dezelfde synchronisatiestrategieën mogelijk die worden ondersteund voor blobs, waaronder exclusieve schrijf-/gedeelde leesbewerkingen, exclusieve schrijf-/exclusieve leesbewerkingen en gedeelde schrijf-/exclusieve leesbewerkingen. Voor containers wordt de exclusieve vergrendeling echter alleen afgedwongen bij verwijderingsbewerkingen. Als u een container met een actieve lease wilt verwijderen, moet een client de actieve lease-id met de verwijderaanvraag opnemen. Alle andere containerbewerkingen slagen in een leasecontainer zonder de lease-id.

Volgende stappen

Resources

Zie Codevoorbeelden met .NET-versie 11.x voor gerelateerde codevoorbeelden met .NET-versie 11.x.