Udostępnij za pośrednictwem


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ę nad tym, 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 należy wziąć pod uwagę przez deweloperów:

  • 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 tej strony, platforma typu wiki musi upewnić się, że druga aktualizacja nie zastępuje pierwszej aktualizacji. Należy również upewnić się, że obaj użytkownicy rozumieją, 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 podstawowe wykonuje aktualizacje, podstawowe zwykle przechowuje wyłączną blokadę danych przez dłuższy czas, aby zapewnić, że nikt inny nie może 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 celu 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 zezwolić na operacje odczytu współbieżnie z operacjami zapisu w ramach jednej partycji. Izolacja migawek gwarantuje, że wszystkie operacje odczytu zwracają spójną migawkę danych nawet wtedy, gdy są wykonywane aktualizacje.

Możesz użyć optymistycznych lub pesymistycznych modeli współbieżności, aby zarządzać dostępem do obiektów blob i kontenerów. 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ą 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 go po raz pierwszy pobrał. Klient powinien ponownie pobrać obiekt blob, aby pobrać zaktualizowaną zawartość i właściwości.
  4. Jeśli bieżąca wartość 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 te dwie wartości elementu ETag są zgodne. 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, w tym jako 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ść dla 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ć prawidłowa z zakresu od 15 do 60 sekund. Dzierżawa może być również nieskończona, co stanowi wyłączną blokadę. Możesz odnowić skończoną dzierżawę, aby ją przedłużyć i zwolnić dzierżawę po zakończeniu jej pracy. Usługa Azure Storage automatycznie zwalnia dzierżawy skończone po wygaśnięciu.

Dzierżawy umożliwiają obsługę różnych strategii synchronizacji, w tym wyłączne operacje zapisu/odczytu współużytkowanego, wyłączne operacje zapisu/wyłącznego odczytu oraz udostępnione operacje zapisu/wyłącznego odczytu. Gdy dzierżawa istnieje, usługa Azure Storage wymusza wyłączny dostęp do operacji zapisu dla właściciela 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 w danym momencie ma prawidłowy identyfikator dzierżawy. Operacje odczytu, które nie zawierają wyniku identyfikatora dzierżawy w udostępnionych odczytach.

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łącznego odczytu/udostępnionego odczytu, wyłącznego zapisu/wyłącznego odczytu oraz udostępnionego zapisu/wyłącznego odczytu. 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 dla dzierżawionego 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 korzystające z platformy .NET w wersji 11.x.