أساسيات التحكّم الحصري في تكامل الملفّات - أفضل الممارسات لـ file lock والـ claim الذرّي

· آخر تحديث: · · تكامل الملفّات, التحكّم الحصري, التصميم, تطوير Windows

سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621313)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). أساسيات التحكّم الحصري في تكامل الملفّات - أفضل الممارسات لـ file lock والـ claim الذرّي. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621313 https://comcomponent.com/ar/blog/2026/03/07/001-file-integration-locking-best-practices-komurasoft-style/

DOI (أحدث نسخة)
10.5281/zenodo.21621313
DOI (هذه النسخة)
10.5281/zenodo.22240802

يصبح التحكّم الحصري في تكامل الملفّات مشكلة شبه دائمة مع المجلّدات المشتركة ودفعات الليل والتكامل بين عمليّات منفصلة. وما يكثر في البحث هو: هل يكفي file lock وحده، وكيف نمنع عدّة عمّال من التقاط الملفّ نفسه، وكيف نتجنّب الملفّ الذي ما زال يُكتَب فيه.

ينظر هذا المقال في التحكّم الحصري في تكامل الملفّات عبر file lock والـ claim الذرّي و temp -> rename و idempotency.

ضبط المصطلحات أوّلاً

هذا المجال مليء بكلمات استقرّت بالإنجليزيّة، ويصعب القراءة إن بقي معناها فضفاضاً. نثبّت المعنى المستخدم في هذا المقال أوّلاً.

المصطلح معناه في هذا المقال
ذرّيّ (atomic) عمليّة لا يظهر حالتها الوسيطة للآخرين. إمّا أن تنجح، وإمّا ألا يحدث شيء
claim تأمين حقّ المعالجة بمعنى «هذا الملفّ أعالجه أنا». في هذا المقال يعني أساساً أنّ من نجح في rename من incoming إلى processing/<worker>/ يصبح المالك
claim ذرّيّ تنفيذ ذلك الـ claim بعمليّة واحدة. إذا انفصل التحقّق عن التأمين، استطاع مسار آخر الدخول في الفجوة (3.1)
lease ملكيّة بمدّة صلاحية. يُكتب في lock file «من» و«حتّى متى»، فإذا انتهت المدّة استطاع عامل آخر أن يتولّى الملفّ (4.4)
stale حالة lock أو claim بقي بعد إنهاء غير طبيعي لصاحبه. إن تعذّر الحكم أحيّ هو أم ميّت، توقّف الجميع (2.3)
manifest ملفّ وصف يوضع بمعزل عن الملفّ الأصلي. يكتب اسم الملفّ والحجم والـ hash وعدد السجلّات وغيرها، ويستخدمه المستلم للتحقّق. ملفّ done هو أصغر صورة منه (4.2)
idempotency (عدم تغيير النتيجة عند التكرار) خاصّية أنّ معالجة المدخل نفسه مرّة أخرى لا تغيّر النتيجة (4.5)
advisory lock قفل يعمل فقط على افتراض أنّ جميع المشاركين يحترمون الاتفاق. نظام التشغيل لا يفرضه، فيمكن كتابة برنامج يتجاهله ويقرأ ويكتب. flock على Linux من هذا النوع
byte-range lock قفل يستهدف نطاقاً معيّناً لا الملفّ بأكمله. LockFileEx على Windows مثال بارز، ونظام التشغيل يفرضه. غير أنّ ثمّة استثناء (3.5)

خريطة المعرفة لهذه المقالة

تنظّم هذه المقالة تكامل الملفّات عبر المجلّدات المشتركة ودفعات الليل لا كاتّكال على قفل نظام التشغيل بل كتصميم بروتوكول تسليم. تُؤمَّن حقّ المعالجة بـ claim ذرّي قبل القراءة، وتُحبَس الملفّات قيد التوليد تحت اسم temp ثمّ تُنشَر بـ rename إلى الاسم النهائي بعد close، ويُعلَن الاكتمال بـ done و manifest لا بالتخمين من الحجم أو الطابع الزمني. إن استُخدم lock file فليكن بصيغة lease يحمل ownerId و expiresAt استعداداً لـ stale lock، ومع إدراك أنّ byte-range lock على Windows يُتجاهَل مع الملفّات المعيَّنة في الذاكرة وأنّ advisory lock لا ينفع طرفاً يتجاهل الاتفاق، يكون التصميم الأقوى في العمل أن يُستقبَل الأمر أخيراً بـ idempotency لا ينكسر إن أُعيدت معالجة المدخل نفسه.

