تطور معالجة Facebook webhook: من صفر إلى 25000 في الثانية

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



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

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

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

يقود ديمتري كوشنيكوف التطوير في ManyChat ويقوم ببرمجة احترافية في PHP منذ عام 2001. سيخبرك Dmitry كيف تغيرت الهندسة إلى جانب نمو الخدمة والحمل ، وما هي الحلول والتقنيات التي تم تطبيقها في مراحل مختلفة ، وكيف تطورت معالجة webhook وكيف يمكن للمنصة التعامل مع حمل ضخم باستخدام موارد متواضعة في PHP.

ملحوظة. تستند المقالة إلى تقرير ديمتري "تطور معالجة Facebook webhook: من صفر إلى 12،500 في الثانية" في PHP Russia 2019 . لكن بينما كان يستعد ، ارتفعت المؤشرات إلى 25000.


ما هو ManyChat


أولاً ، سأقدم لكم في سياق مهامنا. ManyChat هي خدمة تساعد الشركات على استخدام الرسائل الفورية للتسويق والمبيعات والدعم. المنتج الرئيسي هو النظام الأساسي لتسويق Messenger على Facebook Messenger . لمدة ثلاث سنوات ، استخدم أكثر من مليون شركة من 100 دولة حول العالم الخدمة للتواصل مع 700 مليون من عملائها.

من ناحية العميل ، يبدو هذا.


الأزرار والصور وصالات العرض في مربعات الحوار في Facebook Messenger.

هذه هي واجهة Facebook Messenger. بالإضافة إلى الرسائل النصية ، يمكنه إرسال عناصر تفاعلية من أجل التفاعل مع العملاء ، والانخراط في الحوار ، وزيادة الاهتمام بمنتجاتهم والبيع.

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


قلب نظامنا هو مكون Flow Builder.

مجموعة النصوص وقواعد الأتمتة التي نسميها بوت . لذلك ، لتبسيط الأمر ، يمكننا القول أن ManyChat هو مصمم بوت.


مثال على الروبوت.

يُدعى عميل النشاط التجاري الذي يشارك في الحوار المشترك ، لأنه يشترك العميل في البوت من أجل التفاعل .

لماذا الفيسبوك


لماذا الفيسبوك رسول ، نحن بلد برقية على قيد الحياة؟ هناك أسباب لذلك.

  • Telegram , №1 Facebook. 1,5 , Telegram 200-300 .
  • Facebook , . , Facebook - .
  • Facebook F8 - 300 . Facebook Messenger. 20 . ManyChat 40%.

Facebook


يتم تنظيم التفاعل مع Facebook



على النحو التالي : يستخدم Business تطبيق ويب لتكوين منطق الروبوت. عندما يتفاعل العميل مع الروبوت عبر الهاتف ، يتلقى Facebook معلومات حول هذا ويرسل إلينا رسالة إلكترونية. يقوم ManyChat بمعالجته ، اعتمادًا على المنطق الذي تمت برمجته بواسطة الشركة ، ويرسل الطلب مرة أخرى. ثم يسلم Facebook الرسالة إلى هاتف المستخدم.

المكدس التكنولوجي


نفعل كل هذا على كومة متواضعة. في الأساس ، بالطبع ، هو PHP. يعمل خادم الويب Nginx ، وقاعدة البيانات الرئيسية هي PostgreSQL ، وهناك أيضًا Redis و Elasticsearch. كل ذلك يدور في سحابة Amazon Web Services.

التعامل مع Facebook Webhook


هذا هو شكل كاميرا الويب على Facebook: هذا طلب بحمولة بتنسيق JSON.

{
    "object":"page",
    "entry":[
        }
            "id":"<PAGE_ID>",
            "time":1458692752478,
            "messaging":[
                {
                    "sender":{
                        "id":"<PSID>"
                    },
                    "recipient":{
                        "id":"<PAGE_ID>"
                    },

                    ...
                }
            ]  
        }
    ]
}

تمثل Webhooks نسبة 10٪ فقط من حملنا ، ولكنها أهم جزء من النظام. من خلالهم ، تتواصل الأعمال مع المستخدمين. إذا تباطأت الرسائل أو لم يتم إرسالها ، يرفض المستخدم التفاعل مع برنامج التتبُّع ، ويفقد العمل العميل.

