
كل يوم ، يساعدنا مستخدمو نظام 2GIS في الحفاظ على دقة البيانات: فهم يعلمون عن الشركات الجديدة ، ويضيفون أحداث المرور ، ويحملون الصور ويكتبون التعليقات. في السابق ، كان بإمكاننا فقط شكرهم بالكلمات أو ترتيب الهبة. ولكن مع مرور الوقت ، تُنسى الكلمات ، ولا يحصل الجميع على الهدايا. لذلك ، قررنا التأكد من أن جميع من يهتمون بشركة 2GIS يرون مساهمتهم في المنتج وامتناننا لذلك.
لذلك كانت هناك جوائز - ميداليات افتراضية نجمعها لأنواع مختلفة من المهام: تحميل الصور على بطاقات المقاهي ، وكتابة مراجعات حول المسارح ، وتحديد ساعات عمل المنظمات ، وما إلى ذلك. يرى المستخدمون المكافآت التي حصلوا عليها في ملفهم الشخصي 2GIS وفي علامة التبويب "My 2GIS" في تطبيق الهاتف المحمول. هناك نظهر مقدار ما تبقى حتى الإنجاز القادم.
لتطبيق هذه الميزة ، تعلمنا كيفية معالجة مجموعة من الأحداث بحجم 500 ألف سجل في الساعة (في أماكن تصل إلى 50 ألف سجل في الثانية الواحدة) وتحليل البيانات من العديد من الخدمات. وأيضًا - أضافوا مخططًا صغيرًا من أجل تبسيط التكوين عند تطوير جوائز جديدة.
جنبا إلى جنب مع
Rapter ،
سنخبرك بما هو تحت غطاء عملية الجائزة.
مفهوم
لفهم مدى تعقيد الميزة ، تحتاج إلى فهم كيفية ظهور المشكلة الفنية. ثم - النظر في فكرة التنفيذ والمخطط العام لمكونات النظام. هذا هو ما سنفعله في هذا القسم.
متطلبات الملخصات
المتطلبات - شيء ممل إلى حد ما ، لذلك لن نرسم جميع الفروق الدقيقة ، وسوف نركز على أهم الأشياء:
- يتم إصدار الجوائز فقط للمستخدمين المصرح لهم ؛
- يجب أن يكون تحديث التقدم على المكافأة في أسرع وقت ممكن ؛
- مكافأة - نتيجة قيام المستخدم بتنفيذ مجموعة من الإجراءات في المنتج: تحميل صورة أو كتابة مراجعة أو البحث عن الاتجاهات ، إلخ. هناك الكثير من مصادر البيانات.
فكرة معمارية
فكرة التنفيذ ليست معقدة للغاية. يمكن التعبير عنها من الناحية النظرية:
- تتكون الجائزة من مهام ، يتم دمج نتائجها وفقًا للصيغة المحددة عند تكوين الجائزة ؛
- تستجيب المهمة للأحداث المتعلقة بإجراءات المستخدم القادمة من الخارج ، وتقوم بتصفيةها وتسجيل التغيير الجاري في شكل عدادات ؛
- يتم إنشاء "الأحداث الخارجية" من خلال الأنظمة الرئيسية (الصور ، التغذية المرتدة ، خدمات التحسين ، وما إلى ذلك) أو الخدمات المساعدة التي تقوم بتحويل أو تصفية تدفقات الأحداث الموجودة بالفعل ؛
- تتم معالجة الأحداث بشكل غير متزامن ويمكن إيقافها في أي وقت إذا لزم الأمر ؛
- يرى المستخدم الوضع الحالي لجوائزه ؛
- كل شيء آخر هو التفاصيل ...
الكيانات الرئيسية
يوضح الرسم البياني أدناه الكيانات الرئيسية في مجال الموضوع وعلاقتها:

يتم تمييز منطقتين في المخطط:
- مخطط - منطقة لوصف هيكل الجوائز وقواعد تراكمها ؛
- البيانات - منطقة الجائزة لمستخدمين محددين والبيانات المتعلقة بوضعهم الحالي.
الكيانات في الرسم البياني:
- تحقيق - معلومات حول الجائزة التي يمكن الحصول عليها. يتضمن معلومات التعريف ووصفًا لكيفية الجمع بين نتائج المهام - استراتيجية.
- الهدف - مهمة ، يجب الوفاء بشروطها من أجل التقدم إلى الحصول على جائزة.
- UserAchieve - الحالة الحالية للمكافأة لمستخدم معين.
- UserObjective - الحالة الحالية لوظيفة المكافأة للمستخدم.
- المستخدم - معلومات حول المستخدم ، وهي ضرورية للإشعارات وفهم حالته الحالية (لا تكون هناك حاجة إلى مكافآت عن بُعد ومحظورة).
- ProcessingLog - سجل المستحقات للمهام. يحتوي على معلومات حول كيفية تأثير إجراء معين على تقدم المهمة.
- الحدث - الحد الأدنى من المعلومات الضرورية عن الحدث الذي أثر بطريقة ما على تقدم مهام المستخدم.
هيكل الخدمة
الآن النظر في المكونات الرئيسية للخدمة وتوابعها:

- الأحداث حافلة - حافلة الحدث التي يمكن استخدامها لإكمال المهام. نحن نستخدم اباتشي كافكا.
- Master و Slave DBs هي مستودع البيانات الرئيسي. في هذه الحالة ، كتلة PostgreSQL.
- ConsumingWorkers - معالجات الأحداث في الحافلة. تتمثل المهمة الرئيسية في قراءة الأحداث من مصدر محدد (الصور والمراجعات وما إلى ذلك) ، وتطبيقها على مهام المستخدم وحفظ النتيجة.
- AchievesWorker - يحسب التقدم المحرز على مكافآت المستخدم وفقا لحالة المهام.
- NotificationWorkers - مجموعة من معالجات الجدولة وإرسال الإشعارات حول تلقي الجوائز والإعلانات عن الإنجازات المحتملة الجديدة ، إلخ.
- واجهة برمجة التطبيقات العامة - واجهة REST العامة لتطبيقات الويب والهاتف المحمول.
- واجهة API الخاصة - REST للوحة الإدارة ، مما يساعد في التحقيق في الحوادث ودعم الخدمة. وهي متاحة للمطورين وفرق الدعم.
يتم عزل كل مكون من حيث المنطق ومجالات المسؤولية ، مما يتجنب الدمج والتوقف غير الضروري عند تعديل البيانات. أدناه نعتبر جزءًا فقط من المخطط المرتبط بمعالجة الأحداث وتحويلها إلى مكافآت.
التعامل مع الحدث
محتوى
المكافآت هي في المقام الأول خدمة تجميع البيانات. ينشئ كل نظام رئيسي عدة أنواع من الأحداث. كقاعدة عامة ، يرتبط كل نوع من الأحداث ارتباطًا وثيقًا بحالة المحتوى ، ونموذج الحالة الخاص به. لذلك ، يمكن تعديل الصورة أو حذفها أو حظرها أو إخفاؤها أو نشاطها. كل هذه أحداث مختلفة يتم معالجتها بواسطة عامل منفصل متخصص في مصدر معين. في الوقت الحالي ، هناك تفاعل مع المصادر التالية (الأنظمة الرئيسية):
- Photo (Photo) - يولد العديد من الأحداث التي تتعلق بالعمليات التي يقوم بها المستخدمون على الصور الفوتوغرافية.
- المراجعات - الأحداث المتعلقة بالعمليات على مراجعات المستخدم.
- Datafeedback - الأحداث المتعلقة بعمليات التنقيح. التوضيح هو تغيير في المعلومات المتعلقة بالكائن على الخريطة ، سواء أكانت شركة أم نصب تذكاري.
- Check - الأحداث التي تتعلق بتطبيق 2GIS Check.
- BSS هي أحداث تحليلية تولد تطبيقات 2GIS. على سبيل المثال ، فتح شركة معينة ، والرحلات على المستكشف ، إلخ.

تندرج الأحداث التي تم إنشاؤها بواسطة النظام الرئيسي في موضوع كافكا بترتيب تغيير أوضاعهم ، مما يجعل من الممكن تحريك تقدم الجائزة للمستخدم ليس للأمام فحسب ، بل أيضًا استعادته. على سبيل المثال ، إذا كانت الصورة في حالة "نشطة" ، ثم اكتسبت حالة "محظور" لسبب ما ، يجب أن يتغير التقدم المحرز في الجائزة إلى أسفل. تقدم الجائزة عبارة عن تفسير للعناصر الداخلية تسمى عدادات المحتوى.
قد تختلف عدادات البيانات المختلفة. على سبيل المثال ، بالنسبة إلى الأحداث المتعلقة بالصورة ، تكون كالتالي: عدد البطاقات المعتمدة ، وعدد التعديلات ، وعدد البطاقات المحظورة ، وبالنسبة إلى أحداث البطاقات المفتوحة ، يجب مراعاة عدد البطاقات التي يفتحها المستخدم فقط. استنادًا إلى القيم الحالية لعدادات المحتوى ، لمستخدم معين ، في إطار جائزة معينة ، يتم تحديد الإجابات على الأسئلة التالية:
- هل بدأت الجائزة؟
- ما هو التقدم
- هل المكافأة كاملة؟
المرشحات والقواعد
لا يتم تغيير عدادات الوظيفة لجائزة معينة إلا في حالة وصول حدث بنوع المحتوى المرغوب فيه ، وكذلك مع البيانات اللازمة لاستلام الجائزة.
من أجل تخطي المحتوى المناسب للجائزة فقط ، نقوم بتشغيل كل حدث من خلال سلسلة من المرشحات والقواعد.
عامل التصفية هو قيد معين يتم فرضه على المحتوى. إنه مهتم فقط بالإجابة على السؤال: "هل يناسب الحدث الجديد هذا الشرط أم لا؟"
القاعدة هي مرشح خاص ، والغرض منه هو القول: "إذا كان الحدث يناسب الحالة ، فكيف يجب تغيير العدادات؟" تتضمن القاعدة خوارزمية لتغيير العدادات. كل جائزة تحتوي على قاعدة واحدة فقط.
تنفيذ المرشحات والقواعد في رمز المشروع ، ووصف المرشحات (القاعدة) التي تنتمي إلى جائزة معينة موجود في قاعدة البيانات بتنسيق JSON. لم نتوصل إلى مثل هذا القرار على الفور. في البداية ، لا يمكن تعيين عوامل التصفية والقواعد باستخدام التكوين من خلال قاعدة البيانات ، فقد تم وصف الجائزة بالكامل في الشفرة ، وتم تخزين معرفها فقط في الجدول. أعطى هذا القرار عددًا من العيوب المهمة:
- مشكلة دعم بيئات متعددة. إذا كنت ترغب في طرح حالة واحدة من قائمة الجوائز إلى بيئة الاختبار ، وإرسال حالة أخرى إلى المعركة ، فأنت بحاجة إلى معرفة رمز البيئة في رمز المشروع أو لديك ملف تكوين مع قائمة الجوائز. في الوقت نفسه ، لا يمكن استخدام قواعد بيانات مختلفة لهذه المهمة ، على الرغم من وجودها بالفعل لكل بيئة.
- القدرة على تكوين التصفية فقط من قبل المطور. نظرًا لأن كل شيء موصوف في التعليمات البرمجية ، يمكن فقط للشخص الذي يعرف المشروع ولغة البرمجة إجراء تغييرات ، وأود أن يكون ذلك ممكنًا من خلال واجهة برمجة التطبيقات الخاصة أو قاعدة البيانات.
- عيب المشاهدة. هناك العديد من المكافآت ، في بعض الأحيان تحتاج إلى رؤية المرشحات التي يستخدمونها. في كل مرة ، يكون القيام بذلك من خلال النظر في التعليمات البرمجية مملاً للغاية.
في بداية التطبيق ، نطابق اسم المرشحات التي تم تحميلها من قاعدة البيانات ووضعها في مكافأة محددة. مثال وصف عامل التصفية:
[ { "name":"SourceFilter", "config":{ "sources":["reviews"] } }, { "name": "ReviewsLengthFilter", "config": { "allowed_length": 100 } } ]
في هذه الحالة ، سوف نأخذ فقط تلك المراجعات (يشار إليها بواسطة كائن الوصف الأول من صفيف المرشح) ، الذي يحتوي نصه على أكثر من 100 حرف (المرشح الثاني في القائمة).
مثال وصف القاعدة:
{"name": "ReviewUniqueByObjectRule","config":{}}
ستسمح لك هذه القاعدة بتغيير العدادات فقط إذا كتب المستخدم مراجعة للكائن ، بينما سيتم أخذ مراجعة واحدة فقط في الاعتبار لكائن واحد.
BSS
دعنا نتحدث بشكل منفصل عن العمل مع دفق أحداث BSS. هناك ثلاثة أسباب على الأقل لهذا:
- لا يمكن التراجع عن أحداث Analytics ، ولا يوجد نموذج حالة فيها ، وهو أمر منطقي بشكل عام ، لأنه لا يمكن إلغاء القيادة عبر المستكشف أو بناء طريق. كان العمل إما هناك أم لا.
- وحدات التخزين. اسمح لي أن أذكرك أن إجمالي عدد مستخدمي نظام 2GIS يبلغ 50 مليون مستخدم شهريًا. معًا ، يقومون بأكثر من 1.5 مليار استعلام بحث ، بالإضافة إلى العديد من الإجراءات الأخرى: تشغيل التطبيق ، عرض بطاقة الكائن ، وما إلى ذلك. في الذروة ، يمكن أن يصل عدد الأحداث إلى 50000 في الثانية. يجب علينا تمرير كل هذه المعلومات من خلال المرشحات من أجل منح مكافأة للمستخدم.
- تشتمل أحداث Analytics على ميزات: العديد من التنسيقات ، ومجموعة متنوعة من الأنواع.
كل هذا أثر بشكل كبير على معالجة البيانات من موضوع BSS ، لأنه إذا كنا بحاجة إلى الوقت الفعلي ، فإننا نحتاج إلى وقت قريب جدًا للمعالجة.
لتقليل الاختلافات الموضحة ، تم إنشاء خدمة منفصلة تعد هذه الأحداث. يمكن أن تعمل الخدمة مع مجموعة كاملة من تنسيقات الرسائل القادمة من التحليلات. يكمن جوهر عمله على النحو التالي: يتم قراءة مجرى الأحداث BSS بأكمله ، حيث يتم أخذ الأحداث اللازمة للجوائز فقط. يعمل مرشح الخدمة هذا على تقليل الحمل بشكل كبير (بعد التصفية ، يكون معدل التدفق هو events 300 حدث في الثانية) من مكافآت معالج BSS-stream ، كما أنه يولد أحداثًا بتنسيق واحد ، مما يؤدي إلى تسوية العيوب المرتبطة بتاريخ التحليلات الداخلية.
صدور جوائز
لذلك ، اكتشفنا كيفية التعامل مع الأحداث وحساب التقدم في المهام. حان الوقت الآن لإلقاء نظرة على عملية إصدار المكافآت للمستخدمين.
السؤال الأول الذي يطرح نفسه هو: لماذا تخصيص الإخراج إلى عامل منفصل ، لا يمكن إعادة حسابه عند معالجة كل حدث؟ الجواب: ممكن ، ولكن لا يستحق كل هذا العناء.
هناك عدة أسباب لتخصيص التسليم لعملية منفصلة:
- نقل إعادة فرز الأصوات إلى كل ConsumingWorker ، نحصل على شرط السباق لتشغيل تحديث التقدم من خلال المكافأة ، لأن كل معالج سيحاول تحديث التقدم بناءً على حالة المهام المعروفة ، وسيغير الآخرون هذه الحالة بنشاط.
- كل دفعة ConsumingWorker بمعالجة الأحداث من Kafka في معاملة. عن طريق إضافة إدراج إلى جدول المكافآت الخاص بالمستخدم ، سنقوم باستدعاء أقفال إضافية على مستوى قاعدة البيانات ، مما سيحول دون معالجات أخرى.
- في عملية إصدار الجوائز ، هناك منطق لإرسال الإشعارات ، مما سيؤدي فقط إلى إبطاء معالجة تدفق الأحداث ، وهو أمر غير مرغوب فيه.
تم فرز أسباب ظهور AchieveWorker منفصلة (معالج لإصدار الجوائز). أنت الآن بحاجة إلى التعامل مع جزأين مهمين من المعالجة:
- هناك مجموعة من المهام في المكافأة. هناك مجموعة من العدادات لهذه المهام. كيف نفهم مقدار الجائزة وكيف يتم التعبير عنها في الكود؟
مثال: تحتاج إلى كتابة 3 تعليقات أو تحميل 3 صور. المستخدم لديه 1 مراجعة و 2 الصور. ما هو تقدم الجائزة؟ الإجابة: 3 ، لأن المستخدم سيكون متأكدًا بالتأكيد من أنك بحاجة إلى 3 في المجموع. - لدينا معالج منفصل لإصدار الجوائز. في كل مرة ، من غير المرجح أن تنجح عملية إعادة حساب عشرات الجوائز لكل مستخدم مصرح به ، أي عشرات الملايين. كيف يمكنه معرفة مدى تقدم المستخدمين المعينين وماهية المهام التي تغيرت منذ المعالجة الأخيرة؟
سننظر في كل جزء على حدة.
تدفق التقدم
لفهم أفضل لكيفية وصف كيفية تحويل تقدم المهام إلى تقدم عن طريق المكافأة ، نقسم المكافآت إلى فئات وننظر في التحويلات.
"أكمل مهمة واحدة لكل وحدات X." مثال: قم بقيادة 10 كم على المستكشف.
"أكمل العديد من المهام لوحدات X لكل منها." على سبيل المثال: قم بتحميل 5 صور وكتابة 5 ملاحظات في البطاقات - فقط 10 وحدات من المحتوى.
"أكمل العديد من المهام لوحدات X في المجموع." مثال: اكتب 5 تعليقات أو حمّل 5 صور.
"أكمل عدة مهام مجمعة حسب النوع." مثال: قم بتحميل 5 وحدات من المحتوى (صور أو تعليقات) وقيادة 10 كم على المستكشف.

من الناحية النظرية ، قد يكون هناك مجموعات متداخلة أكثر تعقيدًا. ومع ذلك ، في الظروف الحقيقية ، لا يمكن أن تشرح للمستخدم في جملتين أو ثلاث جمل معقدة منطقية التي يجب القيام بها لاستلام الجائزة. لذلك ، في معظم الحالات ، هذه الخيارات كافية.
وصفنا طريقة التحويل بأنها استراتيجية وحاولنا جعلها أكثر أو أقل عالمية من خلال وضع وصف رسمي في شكل كائن JSON. يمكنك بالطبع التفكير في الكتابة في صيغة ، ولكن بعد ذلك يجب عليك استخدام أوجه التشابه في تقييم القواعد أو وصفها وتنفيذها ، ومن الواضح أن هذا يمثل فرطًا في التعقيد. تخزين الإستراتيجية في الكود المصدري لكل جائزة ليس مناسبًا جدًا ، لأن وصف الجائزة (جزء في قاعدة البيانات ، وجزء في الكود) سوف يمزق ، ولن يسمح أيضًا بجمع الجوائز من مكونات جاهزة في المستقبل دون مشاركة التطوير.
يتم تقديم الإستراتيجية في شكل شجرة ، حيث كل عقدة:
- يشير إلى التقدم الحالي في مهمة أو مجموعة من العقد الأخرى.
- قد يكون هناك قيود أعلى - في الواقع إشارة إلى الحاجة إلى استخدام min ().
- قد يكون لها معامل التطبيع. مطلوب لتحويلات بسيطة بضرب النتيجة برقم. لقد وصلنا في متناول اليد لتحويل متر إلى كيلومترات.
لوصف الأمثلة المذكورة أعلاه ، عملية واحدة كافية - مجموع. تعتبر ميزة Sum رائعة لإظهار تقدم المستخدم بوضوح برقم واحد ، ولكن يمكن استخدام عمليات أخرى إذا رغبت في ذلك.
فيما يلي وصف لاستراتيجية المثال للفئة الأخيرة:
{ "goal": 15, "operation": "sum", "strategy": [ { "goal": 5, "operation": "sum", "strategy": [ { "objective_id": "photo" }, { "objective_id": "reviews" } ] }, { "goal": 10, "operation": "sum", "strategy": [ { "objective_id": "navi", "normalization_factor": 0.001 } ] } ] }
التحديثات المطلوبة
هناك عدة معالجات تقوم بتحليل الأحداث بلا هوادة بواسطة المستخدمين وتطبيق التغييرات على تقدم المهام. سيؤدي البحث المنتظم لجميع المستخدمين الذين يحصلون على كل جائزة إلى تحليل لعشرات الملايين من الجوائز - وهذا ليس مشجعًا للغاية ، شريطة أن تقاس التحديثات الحقيقية بالآلاف. كيف تتعلم فقط عن الآلاف ولا تضيع الملايين من وحدة المعالجة المركزية؟
جاءت فكرة كيفية إعادة حساب التقدم فقط على تلك الجوائز التي تغيرت بالفعل ، بسرعة كبيرة. لأنه يقوم على استخدام الساعات ناقلات.
قبل الوصف سوف أذكر الكيانات:
- UserObjective - بيانات عن تقدم المستخدم من خلال تحديد الجائزة.
- UserAchieve - مكافأة بيانات تقدم المستخدم.
يبدو التنفيذ كالتالي:
- نحصل على حقل إصدار UserObjective و UserAchieve و Sequence في PostgreSQL.
- يغير كل تحديث لكيان UserObjective إصداره. القيمة مأخوذة من التسلسل (لدينا مشتركة بين جميع السجلات).
- سيتم تحديد قيمة إصدار UserAchieve كحد أقصى لإصدارات UserObjective المقترنة.
- في كل دورة معالجة ، يبحث AchievesWorker عن UserObjective التي لا يوجد لها UserAchieve أو UserAchieve.version <UserObjective.version. يتم حل المشكلة عن طريق استعلام واحد إلى قاعدة البيانات.
تجدر الإشارة إلى أن الحل يتضمن قيودًا على عدد الإدخالات في جداول الجوائز والواجبات ، وكذلك على تكرار التغييرات الجارية في المهام ، لكن مع وجود عشرات الملايين من الجوائز وعدد التحديثات بأقل من ألف في الدقيقة ، من الممكن التعايش مع مثل هذا الحل. بطريقة أو بأخرى ،
سنخبرك بشكل منفصل عن كيفية تحسين الإصدار للمسابقة "
وكلاء 2GIS ".
النتائج
على الرغم من حقيقة أن المقالة اتضح أنها ضخمة ، إلا أن الكثير من الفروق الدقيقة ظلت وراء الكواليس ، لأنه لن يكون من الممكن التحدث عنها لفترة وجيزة.
ما هي النتائج التي توصلنا إليها بفضل الجوائز:
- لعبت مبدأ "فرق تسد" في هذه الحالة في أيدينا. يساعدنا تخصيص معالجات الأحداث لكل مصدر على التوسع عند الضرورة. يتم عزل عملهم وفقًا للبيانات ويتقاطع فقط في المناطق الصغيرة. يسمح لك تسليط الضوء على منطق المكافآت بتقليل الحمل في معالجات الأحداث.
- إذا كنت بحاجة إلى استيعاب الكثير من البيانات والمعالجة باهظة الثمن ، فيجب أن تفكر على الفور في كيفية تصفية ما هو غير ضروري بالتأكيد. مثال على ذلك تجربة تصفية دفق BSS.
- مرة أخرى ، كنا مقتنعين بأن تكامل الخدمات من خلال ناقل أحداث مشترك مناسب للغاية ويسمح لك بتجنب الحمل غير الضروري على الخدمات الأخرى. إذا استلمت خدمة المكافآت بيانات من خدمات الصور والمراجعات وما إلى ذلك عبر طلبات http ، فسيتعين إعداد العديد من الخدمات لتحميل إضافي.
- يمكن لبرمجة metaprogramming المساعدة في الحفاظ على تكامل تكوين البيانات والبيئات المنفصلة بشكل تعسفي. يؤدي تخزين الفلاتر والقواعد والاستراتيجيات في قاعدة البيانات إلى تبسيط عملية تطوير وإصدار جوائز جديدة.