Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Parathënie libri

Faqja ime e internetit, të cilën e drejtoj si hobi, është krijuar për të pritur faqe kryesore interesante dhe sajte personale. Kjo temë filloi të më interesonte që në fillim të rrugëtimit tim në programim; në atë moment u magjepsa nga gjetja e profesionistëve të mëdhenj që shkruajnë për veten, hobi dhe projektet e tyre. Zakoni për t'i zbuluar ato për veten time mbetet edhe sot e kësaj dite: pothuajse në çdo faqe komerciale dhe jo shumë komerciale, vazhdoj të shikoj në fund të faqes në kërkim të lidhjeve me autorët.

Zbatimi i idesë

Versioni i parë ishte vetëm një faqe html në faqen time personale, ku vendosa lidhje me nënshkrime në një listë ul. Duke shtypur 20 faqe gjatë një periudhe kohore, fillova të mendoj se kjo nuk ishte shumë efektive dhe vendosa të përpiqem të automatizoj procesin. Në stackoverflow, vura re se shumë njerëz tregojnë faqet në profilet e tyre, kështu që shkrova një analizues në php, i cili thjesht kaloi nëpër profile, duke filluar nga i pari (adresat në SO deri më sot janë si kjo: `/users/1` ), nxori lidhjet nga etiketa e dëshiruar dhe e shtoi atë në SQLite.

Ky mund të quhet versioni i dytë: një koleksion prej dhjetëra mijëra URL-ve në një tabelë SQLite, e cila zëvendësoi listën statike në HTML. Bëra një kërkim të thjeshtë në këtë listë. Sepse kishte vetëm URL, atëherë kërkimi bazohej thjesht në to.

Në këtë fazë e braktisa projektin dhe iu riktheva pas një kohe të gjatë. Në këtë fazë, përvoja ime e punës ishte tashmë më shumë se tre vjet dhe ndjeva se mund të bëja diçka më serioze. Për më tepër, kishte një dëshirë të madhe për të zotëruar teknologji relativisht të reja.

Version modern

Projekt e vendosur në Docker, baza e të dhënave u transferua në mongoDb dhe së fundmi u shtua rrepka, e cila në fillim ishte vetëm për caching. Një nga mikrokornizat PHP përdoret si bazë.

problem

Faqet e reja shtohen nga një komandë konsole që në mënyrë sinkronike bën sa më poshtë:

  • Shkarkon përmbajtjen sipas URL-së
  • Vendos një flamur që tregon nëse HTTPS ishte i disponueshëm
  • Ruan thelbin e faqes në internet
  • HTML-ja e burimit dhe titujt ruhen në historinë e "indeksimit".
  • Analizon përmbajtjen, nxjerr Titullin dhe Përshkrimin
  • Ruan të dhënat në një koleksion të veçantë

Kjo ishte e mjaftueshme për të ruajtur thjesht sajtet dhe për t'i shfaqur ato në një listë:

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Por ideja e indeksimit automatik, kategorizimit dhe renditjes së gjithçkaje, mbajtjes së gjithçkaje të përditësuar, nuk përshtatej mirë në këtë paradigmë. Edhe thjesht shtimi i një metode ueb për të shtuar faqe kërkon dyfishim dhe bllokim të kodit për të shmangur DDoS të mundshëm.

Në përgjithësi, natyrisht, gjithçka mund të bëhet në mënyrë sinkrone, dhe në metodën e uebit thjesht mund të ruani URL-në në mënyrë që daemon monstruoz të kryejë të gjitha detyrat për URL-të nga lista. Por megjithatë, edhe këtu fjala "radhë" sugjeron veten. Dhe nëse zbatohet një radhë, atëherë të gjitha detyrat mund të ndahen dhe kryhen të paktën në mënyrë asinkrone.

vendim

Zbatoni radhët dhe krijoni një sistem të drejtuar nga ngjarjet për përpunimin e të gjitha detyrave. Dhe unë kam dashur të provoj Redis Streams për një kohë të gjatë.

Përdorimi i transmetimeve Redis në PHP

Sepse Meqenëse korniza ime nuk është një nga tre gjigantët Symfony, Laravel, Yii, do të doja të gjeja një bibliotekë të pavarur. Por, siç doli (në ekzaminimin e parë), është e pamundur të gjesh biblioteka individuale serioze. Gjithçka që lidhet me radhët është ose një projekt nga 3 kryerje pesë vjet më parë, ose është i lidhur me kornizën.

Kam dëgjuar shumë për Symfony si furnizues i komponentëve individualë të dobishëm dhe tashmë i përdor disa prej tyre. Dhe gjithashtu disa gjëra nga Laravel mund të përdoren gjithashtu, për shembull ORM e tyre, pa praninë e vetë kornizës.

simfonia/lajmëtari

Kandidati i parë u duk menjëherë ideal dhe pa asnjë dyshim e instalova. Por doli të ishte më e vështirë për të kërkuar në google shembuj të përdorimit jashtë Symfony. Si të mblidhen nga një grup klasash me emra universalë, të pakuptimtë, një autobus për kalimin e mesazheve, madje edhe në Redis?

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Dokumentacioni në faqen zyrtare ishte mjaft i detajuar, por inicializimi u përshkrua vetëm për Symfony duke përdorur YML-në e tyre të preferuar dhe metoda të tjera magjike për josimfonistët. Nuk kisha asnjë interes për vetë procesin e instalimit, veçanërisht gjatë festave të Vitit të Ri. Por mua më duhej ta bëja këtë për një kohë të papritur të gjatë.

Përpjekja për të kuptuar se si të krijoni një sistem duke përdorur burimet e Symfony nuk është gjithashtu detyra më e parëndësishme për një afat të ngushtë:

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Pasi u futa në të gjitha këto dhe u përpoqa të bëja diçka me duart e mia, arrita në përfundimin se po bëja një lloj paterica dhe vendosa të provoja diçka tjetër.

ndriçuar/radhë

Doli që kjo bibliotekë ishte e lidhur fort me infrastrukturën Laravel dhe një mori varësish të tjera, kështu që nuk kalova shumë kohë për të: e instalova, e shikova, pashë varësitë dhe e fshiva.

yiisoft/yii2-radhë

Epo, këtu u supozua menjëherë nga emri, përsëri, një lidhje e rreptë me Yii2. Më duhej ta përdorja këtë bibliotekë dhe nuk ishte keq, por nuk mendova për faktin se varet plotësisht nga Yii2.

Pjesa tjetër

Gjithçka tjetër që gjeta në GitHub ishin projekte jo të besueshme, të vjetruara dhe të braktisura pa yje, pirunë dhe një numër të madh angazhimesh.

Kthehu te Symfony/Mesenger, detaje teknike

Më duhej ta gjeja këtë bibliotekë dhe, pasi kalova edhe pak kohë, arrita. Doli se gjithçka ishte mjaft koncize dhe e thjeshtë. Për të instancuar autobusin, bëra një fabrikë të vogël, sepse... Duhej të kisha disa goma dhe me mbajtës të ndryshëm.

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Vetëm disa hapa:

  • Ne krijojmë mbajtës mesazhesh që duhet të jenë thjesht të thirrshëm
  • Ne i mbështjellim ato në HandlerDescriptor (klasa nga biblioteka)
  • Ne i mbështjellim këta "Përshkrues" në një shembull HandlersLocator
  • Shtimi i HandlersLocator në shembullin MessageBus
  • Ne i kalojmë një grup `SenderInterface` te SendersLocator, në rastin tim shembuj të klasave `RedisTransport`, të cilat janë konfiguruar në një mënyrë të qartë
  • Shtimi i SendersLocator në shembullin MessageBus

MessageBus ka një metodë `->dispatch()` që kërkon mbajtësit e duhur në HandlersLocator dhe ua kalon mesazhin, duke përdorur `SenderInterface` përkatës për ta dërguar nëpërmjet autobusit (Redis streams).

Në konfigurimin e kontejnerit (në këtë rast php-di), e gjithë kjo paketë mund të konfigurohet si kjo:

        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);
        },

Këtu mund të shihni se në SendersLocator ne kemi caktuar "transporte" të ndryshme për dy mesazhe të ndryshme, secila prej të cilave ka lidhjen e vet me rrymat përkatëse.

Unë bëra një projekt të veçantë demo që demonstron një aplikim të tre demonëve që komunikojnë me njëri-tjetrin duke përdorur autobusin e mëposhtëm: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Por unë do t'ju tregoj se si mund të strukturohet një konsumator:

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();

Përdorimi i kësaj infrastrukture në një aplikacion

Pas implementimit të autobusit në backend-in tim, ndava fazat individuale nga komanda e vjetër sinkrone dhe bëra mbajtës të veçantë, secili prej të cilëve bën gjënë e tij.

Tubacioni për shtimin e një siti të ri në bazën e të dhënave dukej kështu:

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Dhe menjëherë pas kësaj, u bë shumë më e lehtë për mua të shtoja funksionalitete të reja, për shembull, nxjerrjen dhe analizimin e Rss. Sepse ky proces kërkon gjithashtu përmbajtjen origjinale, më pas mbajtësi i nxjerrjes së lidhjeve RSS, si WebsiteIndexHistoryPersistor, pajtohet në mesazhin "Content/HtmlContent", e përpunon atë dhe e kalon mesazhin e dëshiruar përgjatë tubacionit të tij më tej.

Transferimi i backend-it PHP në autobusin e transmetimeve Redis dhe zgjedhja e një biblioteke të pavarur nga korniza

Në fund, ne përfunduam me disa demonë, secili prej të cilëve mban lidhje vetëm me burimet e nevojshme. Për shembull një demon crawlers përmban të gjithë mbajtësit që kërkojnë të shkoni në internet për përmbajtje, dhe demonin këmbëngul mban një lidhje me bazën e të dhënave.

Tani, në vend që të zgjidhen nga baza e të dhënave, ID-të e kërkuara pas futjes nga personi i përhershëm thjesht transmetohen nëpërmjet autobusit te të gjithë mbajtësit e interesuar.

Burimi: www.habr.com

Shto një koment