PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Өмнөх үг

Миний хоббигоор ажиллуулдаг вэб сайт маань сонирхолтой нүүр хуудас болон хувийн сайтуудыг байрлуулах зориулалттай. Энэ сэдэв миний программчлалын аяллын эхэн үед миний сонирхлыг татаж эхэлсэн бөгөөд тэр үед би өөрийнхөө тухай, хобби, төслийнхөө тухай бичдэг гайхалтай мэргэжилтнүүдийг олж хараад ихэд гайхсан. Тэднийг олж илрүүлэх зуршил өнөөг хүртэл хэвээр байна: бараг бүх арилжааны, тийм ч арилжааны биш сайтууд дээр би зохиогчдын холбоосыг хайж олохын тулд хөлийн доод хэсгээс үргэлжлүүлэн хайж байна.

Санаагаа хэрэгжүүлэх

Эхний хувилбар нь миний хувийн вэбсайт дээрх html хуудас байсан бөгөөд би гарын үсэг бүхий холбоосыг ul list-д оруулсан. Хэсэг хугацааны турш 20 хуудас шивж бичсэний дараа энэ нь тийм ч үр дүнтэй биш гэж бодож эхэлсэн бөгөөд процессыг автоматжуулахаар шийдсэн. Stackoverflow дээр би олон хүмүүс өөрсдийн профайл дээр сайтуудыг зааж байгааг анзаарсан тул би php дээр задлан шинжлэгч бичсэн бөгөөд энэ нь эхнийхээс эхлээд профайлыг гүйлгэн уншсан (Одоог хүртэл SO дээрх хаягууд дараах байдалтай байна: `/users/1` ), хүссэн хаягаас холбоосыг гаргаж аваад SQLite дээр нэмсэн.

Үүнийг хоёр дахь хувилбар гэж нэрлэж болно: HTML дэх статик жагсаалтыг орлуулсан SQLite хүснэгт дэх хэдэн арван мянган URL-н цуглуулга. Би энэ жагсаалтад энгийн хайлт хийсэн. Учир нь Зөвхөн URL-ууд байсан, дараа нь хайлт нь ердөө л тэдгээрт тулгуурласан байсан.

Энэ үе шатанд би төслөө орхиж, удаан хугацааны дараа түүнд буцаж ирсэн. Энэ үе шатанд миний ажлын туршлага аль хэдийн гурван жил гаруй байсан бөгөөд би илүү ноцтой зүйл хийж чадна гэдгээ мэдэрсэн. Үүнээс гадна харьцангуй шинэ технологи эзэмших хүсэл их байсан.

Орчин үеийн хувилбар

Төсөл Docker-д байршуулснаар мэдээллийн баазыг mongoDb руу шилжүүлсэн бөгөөд саяхан зөвхөн кэш хийх зориулалттай улаан лууван нэмсэн. PHP микрофрэймворкуудын нэгийг суурь болгон ашигладаг.

асуудал

Дараах үйлдлийг синхроноор гүйцэтгэдэг консолын тушаалаар шинэ сайтуудыг нэмдэг:

  • Контентыг URL-аар татаж авдаг
  • HTTPS боломжтой эсэхийг харуулах тугийг тохируулна
  • Вэб сайтын мөн чанарыг хадгалдаг
  • Эх сурвалжийн HTML болон толгой хэсгийг "индексжүүлэх" түүхэнд хадгалсан
  • Агуулгыг задлан гарчиг, тайлбарыг задлах
  • Өгөгдлийг тусдаа цуглуулгад хадгалдаг

Энэ нь зүгээр л сайтуудыг хадгалж, жагсаалтад харуулахад хангалттай байсан:

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Гэхдээ бүх зүйлийг автоматаар индексжүүлж, ангилж, эрэмбэлж, бүх зүйлийг шинэчилж байх санаа нь энэ парадигмд тийм ч сайн тохирохгүй байв. Хуудас нэмэхийн тулд зүгээр л вэб аргыг нэмэхэд ч кодыг давхардуулж, боломжит DDoS-ээс зайлсхийхийн тулд блоклох шаардлагатай болдог.

