Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Förord

Min webbplats, som jag driver som en hobby, är utformad för att vara värd för intressanta hemsidor och personliga webbplatser. Det här ämnet började intressera mig i början av min programmeringsresa; i det ögonblicket fascinerades jag av att hitta fantastiska proffs som skriver om sig själva, sina hobbyer och projekt. Vanan att upptäcka dem själv finns kvar till denna dag: på nästan alla kommersiella och inte särskilt kommersiella sajter fortsätter jag att leta i sidfoten på jakt efter länkar till författarna.

Genomförande av idén

Den första versionen var bara en html-sida på min personliga hemsida, där jag lade in länkar med signaturer i en ul-lista. Efter att ha skrivit 20 sidor under en period började jag tänka att detta inte var särskilt effektivt och bestämde mig för att försöka automatisera processen. På stackoverflow märkte jag att många människor anger webbplatser i sina profiler, så jag skrev en parser i php, som helt enkelt gick igenom profilerna, och började med den första (adresser på SO till denna dag är så här: `/users/1` ), extraherade länkar från den önskade taggen och lade till den i SQLite.

Detta kan kallas den andra versionen: en samling av tiotusentals webbadresser i en SQLite-tabell, som ersatte den statiska listan i HTML. Jag gjorde en enkel sökning på den här listan. Därför att det fanns bara webbadresser, sedan baserades sökningen helt enkelt på dem.

I detta skede övergav jag projektet och återvände till det efter en lång tid. I det här skedet var min arbetslivserfarenhet redan mer än tre år och jag kände att jag kunde göra något mer seriöst. Dessutom fanns en stor vilja att behärska relativt ny teknik.

Modern version

Projekt utplacerad i Docker överfördes databasen till mongoDb, och på senare tid lades rädisa till, som först bara var för cachning. Ett av PHP-mikroramverken används som bas.

problem

Nya webbplatser läggs till av ett konsolkommando som synkront gör följande:

  • Laddar ner innehåll via URL
  • Ställer in en flagga som indikerar om HTTPS var tillgängligt
  • Bevarar essensen av webbplatsen
  • Källkods-HTML och rubriker sparas i "indexerings"-historiken
  • Analyserar innehåll, extraherar titel och beskrivning
  • Sparar data till en separat samling

Detta räckte för att helt enkelt lagra webbplatser och visa dem i en lista:

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Men tanken på att automatiskt indexera, kategorisera och rangordna allt, hålla allt uppdaterat, passade inte in i detta paradigm. Att bara lägga till en webbmetod för att lägga till sidor krävde kodduplicering och blockering för att undvika potentiell DDoS.

I allmänhet kan naturligtvis allt göras synkront, och i webbmetoden kan du helt enkelt spara URL:en så att den monstruösa demonen utför alla uppgifter för URL:erna från listan. Men även här antyder ordet "kö" sig själv. Och om en kö implementeras kan alla uppgifter delas upp och utföras åtminstone asynkront.

beslutet

Implementera köer och göra ett händelsestyrt system för att hantera alla uppgifter. Och jag har velat testa Redis Streams länge.

Använder Redis-strömmar i PHP

Därför att Eftersom mitt ramverk inte är en av de tre jättarna Symfony, Laravel, Yii, skulle jag vilja hitta ett oberoende bibliotek. Men, som det visade sig (vid första undersökningen), är det omöjligt att hitta enskilda seriösa bibliotek. Allt som rör köer är antingen ett projekt från 3 commits för fem år sedan, eller är knutet till ramverket.

Jag har hört mycket om Symfony som leverantör av enskilda användbara komponenter, och jag använder redan några av dem. Och även vissa saker från Laravel kan också användas, till exempel deras ORM, utan närvaron av själva ramverket.

symfoni/budbärare

Den första kandidaten verkade genast idealisk och utan tvekan installerade jag den. Men det visade sig vara svårare att googla på exempel på användning utanför Symfony. Hur samlar man ihop sig från ett gäng klasser med universella, meningslösa namn, en buss för att skicka meddelanden och till och med på Redis?

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Dokumentationen på den officiella webbplatsen var ganska detaljerad, men initialiseringen beskrevs endast för Symfony med deras favorit YML och andra magiska metoder för icke-symfonisten. Jag var inte intresserad av själva installationsprocessen, särskilt under nyårshelgerna. Men jag var tvungen att göra det här oväntat länge.

Att försöka ta reda på hur man instansierar ett system med hjälp av Symfony-källor är inte heller den mest triviala uppgiften för en snäv deadline:

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Efter att ha fördjupat mig i allt detta och försökt göra något med händerna kom jag fram till att jag gjorde någon form av kryckor och bestämde mig för att prova något annat.

upplyst/kö

Det visade sig att det här biblioteket var hårt bundet till Laravel-infrastrukturen och en massa andra beroenden, så jag spenderade inte mycket tid på det: jag installerade det, tittade på det, såg beroenden och tog bort det.

yiisoft/yii2-kö

Tja, här antogs det omedelbart från namnet, återigen, en strikt koppling till Yii2. Jag var tvungen att använda det här biblioteket och det var inte dåligt, men jag tänkte inte på det faktum att det helt beror på Yii2.

Resten

Allt annat som jag hittade på GitHub var opålitliga, föråldrade och övergivna projekt utan stjärnor, gafflar och ett stort antal commits.

Återgå till symfony/messenger, tekniska detaljer

Jag var tvungen att ta reda på det här biblioteket och efter att ha spenderat lite mer tid kunde jag det. Det visade sig att allt var ganska kortfattat och enkelt. För att instansiera bussen gjorde jag en liten fabrik, eftersom... Jag skulle ha flera däck och med olika förare.

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Bara några få steg:

  • Vi skapar meddelandehanterare som enkelt ska kunna anropas
  • Vi slår in dem i HandlerDescriptor (klass från biblioteket)
  • Vi slår in dessa "Descriptors" i en HandlersLocator-instans
  • Lägger till HandlersLocator till MessageBus-instansen
  • Vi skickar en uppsättning "SenderInterface" till SendersLocator, i mitt fall instanser av "RedisTransport" klasser, som är konfigurerade på ett uppenbart sätt
  • Lägger till SendersLocator till MessageBus-instansen

MessageBus har en `->dispatch()`-metod som letar upp lämpliga hanterare i HandlersLocator och skickar meddelandet till dem, med hjälp av motsvarande `SenderInterface` för att skicka via bussen (Redis-strömmar).

I containerkonfigurationen (i det här fallet php-di) kan hela denna bunt konfigureras så här:

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

Här kan du se att vi i SendersLocator har tilldelat olika "transporter" för två olika meddelanden, som var och en har sin egen koppling till motsvarande strömmar.

Jag gjorde ett separat demoprojekt som demonstrerade en tillämpning av tre demoner som kommunicerar med varandra med hjälp av följande buss: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Men jag ska visa dig hur en konsument kan struktureras:

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

Använda denna infrastruktur i en applikation

Efter att ha implementerat bussen i min backend, separerade jag enskilda steg från det gamla synkrona kommandot och gjorde separata hanterare, som var och en gör sin egen grej.

Pipelinen för att lägga till en ny webbplats till databasen såg ut så här:

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Och direkt efter det blev det mycket lättare för mig att lägga till ny funktionalitet, till exempel att extrahera och analysera Rss. Därför att denna process kräver också det ursprungliga innehållet, sedan prenumererar RSS-länkextraktionshanteraren, som WebsiteIndexHistoryPersistor, på meddelandet "Content/HtmlContent", bearbetar det och skickar det önskade meddelandet vidare längs sin pipeline.

Överföra PHP-backend till Redis-strömbussen och välja ett ramoberoende bibliotek

Till slut slutade vi med flera demoner, som var och en upprätthåller kopplingar endast till de nödvändiga resurserna. Till exempel en demon Crawlers innehåller alla hanterare som kräver att gå till Internet för innehåll och demonen uthärda har en anslutning till databasen.

Nu, istället för att välja från databasen, sänds de nödvändiga ID:n efter införande av persistern helt enkelt via bussen till alla intresserade hanterare.

Källa: will.com

Lägg en kommentar