
مقدمة
تم تصميم موقعي ، الذي أقوم به كهواية ، لتخزين الصفحات الرئيسية المثيرة للاهتمام والمواقع الشخصية. بدأ هذا الموضوع يثير اهتمامي في بداية مساري في البرمجة ، وفي تلك اللحظة كنت سعيدًا للعثور على مهنيين رائعين يكتبون عن أنفسهم وهواياتهم ومشاريعهم. ظلت عادة اكتشافها في الوقت الحالي: في كل موقع تجاري تقريبًا وليس موقعًا على الويب ، ما زلت أتطلع إلى تذييل الصفحة بحثًا عن روابط للمؤلفين.
تنفيذ الفكرة
الإصدار الأول كان مجرد صفحة html على موقعي الشخصي ، حيث وضعت روابط مع التواقيع في قائمة ul. بعد أن كتبت 20 صفحة لبعض الوقت ، بدأت أعتقد أنها لم تكن فعالة للغاية وقررت محاولة أتمتة العملية. في stackoverflow ، لاحظت أن العديد من المواقع تشير إلى مواقعها في ملفات التعريف الخاصة بها ، لذلك كتبت محلل php الذي مر للتو على الملفات الشخصية ، بدءًا من الأول (العنوان في SO وحتى هذا اليوم من هذا النوع: `/ users / 1`) ، الروابط المستخرجة من العلامة المطلوبة ومكدسة في سكليتي.
يمكن أن يسمى هذا الإصدار الثاني: مجموعة من عشرات الآلاف من عناوين URL في لوحة SQLite التي حلت محل القائمة الثابتة في html. لقد أجريت بحث بسيط في هذه القائمة. لأن كان هناك عناوين url فقط ، ثم كان البحث مخصصًا لهم فقط.
في هذه المرحلة ، تخلت عن المشروع وعدت إليه بعد فترة طويلة. في هذه المرحلة ، كانت خبرة عملي بالفعل أكثر من ثلاث سنوات وشعرت أنه يمكنني القيام بشيء أكثر جدية. بالإضافة إلى ذلك ، كانت هناك رغبة كبيرة في إتقان التقنيات الجديدة نسبياً لأنفسهم.
نسخة حديثة
تم نشر
المشروع في عامل ميناء ، وتم نقل قاعدة البيانات إلى mongoDb ، ومؤخرا نسبيا ، أضيفت الفجل ، والذي كان في البداية فقط للتخزين المؤقت. كأساس ، يتم استخدام واحدة من PHP microframes.
المشكلة
تتم إضافة المواقع الجديدة بواسطة أمر وحدة التحكم بشكل متزامن يقوم بما يلي:
- تحميل المحتوى عن طريق URL
- إشارات سواء HTTPS كانت متاحة
- يحافظ على جوهر الموقع
- HTML المصدر والرؤوس يحفظ في تاريخ الفهرسة
- يوزع المحتوى ، ويسترجع العنوان والوصف
- يحفظ البيانات إلى مجموعة منفصلة.
كان هذا يكفي لتخزين المواقع وعرضها في قائمة:

