تلعب موازنات التحميل دورًا رئيسيًا في هندسة الويب. إنها تسمح لك بتوزيع الحمل عبر عدة واجهات خلفية ، وبالتالي تحسين قابلية التوسع. ونظرًا لتكوين العديد من الواجهة الخلفية ، تصبح الخدمة متاحة للغاية ، لأنه في حالة حدوث عطل في أحد الخوادم ، يمكن للموازن اختيار خادم عمل آخر.
بعد أن لعبت مع موازنات احترافية مثل NGINX ، حاولت إنشاء موازن بسيط للمتعة. لقد كتبت على Go ، إنها لغة حديثة تدعم التوازي الكامل. تحتوي المكتبة القياسية في Go على العديد من الميزات وتتيح لك كتابة تطبيقات عالية الأداء برمز أقل. بالإضافة إلى ذلك ، لسهولة التوزيع ، فإنه يولد ثنائية واحدة مرتبطة بشكل ثابت.
كيف يعمل موازننا
يتم استخدام خوارزميات مختلفة لتوزيع الحمل بين الأسطح الخلفية. على سبيل المثال:
- Round Robin - يتم توزيع الحمل بالتساوي ، مع مراعاة نفس القوة الحاسوبية للخوادم.
- مرجح جولة روبن - اعتمادا على قوة المعالجة ، يمكن تعيين خوادم أوزان مختلفة.
- الاتصالات الأقل - يتم توزيع الحمل عبر الخوادم التي تحتوي على أقل عدد من الاتصالات النشطة.
في موازننا ، ننفذ أبسط الخوارزميات - Round Robin.
اختيار في جولة روبن
خوارزمية Round Robin بسيطة. إنه يعطي جميع فناني الأداء نفس الفرصة لإكمال المهام.
حدد الخوادم في Round Robin لمعالجة الطلبات الواردة.كما هو موضح في الرسم التوضيحي ، تحدد الخوارزمية الخوادم في دائرة بشكل دوري. لكن لا يمكننا اختيارهم
مباشرة ، أليس كذلك؟
وإذا كان الخادم يكذب؟ ربما لا نحتاج إلى إرسال حركة مرور إليه. وهذا يعني أنه لا يمكن استخدام الخادم مباشرة حتى نصل إلى الحالة المطلوبة. من الضروري توجيه حركة المرور فقط إلى الخوادم التي يتم تشغيلها وتشغيلها.
تحديد الهيكل
نحن بحاجة إلى تتبع جميع التفاصيل المتعلقة الخلفية. تحتاج إلى معرفة ما إذا كان على قيد الحياة ، وتتبع عنوان URL. للقيام بذلك ، يمكننا تحديد الهيكل التالي:
type Backend struct { URL *url.URL Alive bool mux sync.RWMutex ReverseProxy *httputil.ReverseProxy }
لا تقلق ، سأشرح معنى الحقول في الخلفية.
الآن في الموازن ، تحتاج إلى تتبع كل المؤثرات الخلفية بطريقة أو بأخرى. للقيام بذلك ، يمكنك استخدام Slice وعداد متغير. حددها في ServerPool:
type ServerPool struct { backends []*Backend current uint64 }
باستخدام ReverseProxy
كما حددنا بالفعل ، فإن جوهر الموازن هو توزيع حركة المرور على خوادم مختلفة وإعادة النتائج إلى العميل. كما تقول وثائق Go:
ReverseProxy هو معالج HTTP يأخذ الطلبات الواردة ويرسلها إلى خادم آخر ، ويعيد الإجابات إلى العميل.بالضبط ما نحتاجه. لا حاجة لإعادة اختراع العجلة. يمكنك ببساطة دفق طلباتنا من خلال
ReverseProxy
.
u, _ := url.Parse("http://localhost:8080") rp := httputil.NewSingleHostReverseProxy(u)
باستخدام
httputil.NewSingleHostReverseProxy(url)
يمكنك تهيئة
ReverseProxy
، والذي سيبث الطلبات إلى
url
تم تمريره. في المثال أعلاه ، تم إرسال جميع الطلبات إلى المضيف المحلي: 8080 ، وتم إرسال النتائج إلى العميل.
إذا نظرت إلى توقيع أسلوب ServeHTTP ، فيمكنك العثور على توقيع معالج HTTP فيه. لذلك ، يمكنك تمريره إلى
HandlerFunc
في
http
.
أمثلة أخرى في
الوثائق .
بالنسبة إلى
ReverseProxy
، يمكنك بدء
ReverseProxy
URL
المرتبط في
Backend
بحيث يقوم ReverseProxy بتوجيه الطلبات إلى
URL
.
عملية اختيار الخادم
أثناء اختيار الخادم التالي ، نحتاج إلى تخطي الخوادم الأساسية. ولكن تحتاج إلى تنظيم العد.
سيتصل العديد من العملاء بالموازن ، وعندما يطلب كل منهم العقدة التالية لنقل حركة المرور ، قد تحدث حالة سباق. لمنع هذا ، يمكننا حظر
ServerPool
مع
mutex
. ولكن سيكون هذا
ServerPool
، إلى جانب أننا لا نريد حظر
ServerPool
. نحن فقط بحاجة إلى زيادة العداد من جانب واحد.
أفضل حل لتلبية هذه المتطلبات هو الزيادة الذرية. Go يدعمها مع الحزمة
atomic
.
func (s *ServerPool) NextIndex() int { return int(atomic.AddUint64(&s.current, uint64(1)) % uint64(len(s.backends))) }
نحن نزيد القيمة الحالية بمقدار واحد ونعيد الفهرس عن طريق تغيير طول المصفوفة. هذا يعني أن القيمة يجب أن تكون دائمًا في النطاق من 0 إلى طول المصفوفة. في النهاية ، سنهتم بمؤشر محدد ، وليس العداد بأكمله.
اختيار خادم الحية
نحن نعلم بالفعل أن طلباتنا يتم تدويرها دوريًا عبر جميع الخوادم. ونحن بحاجة فقط لتخطي الخمول.
إرجاع
GetNext()
دائمًا قيمة تتراوح من 0 إلى طول الصفيف. في أي وقت ، يمكننا الحصول على العقدة التالية ، وإذا كانت غير نشطة ، فنحن بحاجة إلى مزيد من البحث من خلال المصفوفة كجزء من الحلقة.
نحن حلقة من خلال مجموعة.كما هو موضح في الرسم التوضيحي ، نريد الانتقال من العقدة التالية إلى نهاية القائمة. يمكن القيام بذلك باستخدام
next + length
. ولكن لتحديد فهرس ، تحتاج إلى قصره على طول الصفيف. يمكن القيام بذلك بسهولة باستخدام عملية التعديل.
بعد أن وجدنا خادمًا عاملاً أثناء البحث ، يجب وضع علامة عليه كخادم:
تجنب حالة السباق في بنية Backend
هنا تحتاج إلى تذكر قضية مهمة. تحتوي بنية
Backend
على متغير يمكن للعديد من goroutines تعديله أو الاستعلام عنه في نفس الوقت.
نحن نعلم أن goroutines سيقرأ المتغير أكثر من الكتابة إليه. لذلك ، لتسلسل الوصول إلى
Alive
اخترنا
RWMutex
.
تحقيق التوازن بين الطلبات
الآن يمكننا صياغة طريقة بسيطة لموازنة طلباتنا. سوف تفشل فقط إذا سقطت جميع الخوادم.
يمكن تمرير هذه الطريقة إلى خادم HTTP ببساطة مثل
HandlerFunc
.
server := http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: http.HandlerFunc(lb), }
نحن نوجه حركة المرور فقط إلى الخوادم التي تعمل
موازن لدينا لديه مشكلة خطيرة. لا نعرف ما إذا كان الخادم يعمل أم لا. لمعرفة ذلك ، تحتاج إلى التحقق من الخادم. هناك طريقتان للقيام بذلك:
- نشط: تنفيذ الطلب الحالي ، نجد أن الخادم المحدد لا يستجيب ، ووضع علامة عليه في وضع الخمول.
- المبني للمجهول: يمكنك اختبار اتصال خوادم في فترة زمنية معينة والتحقق من الحالة.
التحقق بنشاط الخوادم قيد التشغيل
في حالة
ReverseProxy
أي خطأ
ReverseProxy
يبدأ
ReverseProxy
وظيفة رد الاتصال
ErrorHandler
. هذا يمكن استخدامه للكشف عن الفشل:
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) { log.Printf("[%s] %s\n", serverUrl.Host, e.Error()) retries := GetRetryFromContext(request) if retries < 3 { select { case <-time.After(10 * time.Millisecond): ctx := context.WithValue(request.Context(), Retry, retries+1) proxy.ServeHTTP(writer, request.WithContext(ctx)) } return }
في تطوير معالج الأخطاء هذا ، استخدمنا إمكانيات عمليات الإغلاق. هذا يتيح لنا التقاط متغيرات خارجية مثل عناوين URL للخوادم في طريقتنا. يتحقق المعالج من عداد إعادة المحاولة ، وإذا كان أقل من 3 ، فإننا نرسل الطلب نفسه مرة أخرى إلى نفس الخادم. هذا لأنه بسبب الأخطاء المؤقتة ، قد يتخلى الخادم عن طلباتنا ، لكنه سيصبح متاحًا قريبًا (قد لا يكون لدى الخادم مآخذ توصيل مجانية للعملاء الجدد). لذلك تحتاج إلى ضبط مؤقت التأخير لمحاولة جديدة بعد حوالي 10 مللي ثانية. مع كل طلب نقوم بزيادة عداد المحاولات.
بعد فشل كل محاولة ، نحتفل على الخادم باعتباره خاملاً.
أنت الآن بحاجة إلى تعيين خادم جديد لنفس الطلب. سنفعل ذلك باستخدام عداد العداد باستخدام حزمة
context
. بعد زيادة عداد المحاولات ، نقوم بتمريرها إلى
lb
لتحديد خادم جديد لمعالجة الطلب.
لا يمكننا القيام بذلك إلى أجل غير مسمى ، لذلك سنقوم بالتحقق مما إذا كان قد تم الوصول إلى الحد الأقصى لعدد المحاولات قبل متابعة معالجة الطلب.
يمكنك ببساطة الحصول على عداد المحاولات من الطلب ، إذا وصل إلى الحد الأقصى ، فسنقطع الطلب.
هذا هو تنفيذ العودية.
باستخدام حزمة السياق
تسمح لك حزمة
context
بتخزين البيانات المفيدة في طلبات HTTP. سوف نستخدم هذا بنشاط لتتبع البيانات المتعلقة بالطلبات - عدادات
Retry
وإعادة
Retry
.
أولاً ، تحتاج إلى تعيين مفاتيح السياق. من المستحسن استخدام لا سلسلة ، ولكن القيم العددية الفريدة. تحتوي Go على كلمة رئيسية
iota
للتطبيق التدريجي للثوابت ، يحتوي كل منها على قيمة فريدة. هذا هو الحل الأمثل لتعريف المفاتيح الرقمية.
const ( Attempts int = iota Retry )
يمكنك بعد ذلك استخراج القيمة ، كما نفعل عادةً مع
HashMap
. قد تعتمد القيمة الافتراضية على الموقف الحالي.
خادم السلبي التحقق من الصحة
الشيكات السلبية تحديد واستعادة الخوادم الساقطة. نحن نقوم بتجربتها في فترة زمنية معينة لتحديد حالتها.
لإجراء اختبار ping ، حاول تأسيس اتصال TCP. إذا كان الخادم يستجيب ، فإننا نحتفل به. يمكن تكييف هذه الطريقة لاستدعاء نقاط نهاية معينة مثل
/status
. تأكد من إغلاق الاتصال بعد إنشائه لتقليل الحمل الإضافي على الخادم. وإلا ، فسيحاول الحفاظ على هذا الاتصال وسيستنفد موارده في النهاية.
يمكنك الآن تكرار الخوادم وتحديد حالاتها:
لتشغيل هذا الرمز بشكل دوري ، يمكنك تشغيل المؤقت في Go. سيتيح لك الاستماع إلى الأحداث في القناة.
في هذا الرمز ، تقوم قناة
<-tC
بإرجاع قيمة كل 20 ثانية.
select
يسمح لك لتحديد هذا الحدث. في حالة عدم وجود موقف
default
، ينتظر حتى يتم تنفيذ حالة واحدة على الأقل.
الآن قم بتشغيل الكود في مجموعة منفصلة:
go healthCheck()
استنتاج
في هذه المقالة ، درسنا العديد من الأسئلة:
- جولة روبن خوارزمية
- ReverseProxy من المكتبة القياسية
- كائنات المزامنة
- العمليات الذرية
- دوائر قصيرة
- الاسترجاعات
- عملية الاختيار
هناك العديد من الطرق لتحسين الموازن لدينا. على سبيل المثال:
- استخدم الكومة لفرز الخوادم الحية لتقليل نطاق البحث.
- جمع الإحصاءات.
- قم بتطبيق خوارزمية الدارة المستديرة الموزونة مع أقل عدد من الاتصالات.
- إضافة دعم لملفات التكوين.
و هكذا.
شفرة المصدر
هنا .