SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

يعد تحليل الأداء وضبطه أداة قوية للتحقق من امتثال الأداء للعملاء.

يمكن استخدام تحليل الأداء للتحقق من الاختناقات في البرنامج من خلال تطبيق منهج علمي لاختبار تجارب الضبط. تحدد هذه المقالة منهجًا عامًا لتحليل الأداء وضبطه، باستخدام خادم الويب Go كمثال.

يعد Go جيدًا بشكل خاص هنا لأنه يحتوي على أدوات إنشاء ملفات تعريف pprof في المكتبة القياسية.

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

إستراتيجية

لنقم بإنشاء قائمة ملخصة لتحليلنا الهيكلي. سنحاول استخدام بعض البيانات لاتخاذ القرارات بدلاً من إجراء تغييرات بناءً على الحدس أو التخمين. للقيام بذلك سنفعل هذا:

  • نحدد حدود التحسين (المتطلبات)؛
  • نحن نحسب حمل المعاملات للنظام؛
  • نقوم بإجراء الاختبار (إنشاء البيانات)؛
  • نلاحظ؛
  • نحن نحلل - هل تم استيفاء جميع المتطلبات؟
  • قمنا بإعدادها بشكل علمي، ووضع فرضية؛
  • نقوم بإجراء تجربة لاختبار هذه الفرضية.

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

بنية خادم HTTP بسيطة

في هذه المقالة سوف نستخدم خادم HTTP صغير في Golang. يمكن العثور على كافة التعليمات البرمجية من هذه المقالة هنا.

التطبيق الذي يتم تحليله هو خادم HTTP يقوم باستقصاء Postgresql لكل طلب. بالإضافة إلى ذلك، هناك Prometheus وnode_exporter وGrafana لجمع وعرض مقاييس التطبيق والنظام.

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

للتبسيط، نعتبر أنه من أجل القياس الأفقي (وتبسيط الحسابات)، يتم نشر كل خدمة وقاعدة بيانات معًا:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

تحديد الأهداف

في هذه الخطوة نحدد الهدف. ما الذي نحاول تحليله؟ كيف نعرف متى يحين وقت الانتهاء؟ في هذه المقالة، سنتخيل أن لدينا عملاء وأن خدمتنا ستقوم بمعالجة 10 طلب في الثانية.

В كتاب جوجل SRE وتناقش طرق الاختيار والنمذجة بالتفصيل. دعونا نفعل الشيء نفسه ونبني النماذج:

  • الكمون: يجب إكمال 99% من الطلبات في أقل من 60 مللي ثانية؛
  • التكلفة: يجب أن تستهلك الخدمة الحد الأدنى من المال الذي نعتقد أنه ممكن بشكل معقول. وللقيام بذلك، نقوم بزيادة الإنتاجية إلى الحد الأقصى؛
  • تخطيط السعة: يتطلب فهم وتوثيق عدد مثيلات التطبيق التي يجب تشغيلها، بما في ذلك وظيفة القياس الشاملة، وعدد المثيلات المطلوبة لتلبية متطلبات التحميل والتوفير الأولية التكرار ن +1.

قد يتطلب زمن الوصول التحسين بالإضافة إلى التحليل، ولكن من الواضح أن الإنتاجية تحتاج إلى التحليل. عند استخدام عملية SRE SLO، يأتي طلب التأخير من العميل أو الشركة، التي يمثلها مالك المنتج. وسوف تفي خدمتنا بهذا الالتزام منذ البداية دون أي إعدادات!

إعداد بيئة الاختبار

بمساعدة بيئة الاختبار، سنكون قادرين على وضع حمل مُقاس على نظامنا. للتحليل، سيتم إنشاء بيانات حول أداء خدمة الويب.

تحميل المعاملات

تستخدم هذه البيئة نبت لإنشاء معدل طلب HTTP مخصص حتى يتم إيقافه:

$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta report

مراقبة

سيتم تطبيق تحميل المعاملات في وقت التشغيل. بالإضافة إلى مقاييس التطبيق (عدد الطلبات وزمن الاستجابة) ونظام التشغيل (الذاكرة، وحدة المعالجة المركزية، IOPS)، سيتم تشغيل ملف تعريف التطبيق لفهم أين توجد مشكلات وكيف يتم استهلاك وقت وحدة المعالجة المركزية.

التنميط

يعد التوصيف أحد أنواع القياس الذي يسمح لك بمعرفة أين يتجه وقت وحدة المعالجة المركزية عند تشغيل التطبيق. يسمح لك بتحديد المكان الدقيق ومقدار وقت المعالج الذي يتم إنفاقه:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

يمكن استخدام هذه البيانات أثناء التحليل للحصول على نظرة ثاقبة لوقت وحدة المعالجة المركزية الضائع والعمل غير الضروري الذي يتم تنفيذه. يمكن لـ Go (pprof) إنشاء ملفات تعريف وتصورها كرسوم بيانية لهب باستخدام مجموعة قياسية من الأدوات. سأتحدث عن دليل الاستخدام والإعداد الخاص بهم لاحقًا في المقالة.

التنفيذ والملاحظة والتحليل.

دعونا نجري تجربة. سنقوم بالأداء والمراقبة والتحليل حتى نشعر بالرضا عن الأداء. دعونا نختار قيمة تحميل منخفضة بشكل تعسفي لتطبيقها للحصول على نتائج الملاحظات الأولى. في كل خطوة لاحقة، سنقوم بزيادة الحمل باستخدام عامل قياس معين يتم اختياره مع بعض الاختلاف. يتم تنفيذ كل اختبار تحميل مع تعديل عدد الطلبات: make load-test LOAD_TEST_RATE=X.

50 طلبًا في الثانية

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

انتبه إلى الرسمين البيانيين العلويين. يوضح الجزء العلوي الأيسر أن تطبيقنا يعالج 50 طلبًا في الثانية (يعتقد) ويوضح الجزء العلوي الأيمن مدة كل طلب. تساعدنا كلا المعلمتين في النظر وتحليل ما إذا كنا ضمن حدود أدائنا أم لا. الخط الأحمر على الرسم البياني وقت استجابة طلب HTTP يظهر SLO عند 60 مللي ثانية. يوضح الخط أننا أقل بكثير من الحد الأقصى لوقت الاستجابة.

دعونا ننظر إلى جانب التكلفة:

10000 طلب في الثانية / 50 طلبًا لكل خادم = 200 خادم + 1

لا يزال بإمكاننا تحسين هذا الرقم.

500 طلبًا في الثانية

تبدأ الأشياء الأكثر إثارة للاهتمام في الحدوث عندما يصل التحميل إلى 500 طلب في الثانية:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

مرة أخرى، في الرسم البياني العلوي الأيسر يمكنك أن ترى أن التطبيق يسجل التحميل العادي. إذا لم يكن الأمر كذلك، فهناك مشكلة على الخادم الذي يعمل عليه التطبيق. يقع الرسم البياني لزمن الاستجابة في أعلى اليمين، ويوضح أن 500 طلب في الثانية أدت إلى تأخير الاستجابة بمقدار 25-40 مللي ثانية. لا تزال النسبة المئوية الـ 99 تتناسب بشكل جيد مع SLO الذي يبلغ 60 مللي ثانية والذي تم اختياره أعلاه.

من حيث التكلفة:

10000 طلب في الثانية / 500 طلبًا لكل خادم = 20 خادم + 1

لا يزال من الممكن تحسين كل شيء.

1000 طلبًا في الثانية

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

إطلاق عظيم! يظهر التطبيق أنه قام بمعالجة 1000 طلب في الثانية، ولكن تم انتهاك حد زمن الوصول بواسطة SLO. يمكن رؤية ذلك في السطر p99 في الرسم البياني العلوي الأيمن. على الرغم من أن خط p100 أعلى بكثير، إلا أن التأخير الفعلي أعلى من الحد الأقصى وهو 60 مللي ثانية. دعنا نتعمق في عملية التنميط لمعرفة ما يفعله التطبيق بالفعل.

التنميط

للتوصيف، قمنا بتعيين التحميل على 1000 طلب في الثانية، ثم استخدمه pprof لالتقاط البيانات لمعرفة المكان الذي يقضي فيه التطبيق وقت وحدة المعالجة المركزية. يمكن القيام بذلك عن طريق تنشيط نقطة نهاية HTTP pprof، وبعد ذلك، تحت التحميل، احفظ النتائج باستخدام الضفيرة:

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

ويمكن عرض النتائج على النحو التالي:

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

يوضح الرسم البياني أين وكم يقضي التطبيق وقت وحدة المعالجة المركزية. من الوصف من بريندان جريج:

المحور X هو مجموعة ملفات تعريف المكدس، مرتبة أبجديًا (هذا ليس الوقت)، ويوضح المحور Y عمق المكدس، من الصفر في [أعلى]. كل مستطيل عبارة عن إطار مكدس. كلما كان الإطار أوسع، كلما كان موجودًا في الأكوام. ما هو في الأعلى يعمل على وحدة المعالجة المركزية، وما هو أدناه هو العناصر الفرعية. الألوان عادة لا تعني أي شيء، ولكن يتم اختيارها عشوائيًا للتمييز بين الإطارات.

التحليل - الفرضية

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

باتباع توصيات بريندان جريج، سنقرأ الرسم البياني من الأعلى إلى الأسفل. يعرض كل سطر إطارًا مكدسًا (استدعاء دالة). السطر الأول هو نقطة الدخول إلى البرنامج، وهو الأصل لجميع الاستدعاءات الأخرى (وبعبارة أخرى، جميع الاستدعاءات الأخرى ستكون موجودة في مكدسها). السطر التالي مختلف بالفعل:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

