PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

pratarmė

Mano svetainė, kurią naudoju kaip pomėgį, yra skirta įdomiems namų puslapiams ir asmeninėms svetainėms talpinti. Ši tema mane domino pačioje programavimo kelionės pradžioje, tuo metu mane sužavėjo puikių profesionalų, rašančių apie save, savo pomėgius ir projektus. Įprotis jas atrasti pačiam išliko iki šiol: beveik kiekvienoje komercinėje ir nelabai komercinėje svetainėje toliau dairausi poraštėje ieškodama nuorodų į autorius.

Idėjos įgyvendinimas

Pirmoji versija buvo tik html puslapis mano asmeninėje svetainėje, kur nuorodas su parašais įdėjau į ul sąrašą. Per tam tikrą laiką įvedęs 20 puslapių, pradėjau galvoti, kad tai nėra labai efektyvu, ir nusprendžiau pabandyti automatizuoti procesą. Stackoverflow metu pastebėjau, kad daugelis žmonių savo profiliuose nurodo svetaines, todėl php parašiau analizatorių, kuris tiesiog perėjo profilius, pradedant nuo pirmojo (adresai SO iki šiol yra tokie: `/users/1` ), ištraukė nuorodas iš norimos žymos ir įtraukė ją į SQLite.

Tai galima pavadinti antrąja versija: dešimčių tūkstančių URL rinkinys SQLite lentelėje, kuris pakeitė statinį sąrašą html. Atlikau paprastą paiešką šiame sąraše. Nes buvo tik URL, tada paieška buvo tiesiog pagal juos.

Šiame etape apleidau projektą ir po ilgo laiko grįžau prie jo. Šiame etape mano darbo stažas jau buvo daugiau nei treji metai ir jaučiau, kad galiu nuveikti ką nors rimtesnio. Be to, buvo didelis noras įvaldyti palyginti naujas technologijas.

Šiuolaikinė versija

Projektas įdiegtas Docker, duomenų bazė buvo perkelta į mongoDb, o visai neseniai buvo pridėtas ridikas, kuris iš pradžių buvo skirtas tik talpyklai. Kaip pagrindas naudojamas vienas iš PHP mikrorėmų.

problema

Naujos svetainės pridedamos naudojant konsolės komandą, kuri sinchroniškai atlieka šiuos veiksmus:

  • Atsisiunčia turinį pagal URL
  • Nustato vėliavėlę, rodančią, ar HTTPS buvo pasiekiamas
  • Išsaugoma svetainės esmė
  • Šaltinio HTML ir antraštės išsaugomi „indeksavimo“ istorijoje
  • Nagrinėja turinį, ištraukia pavadinimą ir aprašymą
  • Išsaugo duomenis į atskirą rinkinį

To pakako, kad būtų galima tiesiog saugoti svetaines ir parodyti jas sąraše:

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Tačiau idėja automatiškai viską indeksuoti, suskirstyti į kategorijas ir reitinguoti, viską atnaujinti, nelabai tiko šiai paradigmai. Netgi tiesiog pridedant žiniatinklio metodą puslapiams pridėti reikėjo kodo dubliavimo ir blokavimo, kad būtų išvengta galimo DDoS.

Apskritai, žinoma, viską galima padaryti sinchroniškai, o žiniatinklio metodu galite tiesiog išsaugoti URL, kad monstrinis demonas atliktų visas URL iš sąrašo užduotis. Tačiau net ir čia žodis „eilė“ rodo save. O jei įdiegta eilė, tuomet visas užduotis galima padalinti ir atlikti bent jau asinchroniškai.

sprendimas

Įdiekite eiles ir sukurkite įvykiais pagrįstą sistemą visoms užduotims apdoroti. Ir aš jau seniai norėjau išbandyti Redis Streams.

„Redis“ srautų naudojimas PHP

Nes Kadangi mano sistema nėra viena iš trijų milžinų Symfony, Laravel, Yii, norėčiau rasti nepriklausomą biblioteką. Bet, kaip paaiškėjo (pirmą kartą apžiūrėjus), atskirų rimtų bibliotekų rasti neįmanoma. Viskas, kas susiję su eilėmis, yra arba projektas iš 3 įsipareigojimų prieš penkerius metus, arba yra susietas su sistema.

Daug girdėjau apie Symfony kaip atskirų naudingų komponentų tiekėją ir kai kuriuos jau naudoju. Be to, kai kurie Laravel dalykai taip pat gali būti naudojami, pavyzdžiui, jų ORM, be pačios sistemos.

simfonija / pasiuntinys

Pirmasis kandidatas iš karto atrodė idealus ir be jokios abejonės jį įdiegiau. Tačiau pasirodė, kad „Google“ rasti naudojimo pavyzdžių už „Symfony“ ribų buvo sunkiau. Kaip surinkti autobusą žinutėms perduoti iš daugybės klasių universaliais, beprasmiais pavadinimais ir net Redis?

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Dokumentacija oficialioje svetainėje buvo gana išsami, tačiau inicijavimas buvo aprašytas tik Symfony naudojant mėgstamą YML ir kitus magiškus metodus, skirtus ne simfonistui. Pats diegimo procesas man nebuvo įdomus, ypač per Naujųjų metų šventes. Bet man teko tai daryti netikėtai ilgai.

Bandymas išsiaiškinti, kaip sukurti sistemos egzempliorius naudojant Symfony šaltinius, taip pat nėra pati trivialiausia užduotis per trumpą terminą:

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Į visa tai įsigilinęs ir pabandęs kažką daryti rankomis, padariau išvadą, kad darau kažkokius ramentus ir nusprendžiau pabandyti dar ką nors.

apšviesta / eilė

Paaiškėjo, kad ši biblioteka buvo glaudžiai susieta su Laravel infrastruktūra ir daugybe kitų priklausomybių, todėl daug laiko jai neskiriau: įdiegiau, pažiūrėjau, pamačiau priklausomybes ir ištryniau.

yiisoft/yii2-eilė

Na, čia iš karto buvo manoma iš pavadinimo, vėlgi, griežtas ryšys su Yii2. Teko naudotis šia biblioteka ir ji nebuvo bloga, bet nepagalvojau, kad ji visiškai priklauso nuo Yii2.

Likusieji

Visa kita, ką radau „GitHub“, buvo nepatikimi, pasenę ir apleisti projektai be žvaigždžių, šakučių ir daugybės įsipareigojimų.

Grįžti į Symfony/Messenger, techninės detalės

Turėjau išsiaiškinti šią biblioteką ir, praleidęs daugiau laiko, man pavyko. Paaiškėjo, kad viskas buvo gana glausta ir paprasta. Norėdami sukurti autobusą, aš padariau nedidelę gamyklą, nes... Turėjau turėti kelias padangas ir su skirtingais tvarkytojais.

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Tik keli žingsniai:

  • Kuriame pranešimų tvarkykles, kurios turėtų būti tiesiog skambinamos
  • Mes apvyniojame juos HandlerDescriptor (klasė iš bibliotekos)
  • Šiuos „deskriptorius“ apvyniojame „HandlersLocator“ egzemplioriuje
  • „HandlersLocator“ įtraukimas į „MessageBus“ egzempliorių
  • Perduodame „SenderInterface“ rinkinį „SendersLocator“, mano atveju „RedisTransport“ klasių, kurios sukonfigūruotos akivaizdžiai
  • „SendersLocator“ įtraukimas į „MessageBus“ egzempliorių

„MessageBus“ turi „->dispatch()“ metodą, kuris „HandlersLocator“ suranda atitinkamas tvarkykles ir perduoda jiems pranešimą, naudodamas atitinkamą „SenderInterface“, kad išsiųstų per magistralę („Redis“ srautai).

Sudėtinio konfigūracijoje (šiuo atveju php-di) visą šį paketą galima sukonfigūruoti taip:

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

Čia galite pamatyti, kad „SendersLocator“ priskyrėme skirtingus „pervežimus“ dviem skirtingiems pranešimams, kurių kiekvienas turi savo ryšį su atitinkamais srautais.

Sukūriau atskirą demonstracinį projektą, demonstruojantį trijų demonų, bendraujančių tarpusavyje naudojant šią magistralę, taikomąją programą: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Bet aš jums parodysiu, kaip vartotojas gali būti struktūrizuotas:

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

Šios infrastruktūros naudojimas programoje

Įdiegęs magistralę savo backend'e, atskirus etapus atskyriau nuo senosios sinchroninės komandos ir sukūriau atskirus tvarkytuvus, kurių kiekvienas daro savo.

Naujos svetainės įtraukimo į duomenų bazę dujotiekis atrodė taip:

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Ir iškart po to man tapo daug lengviau pridėti naujų funkcijų, pavyzdžiui, išgauti ir analizuoti Rss. Nes Šiam procesui taip pat reikalingas originalus turinys, tada RSS nuorodų ištraukimo priemonė, kaip WebsiteIndexHistoryPersistor, užsiprenumeruoja pranešimą „Content/HtmlContent“, jį apdoroja ir toliau perduoda norimą pranešimą.

PHP backend perkėlimas į Redis srautų magistralę ir nuo sistemos nepriklausomos bibliotekos pasirinkimas

Galų gale gavome keletą demonų, kurių kiekvienas palaiko ryšius tik su reikiamais ištekliais. Pavyzdžiui, demonas skaitytuvai yra visos tvarkyklės, kurioms norint gauti turinį reikia prisijungti prie interneto, ir demonas išsilaikyti palaiko ryšį su duomenų baze.

Dabar, užuot pasirinkus iš duomenų bazės, reikiami ID po to, kai persisteris įterpiamas, per magistralę tiesiog perduodamas visiems suinteresuotiems tvarkytojams.

Šaltinis: www.habr.com

Добавить комментарий