Zarządzanie współbieżnością w usłudze Blob Storage

Nowoczesne aplikacje często mają wielu użytkowników wyświetlających i aktualizując dane jednocześnie. Deweloperzy aplikacji muszą dokładnie zastanowić się, jak zapewnić użytkownikom końcowym przewidywalne środowisko, szczególnie w scenariuszach, w których wielu użytkowników może aktualizować te same dane. Istnieją trzy główne strategie współbieżności danych, które zwykle rozważają deweloperzy:

  • Optymistyczna współbieżność: aplikacja wykonująca aktualizację określi, czy dane uległy zmianie od czasu ostatniego odczytania tych danych przez aplikację. Jeśli na przykład dwóch użytkowników wyświetlających stronę typu wiki dokona aktualizacji na tej stronie, platforma typu wiki musi upewnić się, że druga aktualizacja nie zastępuje pierwszej aktualizacji. Należy również upewnić się, że obaj użytkownicy będą wiedzieć, czy ich aktualizacja zakończyła się pomyślnie. Ta strategia jest najczęściej używana w aplikacjach internetowych.

  • Pesymistyczna współbieżność: aplikacja, która chce przeprowadzić aktualizację, pobiera blokadę na obiekcie uniemożliwiającym innym użytkownikom aktualizowanie danych do momentu zwolnienia blokady. Na przykład w scenariuszu replikacji danych podstawowych/pomocniczych, w którym tylko podstawowy wykonuje aktualizacje, podstawowy zazwyczaj przechowuje wyłączną blokadę danych przez dłuższy czas, aby nikt inny nie mógł go zaktualizować.

  • Ostatni składnik zapisywania wygrywa: podejście umożliwiające kontynuowanie operacji aktualizacji bez uprzedniego określenia, czy inna aplikacja zaktualizowała dane od czasu ich odczytu. Takie podejście jest zwykle używane, gdy dane są partycjonowane w taki sposób, że wielu użytkowników nie uzyskuje dostępu do tych samych danych w tym samym czasie. Może to być również przydatne w przypadku przetwarzania krótkotrwałych strumieni danych.

Usługa Azure Storage obsługuje wszystkie trzy strategie, chociaż jest ona charakterystyczna w jego możliwości zapewnienia pełnej obsługi optymistycznej i pesymistycznej współbieżności. Usługa Azure Storage została zaprojektowana tak, aby obejmowała model silnej spójności, który gwarantuje, że po wykonaniu operacji wstawiania lub aktualizacji kolejne operacje odczytu lub listy zwracają najnowszą aktualizację.

Oprócz wybrania odpowiedniej strategii współbieżności deweloperzy powinni również wiedzieć, w jaki sposób platforma magazynu izoluje zmiany, szczególnie zmiany w tym samym obiekcie między transakcjami. Usługa Azure Storage używa izolacji migawek, aby umożliwić operacje odczytu współbieżnie z operacjami zapisu w ramach jednej partycji. Izolacja migawki gwarantuje, że wszystkie operacje odczytu zwracają spójną migawkę danych, nawet jeśli są wykonywane aktualizacje.

Aby zarządzać dostępem do obiektów blob i kontenerów, możesz użyć optymistycznych lub pesymistycznego modelu współbieżności. Jeśli nie określisz jawnie strategii, domyślnie ostatni zapis wygrywa.

Optymistyczna współbieżność

Usługa Azure Storage przypisuje identyfikator do każdego przechowywanego obiektu. Ten identyfikator jest aktualizowany za każdym razem, gdy operacja zapisu jest wykonywana na obiekcie. Identyfikator jest zwracany do klienta w ramach odpowiedzi HTTP GET w nagłówku ETag zdefiniowanym przez protokół HTTP.

Klient wykonujący aktualizację może wysłać oryginalny element ETag wraz z nagłówkiem warunkowym, aby upewnić się, że aktualizacja ma miejsce tylko w przypadku spełnienia określonego warunku. Jeśli na przykład określono nagłówek If-Match , usługa Azure Storage sprawdza, czy wartość elementu ETag określonego w żądaniu aktualizacji jest taka sama jak element ETag dla aktualizowanego obiektu. Aby uzyskać więcej informacji na temat nagłówków warunkowych, zobacz Określanie nagłówków warunkowych dla operacji usługi Blob Service.