إذا قمت بتمرير المؤشر فوق اسم دالة على الرسم البياني، فسيتم عرض إجمالي الوقت الذي كانت فيه على المكدس أثناء تصحيح الأخطاء. كانت وظيفة HTTPServe موجودة بنسبة 65% من الوقت، بالإضافة إلى وظائف أخرى في وقت التشغيل runtime.mcall, mstart и gc، واستغرق بقية الوقت. حقيقة ممتعة: يتم قضاء 5% من إجمالي الوقت في استعلامات DNS:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

العناوين التي يبحث عنها البرنامج تنتمي إلى Postgresql. انقر فوق FindByAge:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

ومن المثير للاهتمام أن البرنامج يوضح، من حيث المبدأ، أن هناك ثلاثة مصادر رئيسية تزيد من التأخير: فتح وإغلاق الاتصالات، وطلب البيانات، والاتصال بقاعدة البيانات. يوضح الرسم البياني أن طلبات DNS وفتح الاتصالات وإغلاقها تستهلك حوالي 13% من إجمالي وقت التنفيذ.

فرضية: يجب أن تؤدي إعادة استخدام الاتصالات باستخدام التجميع إلى تقليل وقت طلب HTTP واحد، مما يسمح بإنتاجية أعلى وزمن وصول أقل.

إعداد التطبيق - التجربة

نقوم بتحديث الكود المصدري، ونحاول إزالة الاتصال بـ Postgresql لكل طلب. الخيار الأول هو الاستخدام تجمع الاتصال على مستوى التطبيق. في هذه التجربة نحن دعونا إعداده تجمع الاتصال باستخدام برنامج تشغيل SQL للذهاب:

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

التنفيذ والملاحظة والتحليل

بعد إعادة تشغيل الاختبار بمعدل 1000 طلب في الثانية، من الواضح أن مستويات زمن الوصول لـ p99 قد عادت إلى وضعها الطبيعي مع SLO يبلغ 60 مللي ثانية!

ما هي التكلفة؟

10000 طلب في الثانية / 1000 طلبًا لكل خادم = 10 خادم + 1

دعونا نفعل ذلك بشكل أفضل!

2000 طلبًا في الثانية

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

يُظهر مضاعفة الحمل نفس الشيء، ويُظهر الرسم البياني العلوي الأيسر أن التطبيق قادر على معالجة 2000 طلب في الثانية، وأن p100 أقل من 60 مللي ثانية، وأن p99 يفي بـ SLO.

من حيث التكلفة:

10000 طلب في الثانية / 2000 طلبًا لكل خادم = 5 خادم + 1

3000 طلبًا في الثانية

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

هنا يمكن للتطبيق معالجة 3000 طلب بزمن انتقال p99 أقل من 60 مللي ثانية. لا يتم انتهاك SLO، ويتم قبول التكلفة على النحو التالي:

10000 طلب في الثانية / لكل 3000 طلب لكل خادم = 4 خوادم + 1 (وقد تقريب المؤلف، تقريبا. مترجم)

دعونا نحاول جولة أخرى من التحليل.

التحليل - الفرضية

نقوم بجمع وعرض نتائج تصحيح أخطاء التطبيق بمعدل 3000 طلب في الثانية:

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

لا يزال يتم إنفاق 6٪ من الوقت على إنشاء الاتصالات. أدى إعداد التجمع إلى تحسين الأداء، ولكن لا يزال بإمكانك رؤية أن التطبيق يستمر في العمل على إنشاء اتصالات جديدة بقاعدة البيانات.

فرضية: الاتصالات، على الرغم من وجود تجمع، لا تزال يتم إسقاطها وتنظيفها، لذلك يحتاج التطبيق إلى إعادة تعيينها. من المفترض أن يساعد تعيين عدد الاتصالات المعلقة على حجم التجمع في زمن الوصول عن طريق تقليل الوقت الذي يقضيه التطبيق في إنشاء اتصال.

إعداد التطبيق - التجربة

تحاول التثبيت MaxIdleConns يساوي حجم حمام السباحة (كما هو موضح هنا):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

التنفيذ والملاحظة والتحليل

3000 طلبًا في الثانية

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

p99 أقل من 60 مللي ثانية مع أقل بكثير من p100!

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

يظهر فحص الرسم البياني للهب أن الاتصال لم يعد ملحوظًا! دعونا تحقق في مزيد من التفاصيل pg(*conn).query - نحن أيضًا لا نلاحظ إنشاء الاتصال هنا.

SRE: تحليل الأداء. طريقة الإعداد باستخدام خادم ويب بسيط في Go

اختتام

يعد تحليل الأداء أمرًا بالغ الأهمية لفهم تلبية توقعات العملاء والمتطلبات غير الوظيفية. يمكن أن يساعد التحليل من خلال مقارنة الملاحظات مع توقعات العملاء في تحديد ما هو مقبول وما هو غير مقبول. يوفر Go أدوات قوية مدمجة في المكتبة القياسية التي تجعل التحليل بسيطًا ويمكن الوصول إليه.

المصدر: www.habr.com

إضافة تعليق