Ерөнхийдөө мэдээжийн хэрэг бүх зүйлийг синхроноор хийх боломжтой бөгөөд вэб аргын хувьд та URL-г зүгээр л хадгалах боломжтой бөгөөд ингэснээр аймшигт демон жагсаалтаас URL-уудын бүх ажлыг гүйцэтгэдэг. Гэсэн хэдий ч, энд ч гэсэн "дараалал" гэдэг үг өөрийгөө харуулж байна. Хэрэв дараалал хэрэгжсэн бол бүх ажлыг хувааж, дор хаяж асинхроноор гүйцэтгэж болно.

шийдвэр

Дарааллыг хэрэгжүүлж, бүх ажлыг боловсруулах үйл явдалд суурилсан системийг бий болго. Би Redis Streams-ийг туршиж үзэхийг удаан хугацаанд хүсч байсан.

PHP дээр Redis урсгалыг ашиглах

Учир нь Миний хүрээ нь Symfony, Laravel, Yii гурван аварга томуудын нэг биш учраас би бие даасан номын сан хайж олохыг хүсч байна. Гэхдээ (анхны үзлэгээр) бие даасан ноцтой номын санг олох боломжгүй болсон. Дараалалтай холбоотой бүх зүйл бол таван жилийн өмнөх 3 удаагийн төсөл юм уу, эсвэл хүрээтэй холбоотой.

Би Symfony-г бие даасан ашигтай бүрэлдэхүүн хэсгүүдийн нийлүүлэгчийн талаар маш их сонссон бөгөөд заримыг нь аль хэдийн ашигладаг. Мөн түүнчлэн Laravel-ийн зарим зүйлийг, тухайлбал, тэдгээрийн ORM-г, өөрөө хүрээгүйгээр ашиглаж болно.

symfony/messenger

Эхний нэр дэвшигч тэр даруйдаа тохиромжтой мэт санагдаж, би үүнийг суулгасан. Гэхдээ Symfony-ээс гадуур ашиглах жишээг google-ээс хайх нь илүү хэцүү болсон. Бүх нийтийн, утгагүй нэртэй, мессеж дамжуулах автобус, тэр ч байтугай Redis дээр олон ангиас хэрхэн цуглуулах вэ?

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Албан ёсны сайт дээрх баримт бичгүүд нь нэлээд нарийвчилсан байсан ч эхлүүлэх тохиргоог зөвхөн Symfony-д зориулж тэдний дуртай YML болон симфонист бус хүмүүст зориулсан бусад ид шидийн аргуудыг ашиглан тайлбарласан. Би угсралтын процессыг өөрөө сонирхдоггүй байсан, ялангуяа шинэ жилийн баярын үеэр. Гэхдээ би үүнийг санаанд оромгүй удаан хугацаанд хийх хэрэгтэй болсон.

Symfony эх сурвалжийг ашиглан системийг хэрхэн загварчлахыг олж мэдэхийг оролдох нь эцсийн хугацаатай тул хамгийн энгийн ажил биш юм.

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Энэ бүхнийг сайтар судалж, гараараа ямар нэгэн зүйл хийхийг оролдсоны эцэст би нэг төрлийн суга таяг хийж байна гэсэн дүгнэлтэд хүрч, өөр зүйл туршиж үзэхээр шийдсэн.

гэрэлтүүлэгтэй/дараалалтай

Энэ номын сан нь Ларавелийн дэд бүтэц болон бусад олон хамааралтай нягт холбоотой байсан тул би үүнд их цаг зарцуулаагүй: би үүнийг суулгаж, харж, хамаарлыг нь хараад устгасан.

yiisoft/yii2-дараалал

За, энд тэр даруй нэрнээс нь таамаглаж байсан, дахин, Yii2-тай хатуу холболт. Би энэ номын санг ашиглах ёстой байсан бөгөөд энэ нь тийм ч муу биш байсан ч энэ нь Yii2-ээс бүрэн хамааралтай гэж би бодоогүй.

Үлдсэн хэсэг

