PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Esipuhe

Harrastuksena ylläpitämäni verkkosivustoni on suunniteltu tarjoamaan mielenkiintoisia kotisivuja ja henkilökohtaisia ​​​​sivustoja. Tämä aihe alkoi kiinnostaa minua jo ohjelmointimatkani alussa, sillä hetkellä minua kiehtoi löytää upeita ammattilaisia, jotka kirjoittavat itsestään, harrastuksistaan ​​ja projekteistaan. Tapana löytää ne itselleni on säilynyt tähän päivään asti: melkein kaikilla kaupallisilla ja ei kovin kaupallisilla sivustoilla jatkan alatunnisteen etsimistä etsien linkkejä tekijöihin.

Idean toteutus

Ensimmäinen versio oli vain html-sivu henkilökohtaisella verkkosivustollani, jolle laitoin allekirjoituksia sisältäviä linkkejä ul-luetteloon. Kirjoitettuani 20 sivua ajan kuluessa aloin ajatella, että tämä ei ollut kovin tehokasta, ja päätin yrittää automatisoida prosessin. Stackoverflow:ssa huomasin, että monet ihmiset osoittavat sivustoja profiileissaan, joten kirjoitin php:ssä jäsentimen, joka yksinkertaisesti kävi profiilit läpi, alkaen ensimmäisestä (osoitteet SO:ssa tähän päivään asti ovat seuraavanlaisia: `/users/1` ), poimi linkit halutusta tunnisteesta ja lisäsi sen SQLiteen.

Tätä voidaan kutsua toiseksi versioksi: kokoelma kymmeniä tuhansia URL-osoitteita SQLite-taulukossa, joka korvasi staattisen luettelon HTML:ssä. Tein yksinkertaisen haun tästä luettelosta. Koska oli vain URL-osoitteita, jolloin haku perustui vain niihin.

Tässä vaiheessa hylkäsin projektin ja palasin siihen pitkän ajan kuluttua. Tässä vaiheessa työkokemukseni oli jo yli kolme vuotta ja tunsin, että voisin tehdä jotain vakavampaa. Lisäksi oli suuri halu hallita suhteellisen uusia teknologioita.

Moderni versio

Hanke otettu käyttöön Dockerissa, tietokanta siirrettiin mongoDb:hen ja äskettäin lisättiin retiisi, joka oli aluksi vain välimuistia varten. Perustana käytetään yhtä PHP-mikrokehystä.

ongelma

Uudet sivustot lisätään konsolikomennolla, joka tekee synkronisesti seuraavat:

  • Lataa sisältöä URL-osoitteen mukaan
  • Asettaa lipun, joka osoittaa, oliko HTTPS saatavilla
  • Säilyttää sivuston olemuksen
  • Lähde-HTML ja otsikot tallennetaan "indeksointi"-historiaan
  • Jäsentää sisältöä, otteita otsikon ja kuvauksen
  • Tallentaa tiedot erilliseen kokoelmaan

Tämä riitti yksinkertaisesti sivustojen tallentamiseen ja niiden näyttämiseen luettelossa:

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Mutta ajatus kaiken automaattisesta indeksoinnista, luokittelemisesta ja luokittelemisesta sekä kaiken pitämisestä ajan tasalla ei sopinut hyvin tähän paradigmaan. Jopa pelkkä web-menetelmän lisääminen sivujen lisäämiseen vaati koodin monistamista ja estämistä mahdollisen DDoS:n välttämiseksi.

Yleensä tietysti kaikki voidaan tehdä synkronisesti, ja web-menetelmässä voit yksinkertaisesti tallentaa URL-osoitteen, jotta hirviömäinen demoni suorittaa kaikki tehtävät luettelon URL-osoitteille. Mutta silti, jopa tässä sana "jono" ehdottaa itseään. Ja jos jono on toteutettu, kaikki tehtävät voidaan jakaa ja suorittaa ainakin asynkronisesti.

päätös

Toteuta jonoja ja tee tapahtumapohjainen järjestelmä kaikkien tehtävien käsittelyä varten. Ja olen halunnut kokeilla Redis Streamia jo pitkään.

Redis-streamien käyttäminen PHP:ssä

Koska Koska kehykseni ei ole yksi kolmesta jättiläisestä Symfony, Laravel, Yii, haluaisin löytää itsenäisen kirjaston. Mutta kuten kävi ilmi (ensimmäisellä tarkastelulla), yksittäisiä vakavia kirjastoja on mahdotonta löytää. Kaikki jonoihin liittyvä on joko projektia 3 commitista viisi vuotta sitten tai on sidottu puitteisiin.

Olen kuullut paljon Symfonystä yksittäisten hyödyllisten komponenttien toimittajana, ja käytän jo joitakin niistä. Ja myös joitain Laravelin asioita voidaan käyttää, esimerkiksi niiden ORM, ilman itse kehyksen läsnäoloa.

symfony/mesenger

Ensimmäinen ehdokas vaikutti heti sopivalta ja epäilemättä asensin sen. Mutta Symfonyn ulkopuolisten käyttöesimerkkien googlettaminen osoittautui vaikeammaksi. Kuinka koota joukosta luokkia yleismaailmallisilla, merkityksettömillä nimillä, väylä viestien välittämiseen ja jopa Redis?

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Virallisen sivuston dokumentaatio oli melko yksityiskohtainen, mutta alustus kuvattiin vain Symfonylle käyttämällä heidän suosikki YML:ään ja muita taikamenetelmiä ei-sinfoonisille. Itse asennusprosessi ei kiinnostanut minua varsinkaan uudenvuoden lomien aikana. Mutta minun piti tehdä tätä odottamattoman pitkään.

Symfony-lähteiden avulla tapahtuvan järjestelmän luominen ei myöskään ole triviaalisin tehtävä tiukassa määräajassa:

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Syventyäni tähän kaikkeen ja yrittäessäni tehdä jotain käsilläni, tulin siihen tulokseen, että tein jonkinlaisia ​​kainalosauvoja ja päätin kokeilla jotain muuta.

valaistu/jonossa

Kävi ilmi, että tämä kirjasto oli tiukasti sidottu Laravelin infrastruktuuriin ja joukkoon muita riippuvuuksia, joten en käyttänyt siihen paljon aikaa: asensin sen, katsoin sitä, näin riippuvuudet ja poistin sen.

yiisoft/yii2-jono

No, tässä nimestä oletettiin heti, taas tiukka yhteys Yii2:een. Minun piti käyttää tätä kirjastoa, eikä se ollut huono, mutta en ajatellut sitä tosiasiaa, että se riippuu täysin Yii2:sta.

Loput

Kaikki muu, mitä löysin GitHubista, oli epäluotettavia, vanhentuneita ja hylättyjä projekteja ilman tähtiä, haarukoita ja suurta määrää sitoumuksia.

Palaa symfonyyn/messengeriin, tekniset tiedot

Minun täytyi selvittää tämä kirjasto, ja kun olin viettänyt enemmän aikaa, pystyin siihen. Kävi ilmi, että kaikki oli melko tiivistä ja yksinkertaista. Bussin ilmentämiseksi tein pienen tehtaan, koska... Minulla piti olla useita renkaita ja eri ohjaimia.

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Vain muutama vaihe:

  • Luomme viestinkäsittelijöitä, joiden pitäisi olla yksinkertaisesti kutsuttavia
  • Käärimme ne HandlerDescriptoriin (luokka kirjastosta)
  • Käärimme nämä "kuvaajat" HandlersLocator-instanssiin
  • HandlersLocatorin lisääminen MessageBus-instanssiin
  • Välitämme joukon "SenderInterface" SendersLocatorille, minun tapauksessani "RedisTransport"-luokkia, jotka on määritetty ilmeisellä tavalla
  • SendersLocatorin lisääminen MessageBus-instanssiin

MessageBusissa on `->dispatch()-metodi, joka etsii asianmukaiset käsittelijät HandlersLocatorista ja välittää viestin niille käyttämällä vastaavaa `SenderInterfacea' lähettämiseen väylän kautta (Redis-virrat).

Säilön kokoonpanossa (tässä tapauksessa php-di) tämä koko paketti voidaan määrittää seuraavasti:

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

Tästä näet, että SendersLocatorissa olemme määrittäneet eri "kuljetukset" kahdelle eri viestille, joista jokaisella on oma yhteys vastaaviin tietovirtoihin.

Tein erillisen demoprojektin, joka esitteli kolmen demonin sovellusta, jotka kommunikoivat keskenään seuraavan väylän avulla: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Mutta näytän sinulle, kuinka kuluttaja voidaan jäsentää:

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

Tämän infrastruktuurin käyttäminen sovelluksessa

Toteutettuani väylän taustajärjestelmääni erotin yksittäiset vaiheet vanhasta synkronisesta komennosta ja tein erilliset käsittelijät, joista jokainen tekee oman asiansa.

Uuden sivuston lisääminen tietokantaan näytti tältä:

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Ja heti sen jälkeen minun oli paljon helpompaa lisätä uusia toimintoja, esimerkiksi Rss:n purkaminen ja jäsentäminen. Koska tämä prosessi vaatii myös alkuperäisen sisällön, sitten RSS-linkinpoistokäsittelijä, kuten WebsiteIndexHistoryPersistor, tilaa "Content/HtmlContent" -sanoman, käsittelee sen ja välittää halutun viestin edelleen putkistoaan pitkin.

PHP-taustajärjestelmän siirtäminen Redis-virtaväylään ja kehyksestä riippumattoman kirjaston valitseminen

Lopulta päädyimme useisiin demoniin, joista jokainen ylläpitää yhteyksiä vain tarvittaviin resursseihin. Esimerkiksi demoni indeksoijat sisältää kaikki käsittelijät, jotka vaativat pääsyn Internetiin sisällön saamiseksi ja demonin pysyä pitää yhteyttä tietokantaan.

Nyt tietokannasta valinnan sijaan vaaditut tunnukset siirretään edelleen väylän kautta kaikille kiinnostuneille käsittelijöille.

Lähde: will.com

Lisää kommentti