لكن فكرة فهرسة وتصنيف وتصنيف كل شيء تلقائيًا ، والحفاظ على تحديث كل شيء ، تناسب هذا النموذج بشكل ضعيف. حتى مجرد إضافة طريقة ويب لإضافة الصفحات المطلوبة كود الازدواجية والحظر لتجنب DDoS المحتملة.
بشكل عام ، بالطبع ، يمكن القيام بكل شيء بشكل متزامن ، وفي طريقة الويب ، ما عليك سوى حفظ عنوان URL للتأكد من أن البرنامج الخفي الوحشي يؤدي جميع المهام لعناوين URL من القائمة. ولكن كل نفس ، حتى هنا كلمة "بدوره" يطرح. وإذا تم تنفيذ قائمة الانتظار ، يمكن تقسيم جميع المهام وتنفيذها بشكل غير متزامن على الأقل.
قرار
تقديم قوائم الانتظار وجعل نظام المعالجة يحركها الحدث لجميع المهام. ولفترة طويلة أردت تجربة Redis Streams.
استخدام تدفقات Redis في PHP
لأن ليس لدي إطار عمل للعمالقة الثلاثة Symfony و Laravel و Yii ، وأود أن أجد مكتبة مستقلة. ولكن ، كما اتضح (عند الاختبار الأول) ، من المستحيل العثور على مكتبات جادة فردية. كل ما يرتبط بقوائم الانتظار هو إما إسقاط 3 عمليات ارتكابها قبل خمس سنوات ، أو مرتبط بإطار عمل.
لقد سمعت عن Symfony كمقدم لبعض المكونات المفيدة ، وأنا بالفعل استخدم بعضها. وأيضًا من Laravel ، يمكن أيضًا استخدام شيء ما ، على سبيل المثال ، ORM الخاص بهم ، دون وجود الإطار نفسه.
سيمفوني / رسول
بدا المرشح الأول مثاليًا على الفور ، وبدون أي شك قمت بتثبيته. ولكن كان من الصعب على جوجل استخدام أمثلة خارج Symfony. كيف تجمع من مجموعة من الطبقات ذات أسماء عالمية ، ولا تتحدث عن أي شيء ، أو حافلة لإرسال الرسائل ، أو حتى على Redis؟

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

لقد تجمعت في ذلك وحاولت أن أفعل شيئًا بيدي ، وخلصت إلى أنني كنت أقوم بنوع من العكازات وقررت تجربة شيء آخر.
تضيء / قائمة الانتظار
اتضح أن هذه المكتبة كانت مرتبطة بإحكام بالبنية التحتية لارافيل ومجموعة من التبعيات الأخرى ، لذلك لم أقضي الكثير من الوقت عليها: تثبيتها ونظرتها وشاهد التبعيات وحذفها.
yiisoft / yii2- قائمة الانتظار
حسنًا ، هنا تم افتراضه على الفور من الاسم ، مرة أخرى ارتباط مشدود لـ Yii2. اضطررت إلى استخدام هذه المكتبة ولم تكن سيئة ، لكنني لم أعتقد أن ذلك يعتمد تمامًا على Yii2.
الباقي
كان كل شيء آخر وجدته على جيثب لا يمكن الاعتماد عليه وتوقعات مهجورة بدون نجوم وشوك وعدد كبير من الإلتزامات.
العودة إلى symfony / messenger ، التفاصيل الفنية
كان عليّ التعامل مع هذه المكتبة وبعد قضاء بعض الوقت ، تمكنت من ذلك. اتضح أن كل شيء موجز وبسيط للغاية. من أجل إنشاء مثيل للحافلة ، لقد صنعت مصنعًا صغيرًا ، لأنه كان لدي العديد من الإطارات مع مختلف معالجات.

فقط بضع خطوات:
- إنشاء معالجات الرسائل التي يجب أن تكون فقط للاتصال
- لفها في HandlerDescriptor (فئة من المكتبة)
- نقوم بلف هذه "الواصفات" في مثيل HandlersLocator.
- إضافة HandlersLocator إلى مثيل MessageBus
- نمرر إلى SendersLocator مجموعة من "SenderInterface" ، في حالتي من فئات `RedisTransport` ، التي تم تكوينها بطريقة واضحة
- إضافة SendersLocator إلى مثيل MessageBus
لدى MessageBus طريقة `-> إرسال ()` ، والتي تبحث عن المعالجات المناسبة في HandlersLocator وتمرير الرسالة إليهم ، باستخدام `SenderInterface` المطابق للإرسال عبر الحافلة (تدفقات Redis).
في تكوين الحاوية (في هذه الحالة php-di) ، يمكن تكوين هذه المجموعة بأكملها على النحو التالي:
CONTAINER_REDIS_TRANSPORT_SECRET => function (ContainerInterface $c) { return new RedisTransport( $c->get(CONTAINER_REDIS_STREAM_CONNECTION_SECRET), $c->get(CONTAINER_SERIALIZER)) ; }, CONTAINER_REDIS_TRANSPORT_LOG => function (ContainerInterface $c) { return new RedisTransport( $c->get(CONTAINER_REDIS_STREAM_CONNECTION_LOG), $c->get(CONTAINER_SERIALIZER)) ; }, CONTAINER_REDIS_STREAM_RECEIVER_SECRET => function (ContainerInterface $c) { return new RedisReceiver( $c->get(CONTAINER_REDIS_STREAM_CONNECTION_SECRET), $c->get(CONTAINER_SERIALIZER) ); }, CONTAINER_REDIS_STREAM_RECEIVER_LOG => function (ContainerInterface $c) { return new RedisReceiver( $c->get(CONTAINER_REDIS_STREAM_CONNECTION_LOG), $c->get(CONTAINER_SERIALIZER) ); }, CONTAINER_REDIS_STREAM_BUS => function (ContainerInterface $c) { $sendersLocator = new SendersLocator([ \App\Messages\SecretJsonMessages::class => [CONTAINER_REDIS_TRANSPORT_SECRET], \App\Messages\DaemonLogMessage::class => [CONTAINER_REDIS_TRANSPORT_LOG], ], $c); $middleware[] = new SendMessageMiddleware($sendersLocator); return new MessageBus($middleware); }, CONTAINER_REDIS_STREAM_CONNECTION_SECRET => function (ContainerInterface $c) { $host = 'bu-02-redis'; $port = 6379; $dsn = "redis://$host:$port"; $options = [ 'stream' => 'secret', 'group' => 'default', 'consumer' => 'default', ]; return Connection::fromDsn($dsn, $options); }, CONTAINER_REDIS_STREAM_CONNECTION_LOG => function (ContainerInterface $c) { $host = 'bu-02-redis'; $port = 6379; $dsn = "redis://$host:$port"; $options = [ 'stream' => 'log', 'group' => 'default', 'consumer' => 'default', ]; return Connection::fromDsn($dsn, $options); },
يمكن ملاحظة أنه في SendersLocator قمنا بتخصيص "نقل" مختلف لرسالتين مختلفتين ، ولكل منهما اتصاله الخاص بالتيارات المقابلة.
لقد تقدمت بمشروع تجريبي منفصل يوضح تطبيق ثلاثة شياطين يتواصلون مع بعضهم البعض باستخدام مثل هذه الحافلة:
https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus .
ولكن سأريكم كيف يمكن ترتيب المستهلك:
use App\Messages\DaemonLogMessage; use Symfony\Component\Messenger\Handler\HandlerDescriptor; use Symfony\Component\Messenger\Handler\HandlersLocator; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; use Symfony\Component\Messenger\Middleware\SendMessageMiddleware; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; require_once __DIR__ . '/../vendor/autoload.php'; $container = require_once('config/container.php'); $handlers = [ DaemonLogMessage::class => [ new HandlerDescriptor( function (DaemonLogMessage $m) { \error_log('DaemonLogHandler: message handled: / ' . $m->getMessage()); }, ['from_transport' => CONTAINER_REDIS_TRANSPORT_LOG] ) ], ]; $middleware = []; $middleware[] = new HandleMessageMiddleware(new HandlersLocator($handlers)); $sendersLocator = new SendersLocator(['*' => [CONTAINER_REDIS_TRANSPORT_LOG]], $container); $middleware[] = new SendMessageMiddleware($sendersLocator); $bus = new MessageBus($middleware); $receivers = [ CONTAINER_REDIS_TRANSPORT_LOG => $container->get(CONTAINER_REDIS_STREAM_RECEIVER_LOG), ]; $w = new \Symfony\Component\Messenger\Worker($receivers, $bus, $container->get(CONTAINER_EVENT_DISPATCHER)); $w->run();
باستخدام هذه البنية التحتية في التطبيق
بعد أن قمت بتنفيذ الحافلة في الواجهة الخلفية الخاصة بي ، اخترت خطوات فردية من الفريق القديم المتزامن وأعدت معالجات منفصلة ، كل منها يعمل في أعماله الخاصة.
خط أنابيب لإضافة موقع جديد إلى قاعدة البيانات كما يلي:

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

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