GitHub дээр миний олж мэдсэн бусад бүх зүйл бол од, сэрээ, олон тооны үүрэг хариуцлагагүй, найдваргүй, хуучирсан, орхигдсон төслүүд байсан.

Symfony/messenger, техникийн мэдээлэл рүү буцах

Би энэ номын санг олох ёстой байсан бөгөөд дахиад хэсэг хугацаа зарцуулсны дараа би чадсан. Бүх зүйл маш товч бөгөөд энгийн байсан нь тодорхой болов. Автобусыг загварчлахын тулд би жижиг үйлдвэр хийсэн, учир нь ... Би хэд хэдэн дугуйтай, янз бүрийн жолоодлоготой байх ёстой байсан.

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Хэдхэн алхам:

  • Бид зүгээр л дуудах боломжтой мессеж боловсруулагчийг бий болгодог
  • Бид тэдгээрийг HandlerDescriptor-д боож өгдөг (номын сангийн анги)
  • Бид эдгээр "Тодорхойлогчдыг" HandlersLocator жишээнд оруулдаг
  • HandlersLocator-г MessageBus-д нэмж байна
  • Бид SendersLocator руу `SenderInterface`-ийн багцыг дамжуулдаг, миний хувьд `RedisTransport` ангиуд нь тодорхой байдлаар тохируулагдсан байдаг.
  • MessageBus жишээнд SendersLocator нэмж байна

MessageBus нь `->dispatch()` аргатай бөгөөд 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([
                AppMessagesSecretJsonMessages::class => [CONTAINER_REDIS_TRANSPORT_SECRET],
                AppMessagesDaemonLogMessage::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 AppMessagesDaemonLogMessage;
use SymfonyComponentMessengerHandlerHandlerDescriptor;
use SymfonyComponentMessengerHandlerHandlersLocator;
use SymfonyComponentMessengerMessageBus;
use SymfonyComponentMessengerMiddlewareHandleMessageMiddleware;
use SymfonyComponentMessengerMiddlewareSendMessageMiddleware;
use SymfonyComponentMessengerTransportSenderSendersLocator;

require_once __DIR__ . '/../vendor/autoload.php';
/** @var PsrContainerContainerInterface $container */
$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 SymfonyComponentMessengerWorker($receivers, $bus, $container->get(CONTAINER_EVENT_DISPATCHER));
$w->run();

Энэ дэд бүтцийг програмд ​​ашиглах

Автобусыг өөрийн арын хэсэгт хэрэгжүүлсний дараа би хуучин синхрон командаас тусдаа үе шатуудыг салгаж, тус бүр нь өөр өөрийн гэсэн зүйлийг хийдэг тусдаа зохицуулагч хийсэн.

Мэдээллийн санд шинэ сайт нэмэх шугам дараах байдалтай байв.

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Үүний дараагаар надад шинэ функц нэмэх, жишээлбэл, Rss-г задлах, задлах нь илүү хялбар болсон. Учир нь Энэ процесс нь мөн анхны контентыг шаарддаг бөгөөд дараа нь WebsiteIndexHistoryPersistor гэх мэт RSS холбоос олборлогч нь "Content/HtmlContent" мессежийг захиалж, түүнийг боловсруулж, хүссэн мессежийг дамжуулах шугамын дагуу дамжуулдаг.

PHP backend-ийг Redis streams автобус руу шилжүүлж, хүрээнээс хамааралгүй номын санг сонгох

Эцэст нь бид хэд хэдэн демонтой болсон бөгөөд тус бүр нь зөвхөн шаардлагатай нөөцтэй холбоотой байдаг. Жишээлбэл, чөтгөр мөлхөгчид агуулгын хувьд интернетэд орох шаардлагатай бүх зохицуулагч болон дэмоныг агуулдаг тууштай байх мэдээллийн сантай холбогдох боломжтой.

Одоо мэдээллийн баазаас сонгохын оронд персистер оруулсны дараа шаардлагатай ID-г автобусаар дамжуулан сонирхсон бүх зохицуулагчдад дамжуулдаг.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх