التحجيم اللعب! لآلاف الطلبات المتزامنة

نشرت: 2022-03-11

غالبًا ما يفشل مطورو الويب Scala في مراعاة عواقب وصول آلاف المستخدمين إلى تطبيقاتنا في نفس الوقت. ربما يكون ذلك لأننا نحب عمل النماذج الأولية بسرعة ؛ ربما يكون ذلك بسبب صعوبة اختبار مثل هذه السيناريوهات.

بغض النظر ، سأجادل في أن تجاهل قابلية التوسع ليس سيئًا كما يبدو - إذا كنت تستخدم المجموعة المناسبة من الأدوات واتبعت ممارسات التطوير الجيدة.

إن تجاهل قابلية التوسع ليس بالسوء الذي يبدو عليه - إذا كنت تستخدم الأدوات المناسبة.

Lojinha والمسرح! إطار العمل

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

  • أردت حقًا بيع بعض الأشياء القديمة التي لم أعد أستخدمها.
  • لا أحب مواقع المزادات التقليدية ، خاصة تلك الموجودة لدينا هنا في البرازيل.
  • كنت أرغب في "اللعب" مع المسرحية! الإطار 2 (التورية المقصودة).

من الواضح ، كما ذكر أعلاه ، أنني قررت استخدام Play! إطار العمل. ليس لدي حساب دقيق للوقت الذي استغرقه الإنشاء ، ولكن من المؤكد أنه لم يمض وقت طويل قبل أن أقوم بتشغيل موقعي باستخدام النظام البسيط المنشور على http://lojinha.jcranky.com. في الواقع ، قضيت نصف وقت التطوير على الأقل في التصميم ، والذي يستخدم Twitter Bootstrap (تذكر: لست مصممًا ...).

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

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

في هذه الحالة ، هذه الأدوات هي Play! الإطار ولغة سكالا ، مع ظهور عكا لبعض "المظاهر الضيف".

اسمحوا لي أن أريكم ما أعنيه.

الثبات والتخزين المؤقت

من المتفق عليه عمومًا أن تقليل قابلية التغيير هو ممارسة جيدة. باختصار ، تجعل قابلية التغيير من الصعب التفكير بشأن الكود الخاص بك ، خاصةً عندما تحاول تقديم أي توازي أو التزامن.

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

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

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

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

كمرجع ، إليك رمز Scala لتحميل صفحة البداية بقائمة من المنتجات ، بدون تخزين مؤقت:

 def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }

الآن ، إضافة ذاكرة التخزين المؤقت:

 def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }

بسيط جدا ، أليس كذلك؟ هنا ، "index" هو المفتاح الذي يجب استخدامه في نظام التخزين المؤقت و 5 هو وقت انتهاء الصلاحية ، بالثواني.

بعد التخزين المؤقت ، ارتفع معدل النقل إلى 800 طلب في الثانية. هذا تحسين لأكثر من 4x لأقل من سطرين من التعليمات البرمجية.

لاختبار تأثير هذا التغيير ، أجريت بعض اختبارات JMeter (المضمنة في GitHub repo) محليًا. قبل إضافة ذاكرة التخزين المؤقت ، حققت إنتاجية تقارب 180 طلبًا في الثانية. بعد التخزين المؤقت ، ارتفع معدل النقل إلى 800 طلب في الثانية. هذا تحسين لأكثر من 4x لأقل من سطرين من التعليمات البرمجية.

هكذا استخدمت Play! ذاكرة التخزين المؤقت لتحسين الأداء على موقع مزاد Scala الخاص بي.

استهلاك الذاكرة

مجال آخر حيث يمكن لأدوات Scala الصحيحة أن تحدث فرقًا كبيرًا هو استهلاك الذاكرة. هنا ، مرة أخرى ، العب! يدفعك في الاتجاه الصحيح (القابل للتطوير). في عالم Java ، بالنسبة لتطبيق ويب "عادي" مكتوب باستخدام واجهة برمجة تطبيقات servlet (على سبيل المثال ، أي إطار عمل Java أو Scala موجود تقريبًا) ، من المغري جدًا وضع الكثير من الرسائل غير المرغوب فيها في جلسة المستخدم لأن واجهة برمجة التطبيقات توفر سهولة طرق الاتصال التي تسمح لك بذلك:

 session.setAttribute("attrName", attrValue);

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

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

لعب! يبقيك على المسار الصحيح ، مما يجبرك على التفكير بعناية في استخدامك للذاكرة ، مما ينتج عنه رمز مرور أول جاهز عمليًا للتجميع.

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

دعم غير متزامن

التالي في هذه المسرحية! مراجعة إطار العمل ، سوف ندرس كيفية اللعب! يضيء أيضًا في الدعم غير المتزامن (hronous). بالإضافة إلى ميزاته الأصلية ، العب! يسمح لك بتضمين Akka ، أداة قوية للمعالجة غير المتزامنة.

لا تستفيد Altough Lojinha حتى الآن بالكامل من Akka ، حيث تكاملها البسيط مع Play! جعلت من السهل حقًا:

  1. جدولة خدمة بريد إلكتروني غير متزامنة.
  2. تقدم العملية لمختلف المنتجات في نفس الوقت.

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

لإرسال بريد إلكتروني بشكل غير متزامن ، أقوم أولاً بإنشاء الرسالة والممثل المناسبين. بعد ذلك ، كل ما علي فعله هو شيء مثل:

 EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)

يتم تطبيق منطق إرسال البريد الإلكتروني داخل الممثل ، وتقوم الرسالة بإعلام الممثل بالبريد الإلكتروني الذي نرغب في إرساله. يتم ذلك في مخطط أطلق وانسى ، مما يعني أن السطر أعلاه يرسل الطلب ثم يستمر في تنفيذ كل ما لدينا بعد ذلك (أي أنه لا يمنع).

لمزيد من المعلومات حول Async الأصلي لـ Play! ، ألق نظرة على الوثائق الرسمية.

خاتمة

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

عند تطوير تطبيقك التالي ، ضع في اعتبارك الأدوات الخاصة بك بعناية.

ذات صلة: تقليل كود Boilerplate باستخدام Scala Macros و Quasiquotes