Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

voorwoord

My webwerf, wat ek as 'n stokperdjie bedryf, is ontwerp om interessante tuisbladsye en persoonlike webwerwe te huisves. Hierdie onderwerp het my aan die begin van my programmeringsreis begin interesseer; op daardie oomblik was ek gefassineer deur goeie professionele persone te vind wat oor hulself, hul stokperdjies en projekte skryf. Die gewoonte om dit vir myself te ontdek, bly tot vandag toe: op byna elke kommersiële en nie baie kommersiële webwerf, gaan ek voort om in die voetskrif te kyk op soek na skakels na die skrywers.

Implementering van die idee

Die eerste weergawe was net 'n html-bladsy op my persoonlike webwerf, waar ek skakels met handtekeninge in 'n ul-lys geplaas het. Nadat ek 20 bladsye oor 'n tydperk getik het, het ek begin dink dat dit nie baie effektief was nie en het besluit om die proses te outomatiseer. Op stackoverflow het ek opgemerk dat baie mense werwe in hul profiele aandui, so ek het 'n ontleder in php geskryf, wat eenvoudig deur die profiele gegaan het, begin met die eerste (adresse op SO tot vandag toe is soos volg: `/users/1` ), het skakels uit die verlangde merker onttrek en dit in SQLite bygevoeg.

Dit kan die tweede weergawe genoem word: 'n versameling van tienduisende URL's in 'n SQLite-tabel, wat die statiese lys in HTML vervang het. Ek het 'n eenvoudige soektog op hierdie lys gedoen. Omdat daar was net URL's, dan was die soektog bloot daarop gebaseer.

Op hierdie stadium het ek die projek laat vaar en na 'n lang tyd daarna teruggekeer. Op hierdie stadium was my werkservaring reeds meer as drie jaar en ek het gevoel dat ek iets ernstiger kan doen. Daarbenewens was daar 'n groot begeerte om relatief nuwe tegnologieë te bemeester.

Moderne weergawe

Project in Docker ontplooi is, is die databasis na mongoDb oorgedra, en meer onlangs is radyse bygevoeg, wat eers net vir kas was. Een van die PHP-mikroraamwerke word as basis gebruik.

probleem

Nuwe werwe word bygevoeg deur 'n konsole-opdrag wat sinchronies die volgende doen:

  • Laai inhoud af volgens URL
  • Stel 'n vlag wat aandui of HTTPS beskikbaar was
  • Behou die essensie van die webwerf
  • Die bron-HTML en opskrifte word in die "indeksering"-geskiedenis gestoor
  • Ontleed inhoud, haal titel en beskrywing uit
  • Stoor data in 'n aparte versameling

Dit was genoeg om bloot werwe te stoor en dit in 'n lys te vertoon:

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Maar die idee om alles outomaties te indekseer, kategoriseer en rangskik, om alles op datum te hou, het nie goed in hierdie paradigma gepas nie. Selfs om net 'n webmetode by te voeg om bladsye by te voeg, vereis kodeduplisering en blokkering om potensiële DDoS te vermy.

Oor die algemeen kan alles natuurlik sinchroon gedoen word, en in die webmetode kan jy eenvoudig die URL stoor sodat die monsteragtige daemon al die take vir die URL's uit die lys uitvoer. Maar steeds, selfs hier suggereer die woord "tou" homself. En as 'n tou geïmplementeer word, kan alle take verdeel en ten minste asynchronies uitgevoer word.

besluit

Implementeer toue en skep 'n gebeurtenisgedrewe stelsel vir die verwerking van alle take. En ek wou al lankal Redis Streams probeer.

Gebruik Redis-strome in PHP

Omdat Aangesien my raamwerk nie een van die drie reuse Symfony, Laravel, Yii is nie, wil ek graag 'n onafhanklike biblioteek vind. Maar, soos dit geblyk het (met die eerste ondersoek), is dit onmoontlik om individuele ernstige biblioteke te vind. Alles wat met toue verband hou, is óf 'n projek van 3 commits vyf jaar gelede, óf is gekoppel aan die raamwerk.

Ek het al baie gehoor van Symfony as 'n verskaffer van individuele nuttige komponente, en ek gebruik reeds sommige van hulle. En ook sommige dinge van Laravel kan ook gebruik word, byvoorbeeld hul ORM, sonder die teenwoordigheid van die raamwerk self.

simfonie/boodskapper

Die eerste kandidaat het dadelik ideaal gelyk en sonder enige twyfel het ek dit geïnstalleer. Maar dit blyk moeiliker te wees om voorbeelde van gebruik buite Symfony te google. Hoe om saam te stel uit 'n klomp klasse met universele, betekenislose name, 'n bus om boodskappe deur te gee, en selfs op Redis?

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Die dokumentasie op die amptelike webwerf was redelik gedetailleerd, maar die inisialisering is slegs vir Symfony beskryf deur hul gunsteling YML en ander towermetodes vir die nie-simfonis te gebruik. Ek het geen belangstelling in die installasieproses self gehad nie, veral gedurende die Nuwejaarsvakansie. Maar ek moes dit vir 'n onverwagse lang tyd doen.

