Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Viruerteel

Meng Websäit, déi ech als Hobby lafen, ass entwéckelt fir interessant Heemsäiten a perséinlech Säiten ze hosten. Dëst Thema huet mech am Ufank vu menger Programméierungsrees interesséiert, an dee Moment war ech faszinéiert vu super Fachleit ze fannen déi iwwer sech selwer, hir Hobbien a Projeten schreiwen. D'Gewunnecht fir se selwer ze entdecken bleift bis haut: op bal all kommerziellen an net ganz kommerziellen Site kucken ech weider an de Fousszeilen op der Sich no Linken op d'Auteuren.

Ëmsetzung vun der Iddi

Déi éischt Versioun war just eng HTML Säit op menger perséinlecher Websäit, wou ech Linken mat Ënnerschrëften an eng ul Lëscht setzen. Nodeems ech 20 Säiten iwwer eng Zäit getippt hunn, hunn ech ugefaang ze denken datt dëst net ganz effektiv war an hunn decidéiert de Prozess ze automatiséieren. Op Stackoverflow hunn ech gemierkt datt vill Leit Siten an hire Profiler uginn, also hunn ech e Parser an php geschriwwen, deen einfach duerch d'Profiler gaangen ass, ugefaange mat der éischter (Adressen op SO bis haut sinn esou: `/users/1` ), Extraitéiert Links vum gewënschten Tag an huet et an SQLite bäigefüügt.

Dëst kann déi zweet Versioun genannt ginn: eng Sammlung vun Zéngdausende vun URLen an enger SQLite Tabell, déi d'statesch Lëscht an HTML ersat huet. Ech hunn eng einfach Sich op dëser Lëscht gemaach. Well et waren nëmmen URLen, dunn war d'Sich einfach op hinnen baséiert.

Op dëser Etapp hunn ech de Projet opginn an no enger laanger Zäit zréck komm. Op dëser Etapp war meng Aarbechtserfahrung scho méi wéi dräi Joer an ech hu gefillt datt ech eppes méi sérieux maache kéint. Zousätzlech gouf et e grousse Wonsch relativ nei Technologien ze beherrschen.

Modern Versioun

De Projet am Docker ofgebaut gouf, gouf d'Datebank op mongoDb transferéiert, a méi kierzlech gouf Rettich bäigefüügt, wat am Ufank just fir Cache war. Ee vun de PHP Mikroframeworks gëtt als Basis benotzt.

Problem

Nei Säite ginn duerch e Konsolbefehl bäigefüügt, deen synchron déi folgend mécht:

  • Downloads Inhalt vun URL
  • Setzt e Fändel un, deen uginn ob HTTPS verfügbar war
  • Erhaalt d'Essenz vun der Websäit
  • D'Quell HTML an d'Header ginn an der "Indexéiere" Geschicht gespäichert
  • Parses Inhalt, Extrait Titel a Beschreiwung
  • Späichert Daten op eng separat Sammlung

Dëst war genuch fir einfach Siten ze späicheren an se an enger Lëscht ze weisen:

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Awer d'Iddi fir alles automatesch ze indexéieren, ze kategoriséieren an ze klasséieren, alles um neiste Stand ze halen, huet net gutt an dësem Paradigma gepasst. Och einfach eng Webmethod bäizefügen fir Säiten ze addéieren erfuerderlech Code Duplikatioun a Blockéierung fir potenziell DDoS ze vermeiden.

Am Allgemengen kann natierlech alles synchron gemaach ginn, an an der Webmethod kënnt Dir einfach d'URL späicheren, sou datt de monstréisen Daemon all Aufgaben fir d'URLen aus der Lëscht ausféiert. Awer trotzdem, och hei proposéiert d'Wuert "Schlaang" sech. A wann eng Schlaang ëmgesat ass, da kënnen all Aufgaben opgedeelt an op d'mannst asynchron ausgefouert ginn.

Decisioun

Ëmsetzen Schlaangen a maacht en Event-driven System fir all Aufgaben ze veraarbecht. An ech wollt Redis Streams fir eng laang Zäit probéieren.

Benotzt Redis Streams an PHP

Well Well mäi Kader net ee vun den dräi Risen Symfony, Laravel, Yii ass, géif ech gär eng onofhängeg Bibliothéik fannen. Awer, wéi et sech erausstellt (op der éischter Untersuchung), ass et onméiglech fir eenzel sérieux Bibliothéiken ze fannen. Alles am Zesummenhang mat Schlaangen ass entweder e Projet vun 3 Commits viru fënnef Joer, oder ass un de Kader gebonnen.

Ech hu vill iwwer Symfony als Zouliwwerer vun eenzelne nëtzlech Komponente héieren, an ech benotzen schonn e puer vun hinnen. An och e puer Saachen aus Laravel kënnen och benotzt ginn, zum Beispill hiren ORM, ouni d'Präsenz vum Kader selwer.

symfony/messenger

Den éischte Kandidat war direkt ideal an ouni Zweiwel hunn ech en installéiert. Awer et huet sech méi schwéier gewisen Beispiller vu Gebrauch ausserhalb vu Symfony ze Google. Wéi aus enger Rëtsch Klassen mat universellen, sënnlosen Nimm ze sammelen, e Bus fir Messagen ze passéieren, a souguer op Redis?

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

D'Dokumentatioun op der offizieller Säit war zimlech detailléiert, awer d'Initialiséierung gouf nëmme fir Symfony beschriwwen andeems se hir Liiblings YML an aner Magiemethoden fir den Net-Symphonist benotzt. Ech hat keen Interessi un der Installatiounsprozess selwer, besonnesch an der Neijoerschvakanz. Mee ech hu missen dat fir eng onerwaart laang Zäit maachen.

Probéieren erauszefannen wéi een e System mat Symfony Quellen instantiéiert ass och net déi trivialst Aufgab fir eng enk Frist:

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Nodeem ech an dat alles verdéift hunn a probéiert hunn eppes mat mengen Hänn ze maachen, sinn ech zur Conclusioun komm datt ech eng Aart vu Krutzen maachen an hunn decidéiert eppes anescht ze probéieren.

beliichten / Schlaang

Et huet sech erausgestallt datt dës Bibliothéik enk mat der Laravel Infrastruktur an enger Rëtsch aner Ofhängegkeeten gebonnen ass, also hunn ech net vill Zäit drop verbruecht: Ech hunn et installéiert, gekuckt, d'Ofhängegkeete gesinn an se geläscht.

yiisoft/yii2-queue

Gutt, hei gouf direkt vum Numm ugeholl, erëm eng strikt Verbindung mat Yii2. Ech hu missen dës Bibliothéik benotzen an et war net schlecht, awer ech hunn net iwwer d'Tatsaach geduecht datt et komplett vu Yii2 hänkt.

De Rescht

Alles anescht, wat ech op GitHub fonnt hunn, waren onverlässeg, verännert a verloosse Projeten ouni Stären, Gabel an eng grouss Zuel vu Verpflichtungen.

Zréck op Symfony / Messenger, technesch Detailer

Ech hu missen dës Bibliothéik erausfannen an, nodeems ech e bësse méi Zäit verbruecht hunn, konnt ech. Et huet sech erausgestallt datt alles ganz präzis an einfach war. Fir de Bus ze instantiéieren, hunn ech eng kleng Fabréck gemaach, well ... Ech sollt e puer Pneuen a mat verschiddene Handler hunn.

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Just e puer Schrëtt:

  • Mir kreéieren Message Handler déi einfach callable sollen
  • Mir wéckelen se an HandlerDescriptor (Klass aus der Bibliothéik)
  • Mir wéckelen dës "Descriptoren" an enger HandlersLocator Instanz
  • Füügt HandlersLocator an d'MessageBus Instanz
  • Mir passéieren e Set vun 'SenderInterface' un SendersLocator, a mengem Fall Fäll vu 'RedisTransport' Klassen, déi op eng offensichtlech Manéier konfiguréiert sinn
  • SendersLocator op d'MessageBus Instanz dobäisetzen

MessageBus huet eng `->dispatch()` Method déi déi entspriechend Handler am HandlersLocator opkuckt an de Message un hinnen weiderginn, andeems Dir de entspriechende `SenderInterface` benotzt fir iwwer de Bus ze schécken (Redis Streams).

An der Containerkonfiguratioun (an dësem Fall php-di), kann dëse ganze Bündel esou konfiguréiert ginn:

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

Hei kënnt Dir gesinn datt mir am SendersLocator verschidde "Transporter" fir zwee verschidde Messagen zougewisen hunn, déi jidderee seng eege Verbindung mat den entspriechende Streamen huet.

Ech hunn e separaten Demo-Projet gemaach, deen eng Applikatioun vun dräi Daemonen demonstréiert, déi matenee kommunizéiere mat de folgende Bus: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Awer ech weisen Iech wéi e Konsument strukturéiert ka ginn:

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

Benotzt dës Infrastruktur an enger Applikatioun

Nodeems ech de Bus a mengem Backend ëmgesat hunn, hunn ech eenzel Etappen vum alen Synchron-Kommando getrennt an getrennt Handler gemaach, jidderee vun deenen hir eege Saach mécht.

D'Pipeline fir en neie Site an d'Datebank ze addéieren huet sou ausgesinn:

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

An direkt duerno gouf et vill méi einfach fir mech nei Funktionalitéit ze addéieren, zum Beispill Rss extrahéieren an parséieren. Well Dëse Prozess erfuerdert och den ursprénglechen Inhalt, dann abonnéiert de RSS Link Extractor Handler, wéi WebsiteIndexHistoryPersistor, op den "Content/HtmlContent" Message, veraarbecht et a passt de gewënschte Message laanscht seng Pipeline weider.

Iwwerdroung vum PHP Backend op de Redis Streams Bus a wielt eng Kader-onofhängeg Bibliothéik

Um Enn si mir mat e puer Daemonen opgehalen, jidderee vun deenen d'Verbindungen nëmmen un déi néideg Ressourcen erhalen. Zum Beispill en Dämon Crawler enthält all Handler, déi erfuerderen fir op den Internet ze goen fir Inhalt, an den Daemon bestoe bleiwen hält eng Verbindung mat der Datebank.

Elo, amplaz aus der Datebank ze wielen, ginn déi erfuerderlech IDen no der Aféierung vum Persister einfach iwwer de Bus un all interesséiert Handler iwwerdroen.

Source: will.com

Setzt e Commentaire