خريطة معرفة التحكّم الحصري في تكامل الملفّاتمخطّط يبيّن كيف يجمع بروتوكول التسليم بين الـ claim الذرّي والنشر بـ temp->rename و done/manifest و lock file المحوَّل إلى lease و idempotency، وأيّ نمط مضادّ يقابل حوادث مثل المعالجة المزدوجة وقراءة ملفّ ما زال يُكتَب فيهيستخدميستخدميستخدميستخدميستخدميستخدميستخدميمنعقد يسبّبموصى به لـموصى به لـقد يسبّبيمنعقد يسبّبموصى به لـقد يسبّبموصى به لـموصى به لـيشترطيستخدميخفّفغير موصى به لـغير موصى به لـلا يتوافق معقد يسبّبقد يسبّبموصى به لـموصى به لـقد يسبّببروتوكول التسليمclaim ذرّيّالنشر بـ temp -> close -> rename/replaceملفّ done/manifestlock file محوَّل إلى leaseمعالجة تفترض idempotencyقفل ملفّ نظام التشغيل (قفل OS)إنشاء ذرّيّ (CreateNew / O_CREAT|O_EXCL)معالجة مزدوجة (عدّ مزدوج، إرسال مكرّر، ضياع التحديث)الفحص على مرحلتين Exists->Createالكتابة مباشرة إلى الاسم النهائيحادثة قراءة ملفّ ما زال يُكتَب فيهحكم الاكتمال بانتظار استقرار الحجمنمط مضادّ: تحديث متبادل لملفّ مشتركstale lockbyte-range lock (قفل نطاق)تكامل أنظمة غير متجانسةadvisory lockتراجع rename عبر الأحجام (نسخ + حذف)تكامل ملفّات عبر مجلّد مشترك (SMB)فشل rename بمجرّد أنّ الملفّ مفتوحعدم موثوقيّة طابع الملفّ الزمنيسرد دوري للدليلفقد إشعارات التغيير

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 29، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

المحتويات

  1. الخلاصة أوّلاً (في جملة)
  2. أنماط التعارض في تكامل الملفّات (رسوم)
    • 2.1. قراءة ملفّ ما زال يُكتَب فيه
    • 2.2. عدّة عمّال يلتقطون الملفّ نفسه في الوقت ذاته
    • 2.3. توقّف الجميع بسبب stale lock
  3. أنماط مضادّة
    • 3.1. الفحص على مرحلتين Exists -> Create
    • 3.2. الكتابة مباشرة إلى الاسم النهائي
    • 3.3. اعتبار توقّف حجم الملفّ اكتمالاً
    • 3.4. تحديث الجميع لملفّ مشترك
    • 3.5. الظنّ أنّ lock API علاج لكلّ شيء
  4. أفضل الممارسات
    • 4.1. النشر بـ temp -> close -> rename / replace
    • 4.2. إعلان الاكتمال بـ done / manifest
    • 4.3. يأخذ المستلم claim ذرّيّاً
    • 4.4. إن اعتمدت على lock file فاجعله lease
    • 4.5. افترض idempotency
  5. شيفرة تقريبية (مقتطفات)
  6. تقسيم تقريبي للاستخدام
  7. الخلاصة
  8. مراجع

تكامل الملفّات مجال تنكسر فيه «اتفاقية التسليم» أسهل من الشيفرة نفسها. ينجح اختبار الوحدة، ثمّ ينكسر أحياناً فقط في المجلّد المشترك الإنتاجي أو دفعة الليل. وإعادة الإنتاج صعبة. هذا شائع إلى حدّ بعيد.

كثير من الأسباب ليس واجهة file I/O نفسها، بل غموض هذه الثلاثة:

  • متى يجوز القراءة
  • من يملك حقّ المعالجة
  • كيف يُستعاد الأمر عند الفشل

هذا المقال لا يوقف التحكّم الحصري في تكامل الملفّات عند قفل نظام التشغيل، بل ينظّمه كبروتوكول تسليم.

وشيفرة هذا المقال منشورة على GitHub كعيّنة كاملة قابلة للبناء والتشغيل (مكتبة، وعرض لتنافس claim بين عاملين ولاستلام lease، واختبارات وحدة تعيد إنتاج التعارض والتلف و stale lock).

file-integration-locking-best-practices-komurasoft-style - komurasoft-blog-samples (GitHub)

1. الخلاصة أوّلاً (في جملة)

  • أهمّ ما في تكامل الملفّات أن تصنع حالة «يجوز القراءة الآن» في اللحظة التي يظهر فيها الاسم النهائي
  • عبّر عن قيد التوليد / منشور / قيد المعالجة / تمّت معالجته بالأسماء أو الأدلّة
  • إن وُجد عدّة عمّال، خذ claim ذرّيّاً قبل القراءة
  • استخدم lock file وقفل نظام التشغيل كأداة مساعدة، واستقبل الأمر أخيراً بـ idempotency

بعبارة أخرى، جسم تكامل الملفّات ليس التحكّم الحصري بقدر ما هو تصميم بروتوكول التسليم. استدعاء دالة قفل واحدة لا ينهي الأمر.

2. أنماط التعارض في تكامل الملفّات (رسوم)

2.1. قراءة ملفّ ما زال يُكتَب فيه

تبدأ الكتابة مباشرة إلى الاسم النهائي فتظهر هذه الحادثة. في JSON يغيب قوس الإغلاق، وفي CSV ينقص عدد الأسطر، وفي ZIP ينكسر الملفّ عادة.

جهة الاستلامالمجلّد المشتركجهة الإرسالجهة الاستلامالمجلّد المشتركجهة الإرسالما زال غير مكتملنقص أسطر / فشل تحليل / معالجة جزئيّةإنشاء orders.csv بالاسم النهائيالكتابة جارية من السطر 1 إلى 5000رصد orders.csvبدء القراءة كما هوكتابة الباقي

2.2. عدّة عمّال يلتقطون الملفّ نفسه في الوقت ذاته

بتدفّق «اسرد القائمة، وإن كان غير معالَج فافتحه» يستطيع عاملان الإمساك بالملفّ نفسه. هكذا يبدأ العدّ المزدوج والإرسال المكرّر.

incomingالعامل 2العامل 1incomingالعامل 2العامل 1معالجة المدخل نفسه مرّتينالعثور على a.csvالعثور على a.csvبدء القراءةبدء القراءة

2.3. توقّف الجميع بسبب stale lock

التصميم الذي يقتصر على وضع lock file يسهل أن يعلق عند الإنهاء غير الطبيعي. إن لم يُعرف صاحب الـ lock، وهل ما زال حيّاً، وإلى متى يبقى صالحاً، انتظر اللاحق إلى الأبد.

العامل Bملفّ lockالعامل Aالعامل Bملفّ lockالعامل Aإنهاء غير طبيعي هناتعذّر حكم stale فيتوقّف الجميعإنشاء lockالتحقّق من وجود lockتأجيل بدء المعالجةانتظار إضافي

3. أنماط مضادّة

3.1. الفحص على مرحلتين Exists -> Create

المشكلة أنّ «التحقّق» و«التأمين» عمليّتان منفصلتان. يستطيع مسار آخر أن يتدخّل بينهما، فلا يكون إقصاءً.

نظام الملفّاتالعمليّة Bالعمليّة Aنظام الملفّاتالعمليّة Bالعمليّة Aيتقدّم الطرفان معاًالتحقّق من غياب lockالتحقّق من غياب lockغير موجودغير موجودإنشاء lockإنشاء lock