Om te probeer uitvind hoe om 'n stelsel te instansieer deur Symfony-bronne te gebruik, is ook nie die mees onbenullige taak vir 'n kort sperdatum nie:

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Nadat ek in dit alles gedelf het en iets met my hande probeer doen het, het ek tot die gevolgtrekking gekom dat ek een of ander krukke gedoen het en besluit om iets anders te probeer.

verlig/tou

Dit het geblyk dat hierdie biblioteek stewig gekoppel was aan die Laravel-infrastruktuur en 'n klomp ander afhanklikhede, so ek het nie veel tyd daaraan spandeer nie: ek het dit geïnstalleer, daarna gekyk, die afhanklikhede gesien en dit uitgevee.

yiisoft/yii2-tou

Wel, hier is dit onmiddellik uit die naam aanvaar, weer 'n streng verband met Yii2. Ek moes hierdie biblioteek gebruik en dit was nie sleg nie, maar ek het nie daaraan gedink dat dit heeltemal van Yii2 afhang nie.

Die res

Alles anders wat ek op GitHub gevind het, was onbetroubare, verouderde en verlate projekte sonder sterre, vurke en 'n groot aantal commits.

Keer terug na simfonie/boodskapper, tegniese besonderhede

Ek moes hierdie biblioteek uitvind en, nadat ek nog tyd spandeer het, kon ek. Dit het geblyk dat alles redelik bondig en eenvoudig was. Om die bus te instansieer, het ek 'n klein fabriek gemaak, want... Ek was veronderstel om verskeie bande te hê en met verskillende hanteerders.

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Net 'n paar stappe:

  • Ons skep boodskaphanteerders wat eenvoudig oproepbaar moet wees
  • Ons draai hulle toe in HandlerDescriptor (klas van die biblioteek)
  • Ons draai hierdie "Beskrywings" in 'n HandlersLocator-instansie
  • Voeg HandlersLocator by die MessageBus-instansie
  • Ons gee 'n stel 'SenderInterface' aan SendersLocator, in my geval gevalle van 'RedisTransport'-klasse, wat op 'n ooglopende manier gekonfigureer is
  • Voeg SendersLocator by die MessageBus-instansie

MessageBus het 'n `->dispatch()` metode wat die toepaslike hanteerders in die HandlersLocator opsoek en die boodskap aan hulle deurgee, met behulp van die ooreenstemmende `SenderInterface` om via die bus (Redis-strome) te stuur.

In die houerkonfigurasie (in hierdie geval php-di), kan hierdie hele bundel soos volg gekonfigureer word:

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

Hier kan jy sien dat ons in SendersLocator verskillende "vervoere" vir twee verskillende boodskappe toegewys het, wat elkeen sy eie verbinding met die ooreenstemmende strome het.

Ek het 'n aparte demo-projek gemaak wat 'n toepassing van drie daemone demonstreer wat met mekaar kommunikeer deur die volgende bus te gebruik: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Maar ek sal jou wys hoe 'n verbruiker gestruktureer kan word:

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

Die gebruik van hierdie infrastruktuur in 'n toepassing

Nadat ek die bus in my agterkant geïmplementeer het, het ek individuele stadiums van die ou sinchrone opdrag geskei en aparte hanteerders gemaak, wat elkeen hul eie ding doen.

Die pyplyn vir die byvoeging van 'n nuwe webwerf by die databasis het soos volg gelyk:

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

En onmiddellik daarna het dit vir my baie makliker geword om nuwe funksionaliteit by te voeg, byvoorbeeld om Rss te onttrek en te ontleed. Omdat hierdie proses vereis ook die oorspronklike inhoud, dan teken die RSS-skakelonttrekker-hanteerder, soos WebsiteIndexHistoryPersistor, in op die "Content/HtmlContent"-boodskap, verwerk dit en stuur die verlangde boodskap verder langs sy pyplyn.

Die oordrag van die PHP-agterkant na die Redis-stroombus en die keuse van 'n raamwerk-onafhanklike biblioteek

Op die ou end het ons met verskeie daemone geëindig, wat elkeen slegs verbindings met die nodige hulpbronne behou. Byvoorbeeld 'n demoon crawlers bevat al die hanteerders wat vereis om na die internet te gaan vir inhoud, en die daemon volhard hou 'n verbinding met die databasis.

Nou, in plaas daarvan om uit die databasis te kies, word die vereiste ID's na invoeging deur die persister eenvoudig via die bus aan alle belangstellende hanteerders oorgedra.

Bron: will.com

Voeg 'n opmerking