Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

Prélude

U mo situ web, chì aghju gestitu cum'è un hobby, hè pensatu per accoglie pagine di casa interessanti è siti persunali. Stu tema hà cuminciatu à interessà à mè à l'iniziu di u mo viaghju di prugrammazione; in quellu mumentu era affascinatu da truvà grandi prufessiunali chì scrivenu nantu à elli stessi, i so passatempi è prughjetti. L'abitudine di scopre per mè stessu ferma finu à questu ghjornu: in quasi ogni situ cummerciale è micca assai cummerciale, cuntinuu à circà in u footer in cerca di ligami à l'autori.

Implementazione di l'idea

A prima versione era solu una pagina html in u mo situ persunale, induve aghju messu ligami cù signature in una lista ul. Dopu avè scrittu pagine 20 per un periudu di tempu, aghju cuminciatu à pensà chì questu ùn era micca assai efficace è decisu di pruvà à automatizà u prucessu. Nantu à stackoverflow, aghju nutatu chì assai persone indicanu siti in i so profili, cusì aghju scrittu un parser in php, chì simpricimenti passava per i profili, cuminciendu cù u primu (l'indirizzi in SO à questu ghjornu sò cusì: `/users/1` ), estratti ligami da u tag desideratu è aghjunghjenu in SQLite.

Questu pò esse chjamatu a seconda versione: una cullizzioni di decine di millaie di URL in una tavola SQLite, chì sustituì a lista statica in html. Aghju fattu una ricerca simplice nantu à sta lista. Perchè ci era solu URL, allora a ricerca era simplicemente basatu annantu à elli.

In questu stadiu, aghju abbandunatu u prugettu è turnò à ellu dopu un bellu pezzu. À questu stadiu, a mo sperienza di travagliu era digià più di trè anni è mi sentu chì puderia fà qualcosa di più seriu. Inoltre, ci era un grande desideriu di maestru di tecnulugia relativamente novi.

Versione muderna

U prugettu implementatu in Docker, a basa di dati hè stata trasferita à mongoDb, è più recentemente, u ravane hè statu aghjuntu, chì prima era solu per caching. Unu di i microframeworks PHP hè utilizatu com'è basa.

prublemu

I siti novi sò aghjuntu da un cumandamentu di cunsola chì faci in modu sincronu i seguenti:

  • Scaricate u cuntenutu per URL
  • Stabilisce una bandiera chì indica se HTTPS era dispunibule
  • Preserva l'essenza di u situ web
  • L'HTML fonte è l'intestazione sò salvati in a storia di "indexazione".
  • Analizza u cuntenutu, estrae Titulu è Descrizzione
  • Salvà i dati in una cullizzioni separata

Questu era abbastanza per almacenà i siti è vedeli in una lista:

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

Ma l'idea di indexà automaticamente, categurizà è classificà tuttu, mantene tuttu aghjurnatu, ùn hè micca bè in stu paradigma. Ancu solu aghjunghje un metudu web per aghjunghje e pagine necessitava duplicazione di codice è bloccu per evità DDoS potenziale.

In generale, sicuru, tuttu pò esse fattu in sincronia, è in u metudu web pudete simpliciamente salvà l'URL in modu chì u daemon monstruosu faci tutti i travaglii per l'URL da a lista. Ma ancu quì, a parolla "queue" si suggerisce. È se una fila hè implementata, allora tutti i travaglii ponu esse divisi è eseguiti almenu in modu asincronu.

dicisioni

Implementa file di fila è crea un sistema guidatu da l'avvenimenti per trattà tutte e tarei. È aghju vulutu pruvà Redis Streams per un bellu pezzu.

Utilizà i flussi Redis in PHP

Perchè Siccomu u mo quadru ùn hè micca unu di i trè giganti Symfony, Laravel, Yii, mi piacerebbe truvà una biblioteca indipendente. Ma, cum'è risultò (in u primu esame), hè impussibile di truvà biblioteche serii individuali. Tuttu ciò chì hè in relazione cù e fila hè o un prughjettu da 3 commits cinque anni fà, o hè ligatu à u quadru.

Aghju intesu parlà assai di Symfony cum'è un fornitore di cumpunenti utili individuali, è aghju digià utilizatu alcuni di elli. È ancu alcune cose da Laravel ponu ancu esse aduprate, per esempiu u so ORM, senza a presenza di u quadru stessu.

symfony/messenger

U primu candidatu paria immediatamente ideale è senza dubbitu l'aghju stallatu. Ma hè diventatu più difficiule di google esempi di usu fora di Symfony. Cumu assemble un autobus per passà missaghji da una mansa di classi cù nomi universali, senza significatu, è ancu in Redis?

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

A ducumentazione nantu à u situ ufficiale era abbastanza detallata, ma l'inizializazione hè stata descritta solu per Symfony cù u so YML preferitu è ​​altri metudi magichi per u non-symphonist. Ùn aghju avutu micca interessu in u prucessu di stallazione stessu, soprattuttu durante e vacanze di l'annu novu. Ma aghju avutu à fà questu per un tempu inesperu longu.

Pruvate di scopre cumu istanzià un sistema utilizendu fonti Symfony ùn hè ancu u compitu più triviale per una scadenza stretta:

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

Dopu avè sfondatu in tuttu questu è pruvatu à fà qualcosa cù e mo mani, aghju ghjuntu à a cunclusione chì facia un tipu di crutches è decisu di pruvà una altra cosa.

illuminatu / fila

Risultava chì sta biblioteca era strettamente ligata à l'infrastruttura di Laravel è una mansa di altre dipendenze, perchè ùn aghju micca passatu assai tempu nantu à questu: l'aghju stallatu, l'aghju guardatu, vitti i dependenzii è sguassate.

yiisoft/yii2-queue

Ebbè, quì era subitu assulutu da u nome, di novu, una stretta cunnessione à Yii2. Aviu avutu aduprà sta libreria è ùn era micca male, ma ùn aghju micca pensatu à u fattu chì dipende completamente da Yii2.

U restu

Tuttu u restu chì aghju trovu in GitHub era prughjetti inaffidabili, obsoleti è abbandunati senza stelle, forche è un gran numaru di cummissioni.

Ritorna à symfony/messenger, dettagli tecnichi

Aviu avutu à capisce sta biblioteca è, dopu avè passatu un pocu di più tempu, aghju pussutu. Hè risultatu chì tuttu era abbastanza cuncisu è simplice. Per instantà l'autobus, aghju fattu una piccula fabbrica, perchè... Aviu avutu à avè parechji pneumatici è cù manere differenti.

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

Solu uni pochi di passi:

  • Creemu gestori di messagi chì deve esse simpliciamente chjamati
  • L'imbulighjemu in HandlerDescriptor (classe da a biblioteca)
  • Imbulighjamu questi "Descriptori" in una istanza di HandlersLocator
  • Aghjunghjendu HandlersLocator à l'istanza MessageBus
  • Passemu un inseme di "SenderInterface" à SendersLocator, in u mo casu casi di classi "RedisTransport", chì sò cunfigurati in modu evidenti.
  • Aghjunghjendu SendersLocator à l'istanza MessageBus

MessageBus hà un metudu `->dispatch()` chì cerca i gestori apprupriati in u HandlersLocator è li passa u missaghju, utilizendu u currispundente `SenderInterface' per mandà via l'autobus (redis streams).

In a cunfigurazione di u containeru (in questu casu php-di), stu bundle sanu pò esse cunfiguratu cusì:

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

Quì pudete vede chì in SendersLocator avemu attribuitu diversi "trasporti" per dui messagi diffirenti, ognunu hà a so propria cunnessione cù i flussi currispondenti.

Aghju fattu un prughjettu demo separatu chì mostra una applicazione di trè demoni chì cumunicanu cù l'altri usendu u bus seguente: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Ma vi mustraraghju cumu un cunsumadore pò esse strutturatu:

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

Utilizà sta infrastruttura in una applicazione

Dopu avè implementatu l'autobus in u mo backend, aghju separatu tappe individuali da u vechju cumandamentu sincronu è hà fattu manigliari separati, chì ognunu face u so propiu.

U pipeline per aghjunghje un novu situ à a basa di dati pareva cusì:

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

È subitu dopu, hè diventatu assai più faciule per mè per aghjunghje una nova funziunalità, per esempiu, estrazione è parsing Rss. Perchè stu prucessu hà ancu bisognu di u cuntenutu uriginale, allora u gestore di l'estrattore di ligami RSS, cum'è WebsiteIndexHistoryPersistor, sottumette à u missaghju "Content/HtmlContent", u processa è passa u missaghju desideratu longu u so pipeline.

Trasferendu u backend PHP à l'autobus Redis streams è sceglie una libreria indipendente di framework

À a fine, avemu finitu cù parechji demoni, ognuna di quali mantene e cunnessione solu à e risorse necessarie. Per esempiu un dimòniu cinghiali cuntene tutti i gestori chì necessitanu andà in Internet per u cuntenutu, è u demoniu persiste tene una cunnessione à a basa di dati.

Avà, invece di selezziunà da a basa di dati, l'ids richiesti dopu l'inserimentu da u persistente sò simpliciamente trasmessi via l'autobus à tutti i gestori interessati.

Source: www.habr.com

Add a comment