PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Eessõna

Minu veebisait, millega ma tegelevan hobina, on mõeldud huvitavate veebilehtede ja isiklike saitide säilitamiseks. See teema hakkas mind huvitama juba minu programmeerimise alguses, mil mind vapustas, kui avastasin suuri professionaale, kes kirjutavad endast, oma hobidest ja projektidest. Harjumus neid avastada on jäänud: peaaegu igal kaubanduslikul ja vähem kaubanduslikul leheküljel, kuhu ma jään, vaatan ikka jalgu, et leida linke autoritele.

Idee elluviimine

Esimene versioon oli lihtsalt HTML-leht minu isiklikul veebisaidil, kus kogusin linke koos allkirjadega ul-loetelusse. Kui olin mitme aja jooksul kogunud 20 lehekülge, hakkasin mõtlema, et see ei ole väga efektiivne, ja otsustasin proovida protsessi automatiseerida. Stackoverflow'is märkasin, et paljud näitavad oma profiilides linke, seega kirjutasin php-s parseri, mis lihtsalt läks profiilide kaudu, alustades esimesest (SO aadressid on siiani sellise kujuga: `/users/1`), tõmbas vajalikust sildist lingid ja talletasin need SQLite'i.

Seda võib nimetada teiseks versiooniks: kogu, mis sisaldab kümneid tuhandeid URL-e SQLite tabelis, mis asendas HTML-i staatilist loendit. Selle loendi põhjal tegin lihtsa otsingu. Kuna olid ainult URL-id, siis oli ka otsing lihtsalt nende järgi.

Sellel etapil jätsin projekti pooleli ja naasin selle juurde pärast pikka aega. Selles etapis oli mu töökogemus juba üle kolme aasta ja tundsin, et suudan teha midagi tõsisemalt. Lisaks oli suur soov omandada suhteliselt uusi tehnoloogiaid.

Kaasaegne versioon

Projekt on käivitatud Dockeris, andmebaas on viidud mongoDb-le, ja suhteliselt hiljuti on lisatud Redis, mis algselt oli lihtsalt vahemälu jaoks. Alusena kasutatakse ühte PHP mikrokeskkonnast.

Probleem

Uued saidid lisatakse konsoolikäsklusega, mis üheaegselt teeb järgmist:

  • Laadib sisu URL-ilt
  • Seab lipu, kas HTTPS oli saadaval
  • Salvestab veebisaidi entiteedi
  • Salvestab originaali HTML ja päised 'indekseerimise' ajalukku
  • Parsib sisu, extrahib Title ja Description
  • Andmed salvestatakse eraldi kollektsiooni

Seda oli piisavalt, et lihtsalt salvestada saidid ja kuvada need loendis:

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Kuid idee, et kõik automaatselt indekseerida, kategoriseerida ja reastada, hoides kõik värske, sobis sellesse paradigmasse nõrgalt. Isegi lihtne web-meetodi lisamine lehtede lisamiseks nõudis koodikordust ja lukustusi võimalike DDoS-de vältimiseks.

Üldiselt võib muidugi kõike teha ka sünkroonselt, ning web-meetodis lihtsalt salvestada URL, et monstrum demon täidaks kõik ülesanded listis olevate URL-idega. Siiski, isegi siin tuleb mängu sõna 'järjekord'. Ja kui järjekordsüsteem rakendada, siis saab kõik ülesanded jagada ja täita vähemalt asünkroonselt.

Lahendus

Rakendada järjekordi ja luua sündmustepõhine süsteem kõigi ülesannete töötlemiseks. Ja ma olen juba ammu tahtnud proovida Redis Streams'i.

Redis streams'i kasutamine PHP-s

Kuna mu raamistik ei ole kolmest hiiglasest Symfony, Laravel, Yii, siis tahaksin leida sõltumatu teegi. Kuid nagu selgus (esimesel ülevaatusel) — tõsiseid eraldi teeke on raske leida. Kõik, mis on seotud järjekordadega, on kas projektikene, millel on 3 commit'i viis aastat tagasi, või on seotud raamistikuga.

Olen kuulnud Symfony'st kui kasulikest komponentide pakkujast ning kasutan juba mõningaid. Samuti on Laravelist teatud asju võimalik rakendada, näiteks nende ORM, ilma et peaks ise raamistikku kasutama.

symfony/messenger

Esimene kandidaat tundus kohe ideaalne ja ilma igasuguste kahtlusteta installisin ma selle. Kuid Symfonyst väljaspool kasutamise näidete leidmine osutus keerulisemaks. Kuidas ehitada hulga klasside hulgast, millel on universaalsed ja tähendusteta nimed, sõnumite edastamise süsteem, veelgi enam Redis'e peale?

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Ametlikul kodulehel olev dokumentatsioon oli suhteliselt üksikasjalik, kuid initsialiseerimine oli kirjeldatud ainult Symfony jaoks, kasutades nende armastatud YML-i ja teisi maagilisi meetodeid mitte-sümfoonia jaoks. Mul ei olnud installimise protsessist mingit huvi, eriti uue aasta puhkuse ajal. Kuid pidin selle kallale minema ja ootamatult pikka aega.

Symfony lähtekoodide alusel süsteemi instantside väljamõtlemine ei ole samuti kõige lihtsam ülesanne lühikeste tähtaegade jaoks:

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Kuna ma kaevasin kõike läbi ja proovisin midagi ise teha, jõudsin järeldusele, et tegeledes mingite ajutiste lahendustega, ning otsustasin proovida midagi muud.

illuminate/queue

Selgus, et see teekond on tugevalt seotud Laravel'i infrastruktuuri ja paljude teiste sõltuvustega, seega ei raisanud ma sellele palju aega: installisin, vaatasin, nägin sõltuvusi ja kustutasin.

yiisoft/yii2-queue

Siin eeldati kohe nimest, et on tugev seos Yii2'ga. Olen kasutanud seda teeki ja see oli päris hea, kuid et see sõltub täielikult Yii2-st, ei tulnud mul see mõtte.

Ülejäänud

Kõik muud, mida ma GitHub'is leidsin – on ebausaldusväärsed, vananenud ja hüljatud projektid, millel pole tähti, kahvleid ega suurt hulka commit'e.

Tagasi symfony/messenger'i, tehnilised üksikasjad

Pidin selle teegiga tutvuma ja pärast natuke veel aega kulutades suutsin. Selgus, et kõik on piisavalt lakooniline ja lihtne. Busside instanteerimiseks tegin väikese tehase, kuna planeerisin mitut bussi ja erinevaid töötlejaid.

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Ainult paar sammu:

  • Loome sõnumite töötlejad, mis peavad olema lihtsalt callable
  • Kapseldame need HandlerDescriptor'i (klass teegist)
  • Need „Deskriptorid“ kapseldame HandlersLocator'i instantsi
  • Lisame HandlersLocator'i MessageBus'i instantsi
  • Edastame SendersLocator'i `SenderInterface` komplekti, minu puhul klasside `RedisTransport` instantsid, mis konfigureeritakse ilmselt.
  • Lisame SendersLocator'i MessageBus'i instantsi.

MessageBus'il on meetod `->dispatch()`, mis otsib vastavaid töötlejaid HandlersLocator'ist ja edastab sõnumi neile, kasutades vastavaid `SenderInterface`-i sõnumite saatmiseks väe kaudu (Redis voogusid).

Konteineri konfiguratsioonis (antud juhul php-di) saab kogu selle seose konfigureerida järgmiselt:

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

Siin on näha, et SendersLocatoris on kahte erinevat sõnumit määratud erinevad "transport", millest igaühel on oma ühendus vastavate voogudega.

Loon eraldi demoprojekti, mis demonstreerib rakendust kolmest demonist, kes omavahel suhtlevad sellise bussiga: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Kuid näitan, kuidas võiks olla korraldatud tarbija:

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

Selle infrastruktuuri kasutamine rakenduses

Tagaküljel bussi rakendamisega eraldasin vanast sünkroonilisest käsklusest eraldi etapid ja tegin eraldi käsitlejad, millest igaüks tegeleb oma asjaga.

Uue saidi lisamise protsess andmebaasi näeb välja selline:

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Ja kohe pärast seda muutus uue funktsiooni, näiteks Rss-i kraapimise ja analüüsi, lisamine palju lihtsamaks. Kuna see protsess vajab ka algset sisu, siis rss-i eraldaja käsitleja, nagu ka WebsiteIndexHistoryPersistor, tellib sõnumile «Content/HtmlContent», töötleb seda ja edastab vajaliku sõnumi oma protsessivoolus edasi.

PHP taustakoodide üleminek Redis streams bussile ja raamistikuvaba teegi valik

Lõppkokkuvõttes saime mitu deemonit, millest igaüks hoiustab ühendusi ainult vajalike ressurssidega. Näiteks deemon crawlers kasutab kõiki käsitlejaid, mis nõuavad internetti sisu saamiseks, samas kui deemon persister hoiab ühendust andmebaasiga.

Nüüd, selle asemel et teha päringuid andmebaasist, edastatakse vajaliku ID-d pärast persister'iga lisamist lihtsalt bussis kõikidele huvitatud käsitlejatele.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster