Gelijktijdigheid beheren in Blob-opslag

Moderne toepassingen hebben vaak meerdere gebruikers die gegevens tegelijk bekijken en bijwerken. Toepassingsontwikkelaars moeten goed nadenken over hoe ze hun eindgebruikers een voorspelbare ervaring kunnen bieden, 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 heeft gelezen. Als bijvoorbeeld twee gebruikers die een wikipagina bekijken, een update van die pagina uitvoeren, moet het wikiplatform ervoor zorgen dat de eerste update niet wordt overschreven door de tweede update. 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 waardoor andere gebruikers de gegevens niet kunnen bijwerken totdat de vergrendeling is vrijgegeven. In een scenario voor primaire/secundaire gegevensreplicatie waarin alleen de primaire updates uitvoert, houdt de primaire bijvoorbeeld een exclusieve vergrendeling op de gegevens gedurende een langere periode om ervoor te zorgen dat niemand anders deze kan bijwerken.

  • Laatste schrijver wint: een methode waarmee updatebewerkingen kunnen worden uitgevoerd zonder eerst te bepalen of de gegevens door een andere toepassing zijn bijgewerkt sinds deze zijn gelezen. Deze benadering wordt meestal gebruikt wanneer gegevens zodanig zijn gepartitioneerd dat meerdere gebruikers niet tegelijkertijd toegang hebben tot dezelfde gegevens. Het kan ook handig zijn wanneer gegevensstromen met een 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 gebruiken 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 de manier waarop 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 worden uitgevoerd.

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

Optimistische gelijktijdigheid

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 een update alleen plaatsvindt als aan een bepaalde voorwaarde is voldaan. Als bijvoorbeeld de If-Match-header is opgegeven, controleert Azure Storage of de waarde van de ETag die is opgegeven in de updateaanvraag, dezelfde 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 die de huidige versie van het object identificeert.
  2. Wanneer u de blob bijwerkt, neemt u de ETag-waarde op die u in stap 1 hebt ontvangen 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 afwijkt van de ETag-waarde die is opgegeven in de if-match-header die is opgegeven in de aanvraag, retourneert Azure Storage HTTP-statuscode 412 (voorwaarde mislukt). Deze fout geeft aan dat de blob door een ander proces is 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 wordt de huidige ETag-waarde van de blob bijgewerkt.

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 in de aanvraag is opgegeven en voert de schrijfbewerking alleen uit als de twee ETag-waarden overeenkomen. Als de blob in de tussentijd door een ander proces 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 ondersteunt ook 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 voor 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 te verlengen en u kunt de lease vrijgeven wanneer u klaar bent met de lease. Azure Storage geeft automatisch eindige leases vrij 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 tot schrijfbewerkingen af voor de leasehouder. Voor het garanderen van exclusiviteit voor leesbewerkingen moet de ontwikkelaar er echter voor zorgen 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 voor een blob krijgt, de inhoud van de blob bijwerkt door de lease-id op te geven en vervolgens de lease vrijgeeft. Als de lease actief is en de lease-id niet is opgegeven voor een schrijfaanvraag, mislukt de schrijfbewerking met foutcode 412 (voorwaarde is 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 exclusief schrijven/gedeeld lezen, exclusief schrijven/exclusief lezen en gedeeld schrijven/exclusief lezen. Voor containers wordt de exclusieve vergrendeling echter alleen afgedwongen bij verwijderbewerkingen. Als u een container met een actieve lease wilt verwijderen, moet een client de actieve lease-id bij de verwijderingsaanvraag opnemen. Alle andere containerbewerkingen worden uitgevoerd op een leasecontainer zonder de lease-id.

Volgende stappen

Resources

Zie Codevoorbeelden met .NET versie 11.x voor gerelateerde codevoorbeelden met behulp van afgeschafte .NET versie 11.x SDK's.