دعونا نلقي نظرة على تطور بنيتنا منذ إطلاق المنتج.

مايو 2016 . لقد أطلقنا للتو خدمتنا: 20 روبوت ، منها 10 روبوتات تجريبية و 20 مشتركًا. كان الحمل 0 RPS.

بدا مخطط التفاعل على النحو التالي:



  • يذهب الطلب إلى nginx.
  • يصل Nginx إلى PHP-FPM.
  • يأخذ PHP-FPM التطبيق حتى Yii.
  • يعالج المتحكم عبر الويب المنطق ويرسل الطلبات إلى Facebook وفقًا لها.


حفنة من Nginx و PHP-FPM


يونيو 2016. بعد شهر ، أعلنا عن ManyChat على ProductHunt وزاد عدد الروبوتات إلى 2000. ارتفع عدد المشتركين إلى 7 آلاف.

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

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



قوائم الانتظار على PostgreSQL


ديسمبر 2016. نمت الخدمة 5-10 مرات: 10 آلاف روبوت و 700 ألف مشترك.

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

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

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



قوائم الانتظار في Redis


يونيو 2017. الخدمة في تزايد: 75 ألف روبوت و 7 ملايين مشترك.

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

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

كاتي بيري هي مغنية أمريكية شهيرة مع عدد كبير من المعجبين حول العالم. هناك 64 مليون مشترك في مجموعتها على Facebook وحدها. في مرحلة ما ، قرر المسوقون المغنون إنشاء روبوت على Facebook Messenger واختاروا منصتنا. في تلك اللحظة ، عندما نشروا رسالة تدعو للاشتراك في البوت ، زاد حملنا 3-4 مرات.

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

لم يتم تنفيذ قوائم الانتظار على Redis في المحاولة الأولى. عندما بدأنا فقط في طي webhooks في Redis ومعالجتها في عملية واحدة ، قمنا بتوسيع مسار التحويل في الأعلى: تمت معالجة المزيد من عمليات الرد على الويب الواردة أيضًا ، ولكن العملية نفسها لا تزال تستغرق بعض الوقت. كان هذا القرار الأول غير ناجح.



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



يبدو أن هذه حالة نادرة ، ولكن اختبار عبء العمل لدينا أظهر أن هذا سيحدث بشكل متكرر.

بدأنا في البحث عن حل آخر. هنا وصلت بساطة وقوة Redis إلى الإنقاذ - قررنا إنشاء قائمة انتظار لكل روبوت .



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

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

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

رد PHP


يناير 2018 . لقد وصلنا إلى 1 مليار وظيفة شهريًا.

كان الحمل 5 آلاف RPS لكل نظام. هذا ليس حمولة الذروة ، ولكن قياسي. عندما تظهر بوتات من المطربين المشهورين ، كل شيء ينمو عدة مرات بالفعل من هذا الرقم. لكنها ليست مشكلة. المشكلة في PHP-FPM: لم تعد قادرة على تحمل حمولة 5 آلاف RPS.

كان الجميع في ذلك الوقت يتحدثون عن المعالجة غير المتزامنة العصرية. لقد ألقينا نظرة فاحصة على ذلك ، ورأينا ReactPHP ، وأجرينا اختبارات سريعة ، واستبدلناها بـ PHP-FPM وحصلنا على الفور على زيادة بمقدار أربعة أضعاف.



لم نعيد كتابة معالجة معالجتنا - ReactPHP رفعت إطار عمل Yii. أولاً ، قمنا بجمع 4 خدمات ReactPHP ، ووصلنا فيما بعد إلى 30. لفترة طويلة عشنا عليها ، وتوافق إطار العمل مع الحمل.

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

عناقيد المجموعات


أخذوا الروبوتات ، وزعوها في مجموعات وبنوا سلاسل منطقية من Redis و Postgres ومعالج.



ونتيجة لذلك ، قمنا بتشكيل مفهوم "المجرة" - وهو تجريد مادي منطقي على المعالجة . وتتكون من أمثلة: Redis و PostgreSQL ومجموعة من خدمات PHP. ينتمي كل روبوت إلى كتلة معينة ، و ReactPHP يعرف أي مجموعة يجب وضع الرسالة لهذا البوت. المخطط أعلاه يعمل أكثر.


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

استبدال ReactPHP بمجموعة من Nginx و Lua


