مستوى المقال: متوسط (Intermediate) — يفترض إنك مرتاح مع Node.js و Redis الأساسيات و async/await، ومش لازم تعرف distributed systems من قبل.
تخيّل السيناريو ده: عندك Job Queue بيرسل إيميل ترحيب للمستخدم الجديد. الـ queue شغّال على 5 workers بـ Bull أو BullMQ. مرة واحدة، الـ Redis متلخبط لمدة ثانيتين، وفجأة 3 workers قروا نفس الـ job لأن الـ visibility timeout انتهى. النتيجة؟ المستخدم استلم 3 إيميلات ترحيب، وفريق الدعم بيرد على شكاوى من 8 الصبح.
المشكلة دي اسمها double processing، وحلها بسطر واحد ذكي في Redis اسمه SET NX PX. المقال ده هيوريك بالظبط ازاي تبني distributed lock محترم في 50 سطر Node.js، أرقامه مقاسة على 4 workers متزامنين بـ 1,200 job/دقيقة، وامتى الـ lock نفسه يبقى الاختيار الغلط.
Distributed Lock بـ Redis: المفتاح اللي بيمنع double processing
المشكلة باختصار
أي عملية ما تحتمل تتنفّذ مرتين بالغلط — إرسال إيميل، خصم فلوس، توليد فاتورة PDF، استدعاء Stripe API — لازم يبقى ليها قفل بيمنع worker تاني من تنفيذها في نفس اللحظة. لو الـ workers على نفس السيرفر، Mutex في الذاكرة بيكفي. لكن لما يبقوا على 5 سيرفرات مختلفة، الذاكرة المحلية ما بتتشاركش، ولازم mutex مركزي يشوفه الكل. ده اللي اسمه distributed lock.
مثال للمبتدئ: مفتاح غرفة الاجتماعات
تخيّل غرفة اجتماعات في شركة فيها 5 مدراء يبصّوا فيها بشكل دوري. مفيش أكتر من واحد يقدر يدخلها في نفس الوقت لأن السكرتارية بتدّي مفتاح واحد بس. لما حد ياخد المفتاح، الباقيين لازم يستنوا. لما يخلّص، يرجّع المفتاح. ولو نسيه في جيبه ومشي البيت، السكرتارية عندها مفتاح احتياطي بيشتغل بعد 30 دقيقة بحد أقصى — يعني حتى لو الـ lock اتنسى، فيه TTL بيحرّر الغرفة تلقائياً.
Redis distributed lock بنفس الفكرة بالظبط: مفتاح اسمه lock:send-welcome-email:user-789، أول worker بياخده، وأي worker تاني بيلاقيه مأخود فيستنى أو يعمل skip. والـ TTL بيضمن إن الـ lock ما يفضلش متعلّق لو الـ worker ضاع.
التعريف العلمي: SET NX PX و atomicity
الأمر الأساسي اللي بيشتغل عليه كل الفكرة هو:
SET lock:job-123 worker-A NX PX 30000تفصيل العلامات حسب توثيق Redis الرسمي:
NX: نفّذ الكتابة بس لو المفتاح مش موجود. لو موجود، Redis بيرجّعnilومفيش حاجة بتتغيّر.PX 30000: لو نجحت الكتابة، خلّي المفتاح يعيش 30,000 مللي ثانية بس وبعدها يموت تلقائياً.- القيمة
worker-A: مهمة جداً لاحقاً عشان تتأكد إن الـ worker اللي بيحرّر الـ lock هو نفسه اللي قفله.