PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

priekŔvārds

Mana vietne, kuru vadÄ«ju kā hobiju, ir paredzēta interesantu mājas lapu un personÄ«go vietņu mitināŔanai. Å Ä« tēma mani sāka interesēt jau paŔā programmÄ“Å”anas ceļojuma sākumā, tajā brÄ«dÄ« mani aizrāva izcilu profesionāļu atraÅ”ana, kas raksta par sevi, saviem hobijiem un projektiem. Ieradums tos atklāt sev saglabājies lÄ«dz mÅ«sdienām: gandrÄ«z katrā komerciālajā un ne pārāk komerciālajā vietnē es turpinu skatÄ«ties kājenē, meklējot saites uz autoriem.

Idejas īstenoŔana

Pirmā versija bija tikai html lapa manā personÄ«gajā vietnē, kur es ievietoju saites ar parakstiem ul sarakstā. IerakstÄ«jis 20 lappuses laika periodā, es sāku domāt, ka tas nav pārāk efektÄ«vi, un nolēmu mēģināt automatizēt procesu. Izmantojot stackoverflow, es pamanÄ«ju, ka daudzi cilvēki savos profilos norāda vietnes, tāpēc es uzrakstÄ«ju parsētāju php, kas vienkārÅ”i izgāja cauri profiliem, sākot ar pirmo (adreses SO lÄ«dz Å”ai dienai ir Ŕādas: `/users/1` ), izvilka saites no vēlamā taga un pievienoja to SQLite.

To var saukt par otro versiju: ā€‹ā€‹desmitiem tÅ«kstoÅ”u URL kolekcija SQLite tabulā, kas aizstāja statisko sarakstu html. Es veicu vienkārÅ”u meklÄ“Å”anu Å”ajā sarakstā. Jo bija tikai URL, tad meklÄ“Å”ana tika vienkārÅ”i balstÄ«ta uz tiem.

Å ajā posmā es pametu projektu un pēc ilgāka laika atgriezos pie tā. Å ajā posmā mans darba stāžs jau bija vairāk nekā trÄ«s gadi un es jutu, ka varu darÄ«t ko nopietnāku. Turklāt bija liela vēlme apgÅ«t salÄ«dzinoÅ”i jaunas tehnoloÄ£ijas.

Modernā versija

Projekts izvietojot Docker, datubāze tika pārsÅ«tÄ«ta uz mongoDb, un pavisam nesen tika pievienots redÄ«ss, kas sākotnēji bija paredzēts tikai keÅ”atmiņai. Par pamatu tiek izmantots viens no PHP mikroietvariem.

problēma

Jaunas vietnes pievieno konsoles komanda, kas sinhroni veic Ŕādas darbības:

  • Lejupielādē saturu pēc URL
  • Iestata karodziņu, kas norāda, vai HTTPS bija pieejams
  • Saglabā vietnes bÅ«tÄ«bu
  • Avota HTML un galvenes tiek saglabātas ā€œindeksÄ“Å”anasā€ vēsturē
  • Parsē saturu, izraksta nosaukumu un aprakstu
  • Saglabā datus atseviŔķā kolekcijā

Ar to pietika, lai vienkārŔi saglabātu vietnes un parādītu tās sarakstā:

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Taču ideja par automātisku visu indeksÄ“Å”anu, kategorizÄ“Å”anu un ranžēŔanu, visu atjaunināŔanu Å”ajā paradigmā neiederējās. Pat vienkārÅ”i pievienojot tÄ«mekļa metodi lapu pievienoÅ”anai, bija nepiecieÅ”ama koda dublÄ“Å”ana un bloÄ·Ä“Å”ana, lai izvairÄ«tos no iespējamās DDoS.

Kopumā, protams, visu var izdarÄ«t sinhroni, un tÄ«mekļa metodē jÅ«s varat vienkārÅ”i saglabāt URL, lai zvērÄ«gais dēmons veiktu visus uzdevumus URL no saraksta. Tomēr pat Å”eit vārds ā€œrindaā€ liek domāt par sevi. Un, ja ir ieviesta rinda, tad visus uzdevumus var sadalÄ«t un veikt vismaz asinhroni.

Å Ä·Ä«dums

Ieviesiet rindas un izveidojiet uz notikumiem balstītu sistēmu visu uzdevumu apstrādei. Un es jau ilgu laiku esmu vēlējies izmēģināt Redis Streams.

Redis straumju izmantoŔana PHP

Jo Tā kā mans ietvars nav viens no trim milžiem Symfony, Laravel, Yii, es vēlētos atrast neatkarÄ«gu bibliotēku. Bet, kā izrādÄ«jās (pirmajā pārbaudē), nav iespējams atrast atseviŔķas nopietnas bibliotēkas. Viss, kas saistÄ«ts ar rindām, ir vai nu projekts no 3 saistÄ«bām pirms pieciem gadiem, vai arÄ« ir saistÄ«ts ar ietvaru.

Esmu daudz dzirdējis par Symfony kā atseviŔķu noderÄ«gu komponentu piegādātāju, un dažus no tiem jau izmantoju. Un arÄ« dažas lietas no Laravel var izmantot, piemēram, to ORM, bez paÅ”a ietvara klātbÅ«tnes.

simfonija/ziņnesis

Pirmais kandidāts uzreiz Ŕķita ideāls un bez Å”aubām to uzstādÄ«ju. Taču google izmantoÅ”anas piemērus ārpus Symfony izrādÄ«jās grÅ«tāk. Kā salikt autobusu ziņojumu pārsÅ«tÄ«Å”anai no daudzām klasēm ar universāliem, bezjēdzÄ«giem nosaukumiem un pat uz Redis?

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Dokumentācija oficiālajā vietnē bija diezgan detalizēta, taču inicializācija tika aprakstÄ«ta tikai Symfony, izmantojot viņu iecienÄ«tāko YML un citas burvju metodes, kas nav paredzētas simfonistam. Man nebija nekādas intereses par paÅ”u instalÄ“Å”anas procesu, it Ä«paÅ”i Jaungada brÄ«vdienās. Bet man tas bija jādara negaidÄ«ti ilgu laiku.

Mēģinājums izdomāt, kā izveidot sistēmu, izmantojot Symfony avotus, arī nav pats triviālais uzdevums saspringtā termiņā:

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Iedziļinoties Å”ajā visā un mēģinot kaut ko izdarÄ«t ar savām rokām, es nonācu pie secinājuma, ka es nodarbojos ar kaut kādiem kruÄ·iem un nolēmu izmēģināt kaut ko citu.

izgaismots/rindā

IzrādÄ«jās, ka Ŕī bibliotēka bija cieÅ”i saistÄ«ta ar Laravel infrastruktÅ«ru un daudzām citām atkarÄ«bām, tāpēc es tai netērēju daudz laika: es to instalēju, apskatÄ«ju, ieraudzÄ«ju atkarÄ«bas un izdzēsu.

yiisoft/yii2-rinda

Nu, Å”eit tas uzreiz tika pieņemts no nosaukuma, atkal, stingra saikne ar Yii2. Man bija jāizmanto Ŕī bibliotēka, un tā nebija slikta, taču es nedomāju par to, ka tā ir pilnÄ«bā atkarÄ«ga no Yii2.

Pārējie

Viss pārējais, ko atradu vietnē GitHub, bija neuzticami, novecojuÅ”i un pamesti projekti bez zvaigznēm, dakŔām un daudzām saistÄ«bām.

Atgriezties uz symfony/messenger, tehniskā informācija

Man bija jāizdomā Ŕī bibliotēka, un, pavadot vairāk laika, es to varēju. IzrādÄ«jās, ka viss bija diezgan kodolÄ«gi un vienkārÅ”i. Lai iemūžinātu autobusu, es uztaisÄ«ju nelielu rÅ«pnÄ«cu, jo... Man vajadzēja bÅ«t vairākām riepām un ar dažādiem hendleriem.

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Tikai dažas darbības:

  • Mēs izveidojam ziņojumu apstrādātājus, kuriem vajadzētu bÅ«t vienkārÅ”i izsaucamiem
  • Mēs tos iesaiņojam programmā HandlerDescriptor (klase no bibliotēkas)
  • Mēs iesaiņojam Å”os ā€œdeskriptorusā€ HandlersLocator instancē
  • HandlersLocator pievienoÅ”ana MessageBus instancei
  • Mēs nododam `SenderInterface` kopu pakalpojumam SendersLocator, manā gadÄ«jumā RedisTransport klaÅ”u gadÄ«jumiem, kas ir konfigurēti acÄ«mredzamā veidā.
  • SendersLocator pievienoÅ”ana MessageBus instancei

MessageBus ir metode "->dispatch()", kas rÄ«kā HandlersLocator meklē atbilstoÅ”os apdarinātājus un nosÅ«ta tiem ziņojumu, izmantojot atbilstoÅ”o "SenderInterface", lai nosÅ«tÄ«tu, izmantojot kopni (Redis straumes).

Konteinera konfigurācijā (Å”ajā gadÄ«jumā php-di) visu Å”o komplektu var konfigurēt Ŕādi:

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

Å eit var redzēt, ka programmā SendersLocator esam pieŔķīruÅ”i dažādus ā€œtransportusā€ diviem dažādiem ziņojumiem, kuriem katram ir savs savienojums ar attiecÄ«gajām straumēm.

Es izveidoju atseviŔķu demonstrācijas projektu, kas demonstrē trÄ«s dēmonu lietojumprogrammu, kas sazinās savā starpā, izmantojot Ŕādu kopni: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Bet es jums parādÄ«Å”u, kā patērētāju var strukturēt:

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

Šīs infrastruktūras izmantoŔana lietojumprogrammā

Ieviesot kopni savā aizmugurprogrammā, es atdalīju atseviŔķus posmus no vecās sinhronās komandas un izveidoju atseviŔķus apstrādātājus, no kuriem katrs dara savu.

Cauruļvads jaunas vietnes pievienoŔanai datu bāzei izskatījās Ŕādi:

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Un uzreiz pēc tam man kļuva daudz vieglāk pievienot jaunu funkcionalitāti, piemēram, Rss izvilkÅ”anu un parsÄ“Å”anu. Jo Å”im procesam ir nepiecieÅ”ams arÄ« oriÄ£inālais saturs, tad RSS saiÅ”u izvilkÅ”anas apdarinātājs, piemēram, WebsiteIndexHistoryPersistor, abonē ziņojumu ā€œContent/HtmlContentā€, apstrādā to un tālāk nosÅ«ta vajadzÄ«go ziņojumu pa savu konveijeru.

PHP aizmugursistēmas pārsÅ«tÄ«Å”ana uz Redis straumju kopni un no ietvara neatkarÄ«gas bibliotēkas izvēle

Galu galā mēs nonācām pie vairākiem dēmoniem, no kuriem katrs uztur savienojumus tikai ar nepiecieÅ”amajiem resursiem. Piemēram, dēmons rāpulÄ«Å”i satur visus apdarinātājus, kuriem nepiecieÅ”ams pāriet uz internetu, lai iegÅ«tu saturu, un dēmonu noturēties satur savienojumu ar datu bāzi.

Tagad, tā vietā, lai veiktu atlasi no datu bāzes, nepiecieÅ”amie ID pēc to ievietoÅ”anas tiek vienkārÅ”i pārsÅ«tÄ«ti pa kopni visiem ieinteresētajiem apstrādātājiem.

Avots: www.habr.com

Pievieno komentāru