موازنة الأحمال من جانب العميل
موازنة الأحمال من جانب العميل
في التطبيق الأحادي التقليدي تتصل بمضيف قاعدة بيانات واحد أو بخدمة واحدة في المصب. في بيئة الخدمات المصغّرة تعمل الخدمة المنطقية ذاتها على هيئة نسخ متطابقة متعددة — ثلاث نسخ من order-service، وخمس نسخ من inventory-service، وهكذا. لا بد من جهة ما تقرّر أيّ نسخة تستقبل كل طلب. يمكن أن يحدث هذا القرار في مكانين: عند عملية موازنة الأحمال المخصصة الجالسة في الوسط (الموازنة من جانب الخادم)، أو داخل الخدمة الطالِبة نفسها (الموازنة من جانب العميل). هذا الدرس مكرّس للأخيرة.
موازنة الأحمال من جانب الخادم مقابل موازنة الأحمال من جانب العميل
موازنة الأحمال من جانب الخادم تعني أن كل طلب صادر يتجه إلى IP افتراضي ثابت أو اسم DNS. أداة خارجية (HAProxy، أو AWS ALB، أو كتلة upstream في Nginx) تمتلك قائمة النسخ والخوارزمية. لا يعرف العميل سوى عنوان واحد.
موازنة الأحمال من جانب العميل تنقل تلك المسؤولية إلى الطالب. يقوم الطالب بما يلي:
- يجلب قائمة النسخ الحية من سجل الخدمات (Eureka في هيكلنا).
- يطبّق خوارزمية اختيار محلية لانتقاء نسخة واحدة.
- يرسل طلب HTTP مباشرةً إلى المضيف والمنفذ الخاصَّين بالنسخة المختارة.
الميزة هي تقليل عدد قفزات الشبكة والسيطرة الكاملة على خوارزمية التوجيه. المقايضة هي أن كل عميل يجب أن يكون ذكيًا بما يكفي للحفاظ على ذاكرة تخزين مؤقت محلية للنسخ، ومعالجة الإدخالات المنتهية الصلاحية، وإعادة المحاولة عند الفشل.
إضافة التبعية
تُحزَم موازنة الأحمال من جانب العميل ضمن المشغّل الخاص بعميل اكتشاف الخدمات. إذا كنت تستخدم مشغّل عميل Eureka بالفعل فلديك كل ما تحتاجه:
حزمة spring-cloud-starter-loadbalancer هي تبعية انتقالية لمشغّل عميل Eureka وتُشحَن تلقائيًا. لا تحتاج إلى إضافتها بشكل منفصل إلا إذا كنت تستخدم آلية اكتشاف مختلفة.
كيف يعمل Spring Cloud LoadBalancer
عند بناء عميل HTTP باستخدام WebClient (التفاعلي) أو RestClient / RestTemplate (المتزامن)، تُضيف التعليق التوضيحي @LoadBalanced على الـ @Bean الذي يبنيه. يُغلّف Spring Cloud العميل الناتج بمعترض (interceptor). عند وقت الاستدعاء يقوم هذا المعترض بما يلي:
- يكتشف اسم مضيف يطابق اسم خدمة مسجّل (مثل
http://order-service/...). - يستعلم عن ذاكرة التخزين المؤقت المحلية لـ ServiceInstanceListSupplier، التي تُحدَّث دوريًا من Eureka.
- يشغّل الخوارزمية المضبوطة — round-robin افتراضيًا — لاختيار نسخة.
- يعيد كتابة عنوان URL إلى
host:portالحقيقي للنسخة المختارة ثم يمضي قُدُمًا.
استخدام WebClient ذي موازنة الأحمال
يُعدّ WebClient التفاعلي العميل HTTP الموصى به لخدمات Spring Boot 3. اضبط bean لـ builder ذي موازنة أحمال مرة واحدة في فئة @Configuration:
أدرج الـ builder في أي bean خدمة يحتاج إلى استدعاء خدمة أخرى. استخدم الاسم المنطقي للخدمة بالضبط كما هو مسجّل في Eureka — حالة الأحرف مهمة:
http://inventory-service:8082). يستبدل LoadBalancer الجزء الكامل الخاص بالمضيف؛ وإضافة منفذ تجعل الاستبدال غامضًا وتؤدي إلى فشل في وقت التشغيل.
استخدام RestClient ذي موازنة الأحمال (Spring Boot 3.2 فأحدث)
RestClient هو البديل المتزامن الذي قُدِّم في Spring Framework 6.1. تحصل على نفس نمط التعليق التوضيحي @LoadBalanced:
تغيير خوارزمية موازنة الأحمال
الخوارزمية الافتراضية هي round-robin: يُوزَّع الحمل بالتساوي على جميع النسخ الصحية بالتناوب. يشحن Spring Cloud LoadBalancer أيضًا خوارزمية عشوائية. يمكنك التبديل لكل خدمة على حدة بتوفير فئة إعداد مخصصة وربطها عبر @LoadBalancerClient:
ثم أشِر إلى فئة الإعداد تلك على نقطة الدخول للتطبيق أو أي فئة @Configuration:
جميع استدعاءات inventory-service ستستخدم الاختيار العشوائي الآن؛ واستدعاءات كل خدمة أخرى ستبقى على round-robin.
ذاكرة التخزين المؤقت للنسخ وتصفية الحالة الصحية
يخزّن Spring Cloud LoadBalancer قائمة النسخ مؤقتًا في خط أنابيب ServiceInstanceListSupplier. بشكل افتراضي يُحدَّث كل 35 ثانية. يمكنك ضبط هذا في application.yml:
يدعم خط أنابيب المورّد أيضًا مرشّح الفحص الصحي. فعّله لاستبعاد النسخ التي تُبلّغ عن حالة DOWN في Eureka تلقائيًا:
الاعتبار الأمني: mTLS عبر النسخ
موازنة الأحمال من جانب العميل تعني أن خدمتك تفتح اتصال TCP مباشرًا لكل نسخة. في بيئة الإنتاج يجب تشفير تلك الاتصالات والتحقق المتبادل من هويتها — وإلا يمكن لخدمة داخلية مخترَقة انتحال هوية أي نسخة. الأنماط الشائعة هي:
- شبكة الخدمات (Istio / Linkerd): يُعالَج mTLS بشفافية على مستوى البروكسي الجانبي (sidecar)؛ يرى كود تطبيقك HTTP عاديًا.
- ضبط TLS للعميل في Spring Boot: اضبط truststore و keystore على
WebClient/RestClientلـ mTLS مباشر بدون شبكة خدمات. - نشر JWT: مرّر رمز bearer الخاص بالطالب في المصب مع كل استدعاء بين الخدمات كي تتمكّن الخدمات في المصب من تطبيق التفويض باستقلالية.
الخلاصة
تمنح موازنة الأحمال من جانب العميل مع Spring Cloud LoadBalancer كل خدمة موجّهًا محليًا مدعومًا بالسجل. تُضيف التعليق التوضيحي @LoadBalanced على bean الخاص بـ WebClient.Builder أو RestClient.Builder، وتخاطب الخدمات في المصب باسم خدمتها في Eureka، فيعمل إطار العمل على التحليل والتناوب بين النسخ الحقيقية في وقت الاستدعاء. round-robin هو الافتراضي؛ الاختيار العشوائي والخوارزميات المخصصة تُربط لكل خدمة على حدة عبر @LoadBalancerClient. اضبط مدة TTL لذاكرة تخزين النسخ المؤقتة، وفعّل تصفية الفحص الصحي، وادمج مع آلية إعادة المحاولة لمعالجة سيناريوهات الإدخالات المنتهية الصلاحية والنسخ المتعطلة الحتمية في الإنتاج.