Konspekt tego procesu wygląda następująco:

  1. Pobieranie obiektu blob z usługi Azure Storage. Odpowiedź zawiera wartość nagłówka HTTP ETag, która identyfikuje bieżącą wersję obiektu.
  2. Po zaktualizowaniu obiektu blob dołącz wartość ETag otrzymaną w kroku 1 w nagłówku warunkowym If-Match żądania zapisu. Usługa Azure Storage porównuje wartość elementu ETag w żądaniu z bieżącą wartością elementu ETag obiektu blob.
  3. Jeśli bieżąca wartość elementu ETag obiektu blob różni się od wartości ETag określonej w nagłówku warunkowym If-Match podanym w żądaniu, usługa Azure Storage zwraca kod stanu HTTP 412 (Niepowodzenie warunku wstępnego). Ten błąd wskazuje klientowi, że inny proces zaktualizował obiekt blob od momentu, gdy klient po raz pierwszy go pobrał. Klient powinien ponownie pobrać obiekt blob, aby uzyskać zaktualizowaną zawartość i właściwości.
  4. Jeśli bieżąca wartość elementu ETag obiektu blob jest taka sama jak element ETag w nagłówku warunkowym If-Match w żądaniu, usługa Azure Storage wykonuje żądaną operację i aktualizuje bieżącą wartość elementu ETag obiektu blob.

W poniższych przykładach kodu pokazano, jak utworzyć warunek If-Match w żądaniu zapisu, które sprawdza wartość elementu ETag dla obiektu blob. Usługa Azure Storage ocenia, czy bieżący element ETag obiektu blob jest taki sam jak element ETag podany w żądaniu i wykonuje operację zapisu tylko wtedy, gdy są zgodne dwie wartości elementu ETag. Jeśli inny proces zaktualizował obiekt blob w międzyczasie, usługa Azure Storage zwraca komunikat o stanie HTTP 412 (Niepowodzenie warunku wstępnego).

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

Usługa Azure Storage obsługuje również inne nagłówki warunkowe, takie jak If-Modified-Since, If-Unmodified-Since i If-None-Match. Aby uzyskać więcej informacji, zobacz Określanie nagłówków warunkowych dla operacji usługi Blob Service.

Pesymistyczna współbieżność obiektów blob

Aby zablokować obiekt blob do wyłącznego użytku, możesz uzyskać na nim dzierżawę. Po uzyskaniu dzierżawy należy określić czas trwania dzierżawy. Dzierżawa skończona może być ważna z zakresu od 15 do 60 sekund. Dzierżawa może być również nieskończona, co oznacza wyłączną blokadę. Możesz odnowić ograniczoną dzierżawę, aby ją przedłużyć, a następnie zwolnić dzierżawę po zakończeniu jej pracy. Usługa Azure Storage automatycznie zwalnia dzierżawy ograniczone po wygaśnięciu.

Dzierżawy umożliwiają obsługę różnych strategii synchronizacji, w tym wyłącznie operacje zapisu/współużytkowanego odczytu, wyłączne operacje zapisu/wyłącznego odczytu i współużytkowane operacje zapisu/wyłącznego odczytu. Gdy dzierżawa istnieje, usługa Azure Storage wymusza wyłączny dostęp do operacji zapisu dla posiadacza dzierżawy. Jednak zapewnienie wyłączności dla operacji odczytu wymaga od dewelopera upewnienia się, że wszystkie aplikacje klienckie używają identyfikatora dzierżawy i że tylko jeden klient naraz ma prawidłowy identyfikator dzierżawy. Operacje odczytu, które nie zawierają identyfikatora dzierżawy, powodują odczyty udostępnione.

W poniższych przykładach kodu pokazano, jak uzyskać wyłączną dzierżawę obiektu blob, zaktualizować zawartość obiektu blob, podając identyfikator dzierżawy, a następnie zwolnić dzierżawę. Jeśli dzierżawa jest aktywna, a identyfikator dzierżawy nie jest podany w żądaniu zapisu, operacja zapisu kończy się niepowodzeniem z kodem błędu 412 (Niepowodzenie warunku wstępnego).

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

Pesymistyczna współbieżność dla kontenerów

Dzierżawy w kontenerach umożliwiają te same strategie synchronizacji, które są obsługiwane w przypadku obiektów blob, w tym wyłączny zapis/udostępniony odczyt, wyłączny odczyt/wyłączny odczyt oraz udostępniony zapis/wyłączny odczyt. Jednak w przypadku kontenerów blokada wyłączna jest wymuszana tylko w przypadku operacji usuwania. Aby usunąć kontener z aktywną dzierżawą, klient musi uwzględnić aktywny identyfikator dzierżawy z żądaniem usunięcia. Wszystkie inne operacje kontenera kończą się powodzeniem w dzierżawie kontenera bez identyfikatora dzierżawy.

Następne kroki

Zasoby

Aby uzyskać powiązane przykłady kodu korzystające z przestarzałych zestawów SDK platformy .NET w wersji 11.x, zobacz Przykłady kodu przy użyciu platformy .NET w wersji 11.x.