ما هو Ticketing؟
Ticketing هو خادم أقفال مخصص، مصمم لمهمة واحدة فقط — القفل الموزّع. إنه ثنائي خادم Rust واحد مقترن بعملاء رسميين لكل لغة، جميعهم ينفذون نفس البروتوكول الثنائي، بايتًا ببايت. عمليتان فقط — الحصول (acquire) والتحرير (release) — وطابور FIFO لكل مفتاح، ورموز Fencing، وعنقود Raft اختياري تُفعّله عند الحاجة. كل ما يحتاجه القفل الموزّع، ولا شيء آخر.
ما الفرق عن الأقفال المبنية على مخزن بيانات؟
معظم الأقفال الموزّعة في الإنتاج تعتمد على مخزن key-value — سكربت Lua أو Redlock فوق Redis، أو قفل مبني على جلسات وإيجارات ZooKeeper أو etcd. Redis مخزن ممتاز، لكنه مخزن key-value أُعيد توظيفه للقفل الموزّع، وليس نظام أقفال مخصصًا، وهذا يظهر كنفقات بنيوية زائدة: تكلفة تحليل بروتوكول عام الغرض، ونفقات بنى البيانات عامة الغرض، كلاهما يرافقان كل عملية.
صُمم Ticketing حصريًا للقفل الموزّع منذ اليوم الأول، فكل طبقة فيه مبنية حول الأقفال.
- البروتوكول — حقول ثنائية بعرض ثابت، لا نص ولا تنسيق تسلسل عام الغرض. تُقرأ القيم مباشرة عبر إزاحات البايت دون أي تحليل، ويتيح الأنبوب إرسال الطلب التالي دون انتظار الاستجابة السابقة.
- داخل الخادم — الحالة بأكملها طابور FIFO لكل مفتاح مع مؤقت إيجار (lease). مسار معالجة الطلب لا يخصّص ذاكرة على الكومة إطلاقًا.
- التشغيل — شغّل ثنائيًا واحدًا للخادم (أو حاوية)، واضبطه ببضعة متغيرات بيئة، وانتهى الأمر. لا حاجة لتصميم مخطط، ولا مخزن منفصل، ولا معرفة تشغيلية غير متعلقة بالقفل.
الأداء
- يعالج أكثر من مليون عملية في الثانية باستخدام أقل من 10 ميغابايت من الذاكرة
لماذا Ticketing
🚦 نزاهة الترتيب — طابور FIFO مع تسليم مباشر
الأول في الانتظار هو الأول في الخدمة. عندما يحرر الحائز القفل، يُسلَّم مباشرة إلى مقدمة الطابور دون إعادة تنافس. لا مجاعة (starvation) ناتجة عن التنافس بالاستطلاع وإعادة المحاولة، ولا عواصف إعادة محاولة.
🛟 موت العميل لا يُبقي القفل عالقًا — الإيجار (lease)
إذا مات العميل الحائز للقفل أو انقطع اتصاله، يستعيد الخادم القفل تلقائيًا بعد انقضاء مدة lease المحددة عند الحصول عليه، ويسلّمه للمنتظر التالي. نسيان التحرير لا يوقف النظام بأكمله.
🔑 منع تنفيذ العمل خارج الترتيب — رموز Fencing
هناك عطل واحد لا يستطيع القفل وحده منعه: حائز يستيقظ متأخرًا — دون علم بانتهاء lease الخاص به — ويطلب إنهاء عمله رغم ذلك. يصدر Ticketing رمز Fencing متزايدًا دائمًا مع كل عملية حصول. ما دام المورد المحمي يفرض قاعدة واحدة — "ارفض أي رمز أقل من آخر رمز شوهد" — يُسد حتى هذا الثغرة الأخيرة. راجع آلية العمل للشرح الكامل.
🗳️ عنقود بلا توقف مبني على Raft
خادم واحد يكفي بذاته، لكن عندما تحتاج انعدام التوقف، شكّل عنقودًا من 3 (أو 5) عُقد. كل تغيير في حالة القفل لا يُثبَّت إلا بعد موافقة أغلبية العُقد عبر إجماع Raft، لذا لا يُصدَر نفس القفل مرتين في آن واحد حتى لو مات القائد أو انقسمت الشبكة. إذا فشل القائد، يُنتخب قائد جديد خلال ثانية تقريبًا وينتقل العملاء تلقائيًا. تستمر الخدمة حتى عند سقوط العُقد واحدة تلو الأخرى — كما يحدث أثناء إعادة تشغيل مثيلات السحابة أو الصيانة المتسلسلة.
🔒 مصادقة وTLS مدمجان
يمر كل اتصال بمصادقة رمز challenge-response فور الاتصال، ولا يُرسَل الرمز عبر السلك كنص صريح أبدًا. عمّم TLS على الاتصال بأكمله متى احتجت ذلك.
🌐 نفس البروتوكول، بايتًا ببايت، عبر كل لغة رئيسية
Rust، Java/Kotlin، JavaScript/TypeScript، Python، C#، Go، Ruby، C/C++ — كل عميل رسمي ينفذ نفس البروتوكول الثنائي، بواجهة برمجة طبيعية تناسب بيئة كل لغة (غير متزامنة أو حاجبة). أي قفل يُحصَّل من أي لغة يقف في الطابور بعدالة مع عملاء مكتوبين بأي لغة أخرى. راجع المكتبات لتعليمات التثبيت والأمثلة.
عمليتان فقط
اتساقًا مع كونه خادمًا مخصصًا للأقفال فقط، سطحه أيضًا في أدنى حد.
- اربط مفتاحًا بالمورد الذي تريد حمايته — مثل
order-1234،account-77،daily-batch. - احصل (acquire) على ذلك المفتاح قبل تنفيذ العمل — إن كان شخص آخر يحوزه، تنتظر دورك في طابور FIFO.
- حرّر (release) المفتاح عند انتهاء العمل — يتولاه المنتظر التالي فورًا دون إعادة تنافس.
الخياران الوحيدان فوق هاتين العمليتين هما lease (استعادة تلقائية) وwaitTimeout (التخلي عن الانتظار) — هذه هي الواجهة البرمجية بأكملها.
ماذا تقرأ بعد ذلك
- كيف تُخصَّص الأقفال فعليًا وكيف يبقى العنقود قائمًا — آلية العمل
- المواصفات الدقيقة على مستوى البايت لما يعبر السلك — البروتوكول (API)
- إنشاء أمر تشغيل الخادم — نشر خادم Ticketing