للأشهر الستة المقبلة ، واصلنا النمو: 200 مليون مشترك و 3 مليارات رسالة شهريًا. تخيل موقعًا لـ 200 مليون مستخدم مسجل - نفس العبء.

نشأت مشكلة جديدة. تعتبر Webhooks مهام صغيرة من نفس النوع ، و PHP ليست مناسبة لحلها. حتى ReactPHP لم يعد يساعد.

  • لم يستطع التعامل مع حمولة 10 آلاف RPS - منذ إدخال ReactPHP ، زاد الحمل.
  • كان من الضروري إعادة تشغيله حتى مع عمليات النشر ، علاوة على ذلك ، بالتسلسل ، لأنه لا يمكنك مقاطعة معالجة رسائل الويب الواردة. يقوم Facebook بتعطيل التطبيق عندما يدرك أن لديه مشاكل. بالنسبة إلى ManyChat ، هذه كارثة - لن يسامحنا 650 ألف شركة تعمل بنشاط.

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

تذكرنا أن Nginx بها وحدات ولاحظنا مكتبة OpenResty . بالإضافة إلى دعم لغة البرمجة Lua ، كان لديها وحدة للعمل مع Redis. أظهر اختبار مكتوب في 3 ساعات أن كل عمل 30 خدمة على ReactPHP يمكن أن يتم مباشرة على الجانب nginx.



هذه هي الطريقة التي اتبعت: نعالج نوعًا ما من نقطة النهاية ، ونختار نص الطلب ونضيفه مباشرة إلى Redis.

location / {
    error_log /var/log/nginx/error.log;

    resolver ###resolver###;

    content_by_lua '

        ngx.req.read_body()
        local mybody = ngx.req.get_body_data()

        if not mybody then
            return ngx.exit(400)
        end

        local hash = ngx.crc32_long(mybody)
        local cluster = hash % ###wh_inbound_shards### + 1

        local redis = require "resty.redis";
        local red = radis.new()
        red:set_timeout(3000)

        local ok, err = red:connect("###redisConnectionWh2.server.host###", 6379)
		
        if not ok then
            ngx.log(ngx.ERR, err, "Redis failed to connect")
            return ngx.exit(403)
        end

        local ok, err = red:rpush("###wh_inbound_queue###" .. queuesuffix .. cluster, mybody)
        
        if not ok then
            ngx.log(ngx.ERR, err, "Failed to write data", mybody)
            return ngx.exit(500)
        end

        local ok, err = red:set_keepalive(10000, 100)

        ngn.say("ok")
    ';
}

وقد ساعد OpenResty و Lua على زيادة الإنتاجية. نحن نواصل التعامل مع عبء العمل لدينا ، والخدمة مستمرة ، والجميع سعداء.

تحسين الحل على لوا


المرحلة الأخيرة ( ملاحظة: في وقت التقرير ) هي فبراير 2019 . 500 مليون مشترك يرسلون ويستقبلون 7 ملايين رسالة من مليون روبوت كل شهر.

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



نحافظ على معالجة منفصلة ومعالجة غير متزامنة . تتعلق المعالجة بالإحصاءات وأشياء أخرى - الآن أصبح نظامًا مختلفًا تمامًا.

يبدو النظام بسيطًا ، ولكنه ليس كذلك. تحت الغطاء ، هناك 500 خدمة تعالج طلباتهم. يعمل النظام بأكمله على 50 مثيلًا من أمازون: Redis و PostgreSQL ومعالجات PHP أنفسهم.

تطور المعالجة


يمكن أن يكون الحمل المرتفع رائعًا في PHP.

أذكر بإيجاز كيف فعلنا ذلك في عملية تطوير النظام.

  • بدأت مع Nginx العادية و PHP-FPM.
  • تمت إضافة قوائم الانتظار إلى PostgreSQL ، ثم إلى Redis.
  • تجمّع مضاف.
  • تم تنفيذ ReactPHP.
  • لقد استبدلنا ReactPHP بمجموعة من Nginx و Lua ، ثم نقلنا المنطق إلى المجموعة.



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

, , 11 TeamLead Conf. , LeSS, .

PHP Russia Saint HighLoad++, . PHP , — PHP Russia 13 . highload PHP, Saint HighLoad++ .

Source: https://habr.com/ru/post/undefined/


All Articles