It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

Foarwurd

Myn webside, dy't ik as hobby útfiere, is ûntworpen om ynteressante thússiden en persoanlike siden te hostjen. Dit ûnderwerp begon my te ynteressearjen oan it begjin fan myn programmearring; op dat stuit wie ik fassinearre troch it finen fan geweldige professionals dy't skriuwe oer harsels, har hobby's en projekten. De gewoante om se sels te ûntdekken bliuwt oant hjoed de dei: op hast elke kommersjele en net heul kommersjele side sjoch ik fierder yn 'e foettekst op syk nei keppelings nei de auteurs.

Utfiering fan it idee

De earste ferzje wie gewoan in html-side op myn persoanlike webside, wêr't ik keppelings mei hantekeningen yn in ul-list sette. Nei it typen fan 20 siden oer in perioade fan tiid, begon ik te tinken dat dit net heul effektyf wie en besleat om te besykjen it proses te automatisearjen. Op stackoverflow fernaam ik dat in protte minsken siden yn har profilen oanjaan, dus ik skreau in parser yn php, dy't gewoan troch de profilen gie, begjinnend mei de earste (adressen op SO oant hjoed de dei binne sa: `/ brûkers/1` ), ekstrahearre keppelings fan 'e winske tag en tafoege it yn SQLite.

Dit kin de twadde ferzje neamd wurde: in samling fan tsientûzenen URL's yn in SQLite-tabel, dy't de statyske list yn HTML ferfong. Ik die in ienfâldige sykopdracht op dizze list. Omdat d'r wiene allinich URL's, dan wie it sykjen gewoan op basis dêrfan.

Op dit stadium haw ik it projekt ferlitten en kaam der nei in lange tiid werom. Op dit stadium wie myn wurkûnderfining al mear as trije jier en ik fielde dat ik wat serieuzer dwaan koe. Derneist wie d'r in grutte winsk om relatyf nije technologyen te behearskjen.

Moderne ferzje

It projekt ynset yn Docker, de databank waard oerbrocht nei mongoDb, en mear resint waard radish tafoege, dy't earst allinnich wie foar caching. Ien fan 'e PHP-mikroframeworks wurdt brûkt as basis.

probleem

Nije siden wurde tafoege troch in konsole-kommando dat syngroan it folgjende docht:

  • Download ynhâld troch URL
  • Stelt in flagge yn dy't oanjout oft HTTPS beskikber wie
  • Behâldt de essinsje fan 'e webside
  • De boarne HTML en kopteksten wurde bewarre yn 'e skiednis fan "yndeksearje".
  • Untleedt ynhâld, ekstrakt titel en beskriuwing
  • Besparret gegevens yn in aparte kolleksje

Dit wie genôch om siden gewoan op te slaan en te werjaan yn in list:

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

Mar it idee om alles automatysk te yndeksearjen, te kategorisearjen en te rangearjen, alles by de tiid te hâlden, paste net goed yn dit paradigma. Sels gewoan in webmetoade tafoegje om siden ta te foegjen fereaske koadeduplikaasje en blokkearjen om potinsjele DDoS te foarkommen.

Yn 't algemien kin fansels alles syngroan dien wurde, en yn' e webmetoade kinne jo de URL gewoan bewarje, sadat de meunsterlike daemon alle taken foar de URL's út 'e list útfiert. Mar dochs suggerearret ek hjir it wurd "wachtrige" himsels. En as in wachtrige wurdt útfierd, dan kinne alle taken wurde ferdield en útfierd op syn minst asynchronously.

beslút

Implementearje wachtrijen en meitsje in evenemint-oandreaune systeem foar it ferwurkjen fan alle taken. En ik woe Redis Streams al in lange tiid besykje.

Redis-streamen brûke yn PHP

Omdat Sûnt myn ramt is net ien fan de trije reuzen Symfony, Laravel, Yii, Ik soe graach fine in ûnôfhinklike bibleteek. Mar, sa die bliken (op earste ûndersyk), is it ûnmooglik te finen yndividuele serieuze biblioteken. Alles yn ferbân mei wachtrijen is of in projekt fan 3 commits fiif jier lyn, of is bûn oan it ramt.

Ik haw heard in protte oer Symfony as leveransier fan yndividuele brûkbere komponinten, en ik brûk al guon fan harren. En ek guon dingen fan Laravel kinne ek brûkt wurde, bygelyks har ORM, sûnder de oanwêzigens fan it ramt sels.

symfony / messenger

De earste kandidaat like fuortendaliks ideaal en sûnder twifel haw ik it ynstallearre. Mar it blykte dreger te wêzen om foarbylden fan gebrûk bûten Symfony te googlejen. Hoe te sammeljen út in bosk klassen mei universele, betsjuttingsleaze nammen, in bus foar it trochjaan fan berjochten, en sels op Redis?

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

De dokumintaasje op 'e offisjele side wie frij detaillearre, mar de inisjalisaasje waard allinich beskreaun foar Symfony mei har favorite YML en oare magyske metoaden foar de net-symfonist. Ik hie gjin belangstelling foar it ynstallaasjeproses sels, benammen yn 'e nijjiersfakânsje. Mar ik moast dit foar in ûnferwachte lange tiid dwaan.

Besykje út te finen hoe't jo in systeem kinne instantiearje mei Symfony-boarnen is ek net de meast triviale taak foar in strakke deadline:

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

Nei it dûken yn dit alles en besykje wat mei myn hannen te dwaan, kaam ik ta de konklúzje dat ik in soarte fan krukken die en besleat om wat oars te besykjen.

ferljochte / wachtrige

It die bliken dat dizze bibleteek strak bûn wie oan 'e Laravel-ynfrastruktuer en in protte oare ôfhinklikens, dus ik haw der net folle tiid oan besteegje: ik haw it ynstalleare, besjoen, sjoen de ôfhinklikens en wiske.

yiisoft/yii2-wachtrige

No, hjir waard fuortdaliks fan 'e namme oannommen, wer in strikte ferbining mei Yii2. Ik moast dizze bibleteek brûke en it wie net min, mar ik tocht net oer it feit dat it folslein hinget fan Yii2.

De rest

Al it oare dat ik fûn op GitHub wiene ûnbetroubere, ferâldere en ferlitten projekten sûnder stjerren, foarken en in grut oantal commits.

Werom nei symfony / messenger, technyske details

Ik moast dizze bibleteek útfine en, nei't ik wat mear tiid hie trochbrocht, koe ik dat. It die bliken dat alles frij bondich en ienfâldich wie. Om de bus te instantiearjen makke ik in lyts fabryk, want... Ik soe hawwe ferskate bannen en mei ferskillende handlers.

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

Krekt in pear stappen:

  • Wy meitsje berjochthannelers dy't gewoan opropber wêze moatte
  • Wy ferpakke se yn HandlerDescriptor (klasse fan 'e biblioteek)
  • Wy wrap dizze "Descriptors" yn in HandlersLocator eksimplaar
  • HandlersLocator tafoegje oan it MessageBus-eksimplaar
  • Wy jouwe in set fan 'SenderInterface' troch oan SendersLocator, yn myn gefal gefallen fan 'RedisTransport'-klassen, dy't op in dúdlike manier binne konfigureare
  • SendersLocator tafoegje oan it MessageBus-eksimplaar

MessageBus hat in `-> dispatch()`-metoade dy't de passende handlers opsyket yn 'e HandlersLocator en it berjocht oan har trochjout, mei de oerienkommende 'SenderInterface' om te ferstjoeren fia de bus (Redis streams).

Yn 'e kontenerkonfiguraasje (yn dit gefal php-di), kin dizze heule bondel sa konfigureare wurde:

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

Hjir kinne jo sjen dat yn SendersLocator wy hawwe tawiisd ferskillende "transporten" foar twa ferskillende berjochten, elk fan dat hat in eigen ferbining mei de oerienkommende streamen.

Ik makke in apart demo-projekt dat in tapassing fan trije daemons oantoand dy't mei-inoar kommunisearje mei de folgjende bus: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Mar ik sil jo sjen litte hoe't in konsumint kin wurde struktureare:

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

It brûken fan dizze ynfrastruktuer yn in applikaasje

Nei't ik de bus yn myn backend ymplementearre, skiede ik yndividuele stadia fan it âlde syngroane kommando en makke aparte handlers, elk fan wa't har eigen ding docht.

De pipeline foar it tafoegjen fan in nije side oan 'e database seach der sa út:

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

En fuort dêrnei waard it foar my folle makliker om nije funksjonaliteit ta te foegjen, bygelyks it ekstrahearje en parsearjen fan Rss. Omdat dit proses fereasket ek de orizjinele ynhâld, dan skriuwt de RSS-keppelingsextractor-hanter, lykas WebsiteIndexHistoryPersistor, yn op it berjocht "Ynhâld/HtmlContent", ferwurket it en bringt it winske berjocht troch syn pipeline fierder.

It oerbringen fan de PHP-backend nei de Redis-streambus en it kiezen fan in ramtûnôfhinklike bibleteek

Oan 'e ein kamen wy ​​mei ferskate daemons, elk fan dy't allinich ferbiningen ûnderhâldt mei de nedige boarnen. Bygelyks in demon crawlers befettet alle handlers dy't nedich binne om nei it ynternet te gean foar ynhâld, en de daemon oanhâlde hâldt in ferbining mei de databank.

No, yn stee fan te selektearjen út de databank, wurde de fereaske id's nei it ynfoegjen troch de persister gewoan fia de bus oerbrocht nei alle ynteressearre hannelers.

Boarne: www.habr.com

Add a comment