إدارة التزامن في تخزين Blob

غالبا ما يكون للتطبيقات الحديثة العديد من المستخدمين الذين يعرضون البيانات ويحدثونها في وقت واحد. يحتاج مطورو التطبيقات إلى التفكير بعناية في كيفية توفير تجربة يمكن التنبؤ بها لمستخدميهم النهائيين، خاصة بالنسبة للسيناريوهات التي يمكن فيها لعدة مستخدمين تحديث نفس البيانات. هناك ثلاث استراتيجيات رئيسية لتزامن البيانات يأخذها المطورون في الاعتبار عادة:

  • التزامن المتفائل: سيحدد التطبيق الذي يقوم بإجراء تحديث، كجزء من تحديثه، ما إذا كانت البيانات قد تغيرت منذ آخر مرة قرأ فيها التطبيق تلك البيانات أم لا. على سبيل المثال، إذا كان مستخدمان يعرضان صفحة wiki يقومان بإجراء تحديث لتلك الصفحة، فيجب أن يضمن النظام الأساسي wiki أن التحديث الثاني لا يقوم بالكتابة فوق التحديث الأول. يجب أن يضمن أيضا أن كلا المستخدمين يفهمون ما إذا كان التحديث الخاص بهم ناجحا أم لا. غالبا ما تستخدم هذه الاستراتيجية في تطبيقات الويب.

  • التزامن المتشائم: يأخذ التطبيق الذي يتطلع إلى إجراء تحديث تأمينا على كائن يمنع المستخدمين الآخرين من تحديث البيانات حتى يتم تحرير القفل. على سبيل المثال، في سيناريو النسخ المتماثل للبيانات الأساسي/الثانوي الذي يقوم فيه الأساسي فقط بإجراء التحديثات، يحتفظ الأساسي عادة بقفل حصري على البيانات لفترة طويلة من الزمن لضمان عدم تمكن أي شخص آخر من تحديثها.

  • الكاتب الأخير يفوز: نهج يسمح بمتابعة عمليات التحديث دون تحديد ما إذا كان تطبيق آخر قد قام أولا بتحديث البيانات منذ قراءتها. يستخدم هذا الأسلوب عادة عندما يتم تقسيم البيانات بطريقة لا يصل فيها العديد من المستخدمين إلى نفس البيانات في نفس الوقت. ويمكن أن يكون مفيدا أيضا عندما تتم معالجة تدفقات البيانات قصيرة العمر.

يدعم Azure Storage جميع الاستراتيجيات الثلاث، على الرغم من أنه مميز في قدرته على توفير الدعم الكامل للتزامن المتفائل والمتشائم. تم تصميم Azure Storage لاحتضان نموذج تناسق قوي يضمن أنه بعد تنفيذ الخدمة لعملية إدراج أو تحديث، تقوم عمليات القراءة أو القائمة اللاحقة بإعادة التحديث الأخير.

بالإضافة إلى اختيار استراتيجية تزامن مناسبة، يجب أن يكون المطورون أيضا على دراية بكيفية عزل منصة التخزين للتغييرات، وخاصة التغييرات التي تطرأ على نفس الكائن عبر المعاملات. يستخدم Azure Storage عزل اللقطة للسماح بعمليات القراءة بالتزامن مع عمليات الكتابة داخل قسم واحد. يضمن عزل اللقطة أن تقوم جميع عمليات القراءة بإرجاع لقطة متسقة للبيانات حتى أثناء حدوث التحديثات.

يمكنك اختيار استخدام نماذج التزامن المتفائلة أو المتشائمة لإدارة الوصول إلى النقط والحاويات. إذا لم تحدد بشكل صريح استراتيجية، فسيفوز الكاتب الأخير افتراضيا.

التزامن الأمثل

يعين Azure Storage معرفا لكل كائن مخزن. يتم تحديث هذا المعرف في كل مرة يتم فيها تنفيذ عملية كتابة على كائن. يتم إرجاع المعرف إلى العميل كجزء من استجابة HTTP GET في عنوان ETag المحدد بواسطة بروتوكول HTTP.

يمكن للعميل الذي يقوم بإجراء تحديث إرسال ETag الأصلي مع عنوان شرطي للتأكد من حدوث تحديث فقط إذا تم استيفاء شرط معين. على سبيل المثال، إذا تم تحديد عنوان If-Match، يتحقق Azure Storage من أن قيمة ETag المحددة في طلب التحديث هي نفسها قيمة ETag للكائن الذي يتم تحديثه. لمزيد من المعلومات حول العناوين الشرطية، راجع تحديد العناوين الشرطية لعمليات خدمة Blob.

وفيما يلي الخطوط العريضة لهذه العملية:

  1. استرداد كائن ثنائي كبير الحجم من Azure Storage. تتضمن الاستجابة قيمة رأس HTTP ETag التي تحدد الإصدار الحالي من الكائن.
  2. عند تحديث كائن ثنائي كبير الحجم، قم بتضمين قيمة ETag التي تلقيتها في الخطوة 1 العنوان الشرطي If-Match لطلب الكتابة. يقارن Azure Storage قيمة ETag في الطلب بقيمة ETag الحالية للنقطة.
  3. إذا كانت قيمة ETag الحالية للكائن الثنائي كبير الحجم تختلف عن قيمة ETag المحددة في العنوان الشرطي If-Match المتوفر على الطلب، فإن Azure Storage يرجع رمز حالة HTTP 412 (فشل الشرط المسبق). يشير هذا الخطأ إلى العميل إلى أن عملية أخرى قد قامت بتحديث كائن ثنائي كبير الحجم منذ أن قام العميل باستردادها لأول مرة. يجب على العميل إحضار الكائن الثنائي كبير الحجم مرة أخرى للحصول على المحتوى والخصائص المحدثة.
  4. إذا كانت قيمة ETag الحالية للنقطة هي نفس إصدار ETag في العنوان الشرطي If-Match في الطلب، يقوم Azure Storage بتنفيذ العملية المطلوبة وتحديث قيمة ETag الحالية للنقطة.

توضح أمثلة التعليمات البرمجية التالية كيفية إنشاء شرط If-Match على طلب الكتابة الذي يتحقق من قيمة ETag لكائن ثنائي كبير الحجم. يقوم Azure Storage بتقييم ما إذا كان ETag الحالي لكائن ثنائي كبير الحجم هو نفسه ETag المقدم بناء على الطلب وينفذ عملية الكتابة فقط إذا تطابقت قيمتا ETag. إذا قامت عملية أخرى بتحديث كائن ثنائي كبير الحجم في غضون ذلك، فسيقوم Azure Storage بإرجاع رسالة حالة HTTP 412 (فشل الشرط المسبق).

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 أيضا العناوين الشرطية الأخرى، بما في ذلك If-Modified-Since وIf-Unmodified-Since و If-None-Match. لمزيد من المعلومات، راجع تحديد العناوين الشرطية لعمليات خدمة كائن ثنائي كبير الحجم.

تزامن متشائم للكائنات الثنائية كبيرة الحجم

لقفل كائن ثنائي كبير الحجم للاستخدام الحصري، يمكنك الحصول على عقد إيجار عليها. عندما تحصل على عقد الإيجار، فإنك تحدد مدة عقد الإيجار. قد يكون عقد الإيجار المحدود صالحا من 15 إلى 60 ثانية. يمكن أن يكون عقد الإيجار أيضا غير محدود، وهو ما يرقى إلى قفل حصري. يمكنك تجديد عقد إيجار محدود لتمديده، ويمكنك تحرير عقد الإيجار عند الانتهاء منه. تقوم Azure Storage تلقائيا بإطلاق عقود تأجير محدودة عند انتهاء صلاحيتها.

تتيح عقود الإيجار دعم استراتيجيات المزامنة المختلفة، بما في ذلك عمليات الكتابة / القراءة المشتركة الحصرية، وعمليات الكتابة / القراءة الحصرية، وعمليات الكتابة / القراءة الحصرية المشتركة. عند وجود عقد إيجار، يفرض Azure Storage الوصول الحصري لعمليات الكتابة لحامل الإيجار. ومع ذلك، فإن ضمان التفرد لعمليات القراءة يتطلب من المطور التأكد من أن جميع تطبيقات العميل تستخدم معرف تأجير وأن عميلا واحدا فقط في كل مرة لديه معرف إيجار صالح. تؤدي عمليات القراءة التي لا تتضمن معرف عقد الإيجار إلى قراءات مشتركة.

توضح أمثلة التعليمات البرمجية التالية كيفية الحصول على عقد إيجار حصري على كائن ثنائي كبير الحجم، وتحديث محتوى كائن ثنائي كبير الحجم من خلال توفير معرف الإيجار، ثم تحرير عقد الإيجار. إذا كان عقد الإيجار نشطا ولم يتم توفير معرف التأجير على طلب كتابة، فستفشل عملية الكتابة مع رمز الخطأ 412 (فشل الشرط المسبق).

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

تزامن متشائم للحاويات

تتيح عقود الإيجار على الحاويات نفس استراتيجيات المزامنة المدعومة للنقاط، بما في ذلك الكتابة الحصرية / القراءة المشتركة، والكتابة الحصرية / القراءة الحصرية، والكتابة المشتركة / القراءة الحصرية. ومع ذلك، بالنسبة للحاويات، يتم فرض القفل الحصري فقط على عمليات الحذف. لحذف حاوية ذات عقد إيجار نشط، يجب على العميل تضمين معرف الإيجار النشط مع طلب الحذف. تنجح جميع عمليات الحاوية الأخرى على حاوية مؤجرة دون معرف الإيجار.

الخطوات التالية

الموارد

للحصول على نماذج التعليمات البرمجية ذات الصلة باستخدام .NET الإصدار 11.x SDKs المهمل، راجع نماذج التعليمات البرمجية باستخدام .NET الإصدار 11.x.