Mengelola Konkurensi dalam penyimpanan Blob

Aplikasi modern sering mengalami kejadian beberapa pengguna melihat dan memperbarui data secara bersamaan. Pengembang aplikasi perlu memikirkan dengan cermat tentang cara memberikan pengalaman yang dapat diprediksi kepada pengguna akhir mereka, terutama untuk skenario di mana beberapa pengguna dapat memperbarui data yang sama. Terdapat tiga strategi konkurensi data utama yang biasanya dipertimbangkan pengembang:

  • Konkurensi optimis: Aplikasi yang melakukan pembaruan akan, sebagai bagian dari pembaruannya, menentukan apakah data telah berubah sejak aplikasi terakhir kali membaca data tersebut. Misalnya, jika dua pengguna yang melihat halaman wiki membuat pembaruan ke halaman tersebut, maka platform wiki harus memastikan bahwa pembaruan kedua tidak menimpa pembaruan pertama. Ini juga harus memastikan bahwa kedua pengguna memahami apakah pembaruannya berhasil. Strategi ini paling sering digunakan dalam aplikasi web.

  • Konkurensi pesimis: Aplikasi yang ingin melakukan pembaruan mengambil kunci pada objek yang mencegah pengguna lain memperbarui data hingga kunci dilepaskan. Misalnya, dalam skenario replikasi data primer/sekunder di mana hanya yang primer yang melakukan pembaruan, primer biasanya memegang kunci eksklusif pada data untuk jangka waktu yang lama demi memastikan tidak ada orang lain yang dapat memperbaruinya.

  • Penulis terakhir menang: Pendekatan yang memungkinkan operasi pembaruan untuk diproses tanpa terlebih dahulu menentukan apakah aplikasi lain telah memperbarui data sejak dibaca. Pendekatan ini biasanya digunakan ketika data dipartisi sedih sehingga beberapa pengguna tidak mengakses data yang sama secara bersamaan. Ini juga dapat berguna di mana aliran data dengan masa singkat sedang diproses.

Azure Storage mendukung ketiga strategi, meskipun khas dalam kemampuannya untuk memberikan dukungan penuh untuk konkurensi optimis dan pesimis. Azure Storage dirancang untuk merangkul model konsistensi kuat yang menjamin bahwa setelah layanan melakukan operasi penyisipan atau pembaruan, operasi baca atau daftar berikutnya mengembalikan pembaruan terbaru.

Selain memilih strategi konkurensi yang sesuai, pengembang juga harus menyadari cara platform penyimpanan mengisolasi perubahan, terutama perubahan pada objek yang sama di seluruh transaksi. Azure Storage menggunakan isolasi rekam jepret untuk memungkinkan operasi baca bersamaan dengan operasi tulis dalam satu partisi. Isolasi rekam jepret menjamin bahwa semua operasi baca mengembalikan rekam jepret data yang konsisten bahkan saat pembaruan terjadi.

Anda dapat memilih untuk menggunakan model konkurensi optimis atau pesimis dalam mengelola akses ke blob dan kontainer. Jika Anda tidak secara eksplisit menentukan strategi, maka secara default penulis terakhir menang.

Konkurensi optimis

Azure Storage menetapkan pengidentifikasi untuk setiap objek yang disimpan. Pengidentifikasi ini diperbarui setiap kali operasi tulis dilakukan pada suatu objek. Pengidentifikasi dikembalikan ke klien sebagai bagian dari respons HTTP GET di header ETag yang ditentukan oleh protokol HTTP.

Klien yang melakukan pembaruan dapat mengirim ETag asli bersama dengan header kondisional untuk memastikan bahwa pembaruan hanya terjadi jika kondisi tertentu telah terpenuhi. Misalnya, jika header If-Match ditentukan, Azure Storage memastikan bahwa nilai ETag yang ditentukan dalam permintaan pembaruan sama dengan ETag untuk objek yang diperbarui. Untuk informasi selengkapnya tentang penggunaan header kondisional, lihat Menentukan header kondisional untuk operasi Blob service.

Garis besar proses ini adalah sebagai berikut:

  1. Mengambil blob dari Azure Storage. Responsnya mencakup nilai Header ETag HTTP yang mengidentifikasi versi objek saat ini.
  2. Saat Anda memperbarui blob, sertakan nilai ETag yang diterima di langkah 1 pada header kondisional If-Match dari permintaan tulis. Azure Storage membandingkan nilai ETag dalam permintaan dengan nilai ETag saat ini dari blob.
  3. Jika nilai ETag blob saat ini berbeda dari nilai ETag yang ditentukan dalam header kondisional If-Match yang disediakan pada permintaan, maka Azure Storage mengembalikan kode status HTTP 412 (Prasyarat Gagal). Kesalahan ini menunjukkan kepada klien bahwa proses lain telah memperbarui blob sejak klien pertama kali mengambilnya. Klien harus mengambil blob lagi untuk mendapatkan konten dan properti yang diperbarui.
  4. Jika nilai ETag saat ini dari blob adalah versi yang sama dengan ETag dalam header kondisional If-Match dalam permintaan, Azure Storage melakukan operasi yang diminta dan memperbarui nilai ETag saat ini dari blob.

Contoh kode berikut menunjukkan cara membuat kondisi If-Match pada permintaan tulis yang memeriksa nilai ETag untuk blob. Azure Storage mengevaluasi apakah ETag blob saat ini sama dengan ETag yang disediakan atas permintaan dan melakukan operasi tulis hanya jika kedua nilai ETag cocok. Jika proses lain telah memperbarui blob untuk sementara, maka Azure Storage akan mengembalikan pesan status HTTP 412 (Prakondisi Gagal).

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 juga mendukung header kondisional lainnya, termasuk If-Modified-Since, If-Unmodified-Since dan If-None-Match. Untuk informasi selengkapnya, lihat Menentukan Header Kondisional untuk Operasi Blob Service.

Konkurensi pesimis untuk blob

Guna mengunci blob untuk penggunaan eksklusif, Anda dapat memperoleh sewa atasnya. Jika memperoleh sewanya, Anda menentukan durasi sewa. Sewa terbatas dapat berlaku antara 15 hingga 60 detik. Suatu sewa juga dapat bersifat tak terbatas, yang senilai dengan kunci eksklusif. Anda dapat memperpanjang sewa terbatas, dan Anda dapat melepaskan sewa jika Anda selesai. Azure Storage secara otomatis melepaskan sewa terbatas saat kedaluwarsa.

Sewa memungkinkan strategi sinkronisasi yang berbeda untuk didukung, termasuk operasi tulis/baca bersama eksklusif, operasi baca eksklusif/tulis eksklusif, dan operasi baca eksklusif/tulis bersama. Jika terdapat suatu sewa, Azure Storage memberlakukan akses eksklusif untuk operasi tulis bagi pemegang sewa. Namun, memastikan eksklusivitas untuk operasi baca mewajibkan pengembang untuk memastikan agar semua aplikasi klien menggunakan ID sewa dan hanya satu klien pada satu waktu yang memiliki ID sewa yang valid. Operasi baca yang tidak menyertakan ID sewa menghasilkan bacaan bersama.

Contoh kode berikut menunjukkan cara memperoleh sewa eksklusif terhadap blob, memperbarui konten blob dengan memberikan ID sewa, dan kemudian melepaskan sewa. Jika sewa aktif dan ID sewa tidak disediakan pada permintaan tulis, maka operasi tulis gagal dengan kode kesalahan 412 (Prasyarat Gagal).

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

Konkurensi pesimis untuk kontainer

Sewa untuk kontainer memungkinkan strategi sinkronisasi yang sama yang didukung untuk blob, termasuk tulis eksklusif/baca bersama, tulis eksklusif/baca eksklusif, dan tulis bersama/baca eksklusif. Namun, untuk kontainer, kunci eksklusif hanya diberlakukan pada operasi penghapusan. Untuk menghapus kontainer dengan sewa aktif, klien harus menyertakan ID sewa aktif dengan permintaan penghapusan. Semua operasi kontainer lainnya berhasil pada kontainer sewaan tanpa ID sewa.

Langkah berikutnya

Sumber

Untuk sampel kode terkait menggunakan .NET versi 11.x SDK yang tidak digunakan lagi, lihat Sampel kode menggunakan .NET versi 11.x.