مثال سيّئ نموذجيّ بهذا الشكل.

if (!File.Exists(lockPath))
{
    File.WriteAllText(lockPath, Environment.ProcessId.ToString());
    ProcessFile();
}

المطلوب جعل «أنشئ إن لم يوجد» عمليّة واحدة. في .NET استخدم عائلة FileMode.CreateNew، وفي POSIX إنشاءً ذرّيّاً مثل O_CREAT | O_EXCL.

3.2. الكتابة مباشرة إلى الاسم النهائي

إن فسّر المستلم «ظهور ذلك الاسم» بأنّه «يجوز القراءة»، فأنت خاسر من لحظة بدء الكتابة مباشرة إلى الاسم النهائي. القاعدة ألّا تجعل «مرئيّاً» و«يجوز القراءة» شيئاً واحداً.

ظهور الاسم النهائيرصد المستلمجهة الإرسال ما زالت تكتبقراءة بيانات غير مكتملة
using var writer = OpenForWrite(finalPath); // ここで finalPath が見えてしまう
foreach (var row in rows)
{
    writer.WriteLine(row);
}

هذه الطريقة تستدعي حادثة 2.1 بنفسك.

3.3. اعتبار توقّف حجم الملفّ اكتمالاً

يبدو مريحاً، لكنّه محفوف. ينحرف بسهولة مع النسخ عبر الشبكة، والتوقّف المؤقّت لجهة الإرسال، والـ buffering، وإعادة المحاولة.

جهة الاستلامالمجلّد المشتركجهة الإرسالجهة الاستلامالمجلّد المشتركجهة الإرسالحكم خاطئ بالاكتمالبدء نسخ data.zipتوقّف مؤقّت في الأثناءالحجم لم يتغيّر 10 ثوانبدء القراءةاستئناف النسخ
if (currentLength == lastLength && stableSeconds >= 10)
{
    return Ready;
}

إن قرّرت الاكتمال بالتخمين، تعثّرت في المجلّدات المشتركة والملفّات الكبيرة. أثبت الاكتمال بـ manifest أو done file يكون أثبت.

3.4. تحديث الجميع لملفّ مشترك

تصميم يقرأ فيه الجميع status.csv أو counter.json واحداً ثمّ يحدّثونه ينتهي غالباً بأنّ آخر من كتب يفوز. ما إن يبدأ تكامل الملفّات بالعمل كقاعدة بيانات مبسّطة حتّى يضيق الأمر هنا.

status.csvالدفعة Bالدفعة Astatus.csvالدفعة Bالدفعة Aيختفي تحديث Aقراءة v1قراءة v1كتابة v2-Aكتابة v2-B

هناك من يهرب إلى append-only، لكنّ المعنى ينحرف حسب نظام الملفّات وشكل النشر. إن احتجت تحديثاً مشتركاً، فالأولى ألّا تُكرِه تكامل الملفّات على ذلك.

3.5. الظنّ أنّ lock API علاج لكلّ شيء

lock API مهمّ، لكنّه يعمل فقط حين يتحرّك جميع المشاركين بالاتفاق نفسه. في تكامل أنظمة غير متجانسة، أأمن ألّا تبالغ في الثقة هنا.

تكميل:

  • flock على Linux هو advisory lock، فيمكن كتابة طرف يتجاهل الاتفاق بسهولة
  • byte-range lock على Windows يُتجاهَل مع الملفّات المعيَّنة في الذاكرة
  • أي أنّ الأفضل ألّا تحمّل قفل نظام التشغيل وحده إشعار الاكتمال وتصميم الملكيّة

البند الثاني منصوص عليه كمواصفة Windows. في Locking and Unlocking Byte Ranges in Files على Microsoft Learn، بعد القول إنّ وصول مسار آخر إلى نطاق مقفل يفشل حتماً (أي أنّ قفل النطاق في Windows مفروض لا advisory)، توضع ملاحظة أنّ byte-range lock يُتجاهَل عند استخدام ملفّ معيَّن في الذاكرة. إن لمس الطرف الملفّ نفسه عبر CreateFileMapping، مرّ قفلك دون أثر.

لأخذ قفل نطاق في .NET استخدم FileStream.Lock / Unlock (على Windows).

using var stream = new FileStream(
    path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

// قفل بايت واحد حصري عند الإزاحة 0 كعلامة «قيد المعالجة»
stream.Lock(0, 1);
try
{
    // هنا اقرأ/اكتب الجسم
}
finally
{
    // ألغِ القفل حتماً قبل الإغلاق
    stream.Unlock(0, 1);
}

هذا الشكل ينفع بين تطبيقات تتحرّك بالاتفاق نفسه. لكن كما سبق، لا يعمل إن مرّ الطرف عبر تعيين الذاكرة، ولا ضمان أصلاً أنّ نظاماً آخر ينظر إلى هذه العلامة. لذلك يكون جسم الأمر بروتوكول التسليم في الفصل 4.

4. أفضل الممارسات

نرتّب أوّلاً المقابلة مع الأنماط المضادّة في الفصل 3. إن عرفت أنّك تطأ أحدها، يمكنك أن تبدأ من الفقرة المقابلة.

النمط المضادّ ماذا يحدث الإجراء المقابل
3.1. الفحص على مرحلتين Exists -> Create يتدخّل طرف في فجوة التحقّق والتأمين، فيتقدّم مساران معاً 4.3 أخذ claim ذرّيّاً (rename أو FileMode.CreateNew)
3.2. الكتابة مباشرة إلى الاسم النهائي يقرأ المستلم ملفّاً ما زال يُكتَب فيه 4.1 النشر بـ temp -> close -> rename / replace
3.3. اعتبار توقّف حجم الملفّ اكتمالاً يُحكَم على توقّف النسخ المؤقّت بأنّه اكتمال 4.2 إعلان الاكتمال بـ done / manifest
3.4. تحديث الجميع لملفّ مشترك يُكتَب فوقه لاحقاً فيختفي التحديث 4.3 لتضييق الكاتب إلى واحد، و 4.5 لامتصاص المعالجة المزدوجة. إن لم يكفِ فحكم الانسحاب في الفصل 6
3.5. الظنّ أنّ lock API علاج لكلّ شيء يُخرَق مع طرف لا يحترم الاتفاق أو عبر تعيين الذاكرة 4.4 lock file كـ lease، و 4.5 الاستقبال بـ idempotency

4.1. النشر بـ temp -> close -> rename / replace

هذا هو المسار المعتمد. تُحبَس الملفّات قيد التوليد تحت اسم temp، وبعد close تُبدَّل إلى الاسم النهائي. ويراقب المستلم الاسم النهائي فقط.

صنع اسم temp فريدكتابة المحتوى كاملاً في tempflush / closerename / replace إلى الاسم النهائي في الدليل نفسهالمستلم يراقب الاسم النهائي فقط

نقاط:

  • ضع temp والاسم النهائي في الدليل نفسه، أو على الأقل على الحجم / نظام الملفّات نفسه
  • على Windows / .NET يمكن النظر في عائلة File.Replace
  • اجعل الاتفاق: ظهور الاسم النهائي يعني أنّ المحتوى مكتمل

إن وضعت temp على محرّك آخر، صار rename معادلاً لنسخ عادي، أو فشل Replace. هذا الافتراض متواضع المظهر، لكنّه مهمّ جدّاً.

عبر مجلّد مشترك (SMB) تنحرف أربع نقاط إضافيّة. ميدان هذا المقال هو هناك تحديداً، لذا نفصلها.

  • rename داخل الدليل نفسه على المشاركة نفسها يُنفَّذ في جانب الخادم. لذلك تُحفَظ خاصّية «الاسم الوسيط لا يُرى». بالمقابل، العبور من \\server\shareA إلى \\server\shareB يُعامَل كحجم آخر، و MoveFileEx في Windows إن عُيِّن MOVEFILE_COPY_ALLOWED يستبدل النقل بنسخ ثمّ حذف. أي أنّ العملية تفقد الذرّيّة وتظهر الحالة الوسيطة. افتراض وضع temp والاسم النهائي، و incoming و processing، داخل المشاركة نفسها أهمّ هنا ممّا هو عليه محلّيّاً
  • يفشل rename بمجرّد أنّ أحداً فتح الملفّ. على المجلّد المشترك يصل أطراف لا تعرفهم: مكافحة فيروسات، مفهرس بحث، عملاء في مواقع أخرى. تعامل فشل rename في النشر والـ claim كفرع عادي لا كشذوذ، وأعد المحاولة بعد انتظار قصير؛ هذا هو الواقعي
  • الطابع الزمني ليس مادّة حكم. في File Times على Microsoft Learn مكتوب أنّ الضمان الوحيد لأوقات الملفّ هو «أن تنعكس صحيحاً عند إغلاق الـ handle الذي أجرى التغيير». وقت آخر تعديل أثناء الكتابة لا يُحدَّث تماماً حتّى تُغلَق كلّ مقابض الكتابة. الدقّة تتبع نظام الملفّات أيضاً: آخر تعديل على FAT بوحدة ثانيتين، وآخر وصول على NTFS قد يتأخّر التحديث حتّى ساعة. وعبر SMB تُختَم الأوقات بساعة الخادم، فإن انحرفت ساعة العميل عن الخادم انحرف حكم «عالِج بعد N دقيقة من التحديث» كما هو. لهذا لا تُحكَم الاكتمال بالوقت أو الحجم، بل بـ done / manifest في 4.2
  • إشعارات التغيير تُفقَد أيضاً. لا تعتمد مراقبة المجلّد المشترك على إشعار الأحداث وحده؛ الجمع مع سرد دوري للدليل أثبت. هذا الموضوع ملخَّص في دليل FileSystemWatcher العمليّ - مواجهة الفقد والتكرار

4.2. إعلان الاكتمال بـ done / manifest

إن صرّحت في ملفّ منفصل لا عن البيانات وحدها بل عن «ما الذي اكتمل»، استقرّ المستلم. وينفع خصوصاً في تكامل أنظمة غير متجانسة.

توليد data.tmpالنشر إلى data.csvإنشاء data.done / manifest.jsonرصد المستلم لـ done / manifestالتحقّق من الاسم والحجم والـ hash

ما يستحقّ وضعه في manifest تقريباً هذه البنود.

  • اسم الملفّ المستهدف
  • الحجم
  • الـ hash
  • عدد السجلّات
  • معرّف التكامل / idempotency key
  • وقت التوليد

الترتيب مهمّ أيضاً. وضع done قبل نشر الأصل ليس إشعاراً بالاكتمال، بل إنذاراً بحادثة.

4.3. يأخذ المستلم claim ذرّيّاً

إن نظر عدّة عمّال إلى incoming نفسه، الأوضح أن «تنقل الملفّ إليك قبل القراءة». يعالج الملفّ فقط العامل الذي نجح rename من incoming إلى processing/<worker>/.

processingincomingالعامل 2العامل 1processingincomingالعامل 2العامل 1من نجح أوّلاً يأخذ الملكيّة وحدهالعثور على a.csvالعثور على a.csvrename لـ a.csvrename لـ a.csv

تشغيليّاً، فصل الأدلّة أيضاً يسهّل التتبّع.

publishclaimنجاحفشلtempincomingprocessingarchiveerror

و rename الخاصّ بالـ claim مفترض أيضاً على نظام الملفّات نفسه.

4.4. إن اعتمدت على lock file فاجعله lease

إن استخدمت lock file، فلا تجعله ملفّاً فارغاً، بل معلومات ملكيّة بمدّة صلاحية. الـ lock الذي لا يُعرف من أخذه يثير نزاعاً لاحقاً حتماً.

lock.jsonownerIdhostpidacquiredAtexpiresAtheartbeatAt

نقاط:

  • أنشئه ذرّيّاً
  • اجعل توقّف التحديث مادّة لحكم stale
  • الحذف من حيث المبدأ لمن أنشأه فقط
  • افترض تسرّب التحرير، وضع مسار الاسترداد مسبقاً

lock file علامة للتنسيق فحسب. محاولة ضمان الاتّساق الكامل بهذا الملفّ وحده تضيق في الغالب.

4.5. افترض idempotency

التحكّم الحصري مهمّ، لكن في التشغيل الفعلي لا تصفّر «يأتي مزدوجاً أحياناً» و«يُعاد التنفيذ في الأثناء». أخيراً ينفع تصميم لا ينكسر إن أكل المدخل نفسه مرّة أخرى.

نعملامدخل + idempotency keyأُعالج من قبل؟نجاح دون إعادة تنفيذتنفيذ المعالجةالتسجيل في دفتر المعالَج

مثلاً، امنح كلّ ملفّ مستلم معرّف تكامل، وسجّله في دفتر المعالَج. حتّى إن خُرِق الإقصاء مرّة، يسهل التشغيل كثيراً إن لم تُعدّ النتيجة مرّتين.

5. شيفرة تقريبية (مقتطفات)

MakeTempPathSameDirectory و TryClaimBundleByRename الآتيان أسماء دوالّ افتراضيّة لعرض الترتيب. التنفيذ الذي يعمل فعلاً في العيّنة الكاملة المذكورة في المقدّمة.

تنفيذ هذه الشيفرة التقريبية (مكتبة، عرض تنافس claim بين عاملين، اختبارات وحدة) - komurasoft-blog-samples (GitHub)

5.1. نمط فشل نموذجي

var lockPath = finalPath + ".lock";

if (!File.Exists(lockPath))
{
    File.WriteAllText(lockPath, "");
    using var writer = OpenForWrite(finalPath); // 最終名に直接書く
    WritePayload(writer);

    File.Delete(lockPath);
}

المشكلات ثلاث.

  • Exists و WriteAllText عمليّتان منفصلتان
  • finalPath يظهر أثناء الكتابة
  • يبقى lock عند الإنهاء غير الطبيعي

5.2. مثال في الاتجاه الصحيح (كتابة تقريبية)

var tempPath = MakeTempPathSameDirectory(finalPath);
WritePayload(tempPath);
FlushAndClose(tempPath);

PublishByRenameOrReplace(tempPath, finalPath); // 同一FS / 同一volume 前提
PublishDoneFile(finalPath + ".done", new
{
    FileName = Path.GetFileName(finalPath),
    Size = GetFileSize(finalPath),
    Hash = ComputeHash(finalPath),
    IdempotencyKey = integrationId
});
if (!TryClaimBundleByRename(baseName, incomingDir, processingDir))
{
    return; // 他ワーカーが先に取得
}

var manifest = ReadDoneFile(Path.Combine(processingDir, baseName + ".done"));
VerifyPayload(Path.Combine(processingDir, baseName), manifest);

if (AlreadyProcessed(manifest.IdempotencyKey))
{
    MoveBundle(processingDir, archiveDir, baseName);
    return;
}

Process(Path.Combine(processingDir, baseName));
RecordProcessed(manifest.IdempotencyKey);
MoveBundle(processingDir, archiveDir, baseName);

هنا الترتيب أهمّ من تفاصيل التنفيذ. خلط «الكتابة» و«النشر» و«أخذ الملكيّة» و«تسجيل اكتمال المعالجة» يزيد احتمال الكسر.

6. تقسيم تقريبي للاستخدام

  • كاتب واحد / قارئ واحد / المضيف نفسه: temp -> rename وحده يثبت كثيراً أصلاً
  • إن وُجد عدّة مستهلكين، أدخل claim rename من incoming إلى processing
  • تكامل أنظمة غير متجانسة، NAS، مجلّد مشترك: أأمن إدخال manifest / done و idempotency أيضاً
  • إن أراد عدّة كتّاب تحديث الحالة المنطقيّة نفسها، فلا تُفرِط في تكامل الملفّات، وانظر أيضاً في قاعدة بيانات أو طابور
  • قفل نظام التشغيل ينفع داخل مجموعة تطبيقات واحدة وبالافتراض نفسه، لكنّه لا يغني عن بروتوكول التسليم

البند الأخير حكم انسحاب أيضاً. ثمّة مشكلات تكون شاقة حقّاً إن فُرضت على الملفّات.

7. الخلاصة

التحكّم الحصري في تكامل الملفّات ليس استدعاء دالة قفل، بل تقرير انتقالات الحالة. هذا هو عمود المقال. عبّر عن قيد التوليد / منشور / قيد المعالجة / تمّت معالجته بالأسماء أو الأدلّة، وتجنّب الفحص على مرحلتين Exists -> Create، والكتابة المباشرة إلى الاسم النهائي، وانتظار استقرار الحجم، والتحديث المتبادل لملفّ مشترك، والمبالغة في الثقة بـ lock API. ثمّ اجمع temp -> close -> rename / replace و done / manifest و claim rename و lease و idempotency، فتنخفض حوادث التكامل عبر المجلّدات المشتركة كثيراً.

في تكامل الملفّات الحيلة ألّا تجعل «يمكن قراءته» و«يجوز قراءته» شيئاً واحداً. هذا الفصل وحده يقلّل كثيراً نوع الحوادث التي لا تظهر إلّا في منتصف الليل.

8. مراجع

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

هل يكفي lock API وحده للتحكّم الحصري في تكامل الملفّات؟
غالباً لا يكفي. flock على Linux هو advisory lock، فيمكن كتابة طرف يتجاهل الاتفاق بسهولة، و byte-range lock على Windows يُتجاهَل مع الملفّات المعيَّنة في الذاكرة. قفل نظام التشغيل ينفع داخل مجموعة تطبيقات واحدة وبالافتراض نفسه، لكنّه أداة مساعدة. الأساس أن يكون جسم التصميم بروتوكول التسليم: temp -> rename، و done/manifest، والـ claim الذرّي، و idempotency.
كيف أمنع قراءة ملفّ ما زال يُكتَب فيه؟
المسار المعتمد هو النشر بـ temp -> close -> rename/replace. تُحبَس الملفّات قيد التوليد تحت اسم temp، وبعد close تُبدَّل إلى الاسم النهائي في الدليل نفسه، ويراقب المستلم الاسم النهائي فقط. الشرط أن يكون temp والاسم النهائي في الدليل نفسه، أو على الأقل على الحجم / نظام الملفّات نفسه، وأن يعني ظهور الاسم النهائي أن المحتوى مكتمل.
كيف أمنع عدّة عمّال من معالجة الملفّ نفسه في الوقت ذاته؟
خذ claim ذرّيّاً قبل القراءة. عمليّاً، يعالج الملفّ فقط العامل الذي نجح rename من incoming إلى processing/<worker>/. الفحص على مرحلتين Exists -> Create يفصل «التحقّق» عن «التأمين»، فيستطيع مسار آخر أن يتدخّل بينهما، فلا يكون إقصاءً. إن احتجت إنشاءً ذرّيّاً فاستخدم عائلة FileMode.CreateNew في .NET أو O_CREAT | O_EXCL في POSIX.
ما الذي ينبغي الانتباه إليه عند استخدام lock file؟
لا تجعله ملفّاً فارغاً، بل lease (معلومات ملكيّة) بمدّة صلاحية يحمل ownerId و host و pid و acquiredAt و expiresAt و heartbeatAt. أنشئه ذرّيّاً، واستخدم توقّف التحديث مادّة لحكم stale، وليحذف الملفّ من أنشأه من حيث المبدأ، وافترض تسرّب التحرير وضع مسار الاسترداد مسبقاً. لا تحاول ضمان الاتّساق الكامل بملفّ lock واحد؛ في العمل الفعلي يكون التصميم الأقوى أن يُستقبَل الأمر أخيراً بـ idempotency.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

العودة إلى المدونة