Előszó
Honlapomat, amelyet hobbiból üzemeltetek, úgy tervezték, hogy érdekes kezdőlapokat és személyes oldalakat helyezzen el. Ez a téma már a programozási utam kezdetén érdekelt, abban a pillanatban lenyűgözött, hogy nagyszerű szakembereket találtam, akik magukról, hobbijairól, projektjeikről írnak. Az a szokás, hogy magam fedezzem fel őket, a mai napig megmaradt: szinte minden kereskedelmi és nem túl kommersz oldalon továbbra is a láblécben nézek, keresve a szerzőkre mutató linkeket.
Az ötlet megvalósítása
Az első verzió csak egy html oldal volt a személyes honlapomon, ahol aláírásokkal ellátott hivatkozásokat tettem egy ul listába. Miután 20 oldalt begépeltem egy ideig, kezdtem azt gondolni, hogy ez nem túl hatékony, és úgy döntöttem, hogy megpróbálom automatizálni a folyamatot. A stackoverflow-nál azt vettem észre, hogy sokan oldalakat jeleznek a profiljukban, ezért írtam egy értelmezőt php-ben, ami egyszerűen végigment a profilokon, kezdve az elsővel (a SO-n a mai napig a következő címek vannak: `/users/1` ), kibontotta a hivatkozásokat a kívánt címkéből, és hozzáadta az SQLite-hoz.
Ezt nevezhetjük a második verziónak: több tízezer URL-ből álló gyűjtemény egy SQLite táblában, amely felváltotta a html statikus listát. Végeztem egy egyszerű keresést ezen a listán. Mert csak URL-ek voltak, akkor a keresés egyszerűen ezek alapján történt.
Ebben a szakaszban felhagytam a projekttel, és hosszú idő után visszatértem hozzá. Ebben a szakaszban a munkatapasztalatom már több mint három év volt, és úgy éreztem, hogy tudok valami komolyabbat is csinálni. Emellett nagy volt a vágy a viszonylag új technológiák elsajátítására.
Modern változat
probléma
Az új webhelyeket egy konzolparancs adja hozzá, amely szinkron módon a következőket teszi:
- Töltse le a tartalmat URL alapján
- Jelzőt állít be, amely jelzi, hogy elérhető volt-e a HTTPS
- Megőrzi a weboldal lényegét
- A forrás HTML és a fejlécek mentésre kerülnek az „indexelési” előzményekbe
- A tartalmat elemzi, kivonja a címet és a leírást
- Az adatokat külön gyűjteménybe menti
Ez elég volt ahhoz, hogy egyszerűen tárolja a webhelyeket és megjelenítse azokat egy listában:
De az az elképzelés, hogy mindent automatikusan indexelnek, kategorizálnak és rangsorolnak, mindent naprakészen tartanak, nem illett jól ebbe a paradigmába. Még az oldalak hozzáadásához szükséges webes metódus egyszerű hozzáadásához is kódduplázásra és blokkolásra volt szükség az esetleges DDoS elkerülése érdekében.
Általában természetesen mindent meg lehet csinálni szinkronban, és a webes metódusban egyszerűen elmentheti az URL-t, így a szörnyű démon elvégzi az összes feladatot a listából származó URL-ekre. De még itt is a „sor” szó sugallja magát. Ha pedig sor kerül megvalósításra, akkor minden feladat felosztható és legalább aszinkron módon végrehajtható.
döntés
Várólisták megvalósítása és eseményvezérelt rendszer létrehozása az összes feladat feldolgozásához. A Redis Streams-et pedig már régóta ki akartam próbálni.
Redis streamek használata PHP-ben
Mert Mivel a keretrendszerem nem a három óriás Symfony, Laravel, Yii egyike, szeretnék egy független könyvtárat találni. De, mint az első vizsgálatra kiderült, lehetetlen egyedi, komoly könyvtárakat találni. Minden, ami a sorokkal kapcsolatos, vagy egy öt évvel ezelőtti 3 commit projekt, vagy a kerethez kötődik.
Sokat hallottam a Symfony-ról, mint egyedi hasznos komponensek szállítójáról, és már használok is néhányat. És néhány dolog a Laraveltől is használható, például az ORM-jük, a keret jelenléte nélkül.
symfony/mesenger
Az első jelölt azonnal ideálisnak tűnt, és minden kétséget kizáróan beállítottam. De a Symfonyn kívüli felhasználási példák keresése a google-ban nehezebbnek bizonyult. Hogyan állítsunk össze egy buszt üzenetek átadására egy csomó univerzális, értelmetlen nevű osztályból, és még Redis-en is?
A hivatalos oldalon található dokumentáció meglehetősen részletes volt, de az inicializálást csak a Symfony számára írták le, a kedvenc YML-lel és más varázslatos módszerekkel a nem szimfonikus számára. Maga a telepítési folyamat nem érdekelt, különösen az újévi ünnepek alatt. De ezt váratlanul sokáig kellett csinálnom.
A Symfony-források használatával való rendszer példányosítása sem a legtriviálisabb feladat egy szűk határidő mellett:
Miután elmélyültem ebben az egészben, és megpróbáltam valamit csinálni a kezemmel, arra a következtetésre jutottam, hogy valamilyen mankóval foglalkozom, és úgy döntöttem, hogy megpróbálok valami mást.
világít/sor
Kiderült, hogy ez a könyvtár szorosan kötődik a Laravel infrastruktúrához és egy csomó egyéb függőséghez, így nem sok időt fordítottam rá: telepítettem, megnéztem, láttam a függőségeket és töröltem.
yiisoft/yii2-queue
Nos, itt rögtön a névből feltételezték, ismét szoros kapcsolat a Yii2-vel. Ezt a könyvtárat kellett használnom, és nem volt rossz, de nem gondoltam arra, hogy ez teljesen a Yii2-től függ.
A többit
Minden más, amit a GitHubon találtam, megbízhatatlan, elavult és félbehagyott projekt volt csillagok, elágazások és nagyszámú commit nélkül.
Vissza a symfonyhoz/messengerhez, technikai részletek
Ki kellett találnom ezt a könyvtárat, és miután több időt töltöttem, sikerült is. Kiderült, hogy minden nagyon tömör és egyszerű. A busz példányosításához készítettem egy kis gyárat, mert... Több abroncsom kellett volna, és különböző kezelőkkel.
Csak néhány lépés:
- Üzenetkezelőket hozunk létre, amelyeknek egyszerűen hívhatónak kell lenniük
- HandlerDescriptorba csomagoljuk (osztály a könyvtárból)
- Ezeket a „leírókat” egy HandlersLocator példányba csomagoljuk
- HandlersLocator hozzáadása a MessageBus példányhoz
- A SenderInterface egy halmazát adjuk át a SendersLocatornak, az én esetemben a RedisTransport osztályok példányait, amelyek nyilvánvaló módon vannak beállítva.
- A SendersLocator hozzáadása a MessageBus példányhoz
A MessageBus rendelkezik egy "->dispatch()" metódussal, amely megkeresi a megfelelő kezelőket a HandlersLocatorban, és átadja nekik az üzenetet a megfelelő "SenderInterface" használatával a buszon keresztül történő küldéshez (Redis folyamok).
A konténer konfigurációban (jelen esetben a php-di) ez a teljes köteg a következőképpen konfigurálható:
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);
},
Itt láthatja, hogy a SendersLocatorban különböző „szállításokat” rendeltünk két különböző üzenethez, amelyek mindegyike saját kapcsolattal rendelkezik a megfelelő adatfolyamokhoz.
Készítettem egy külön bemutató projektet, amely három démon alkalmazását mutatja be, amelyek a következő buszon keresztül kommunikálnak egymással:
De megmutatom, hogyan lehet egy fogyasztót felépíteni:
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();
Ennek az infrastruktúrának az alkalmazása egy alkalmazásban
Miután megvalósítottam a buszt a háttérrendszeremben, az egyes szakaszokat leválasztottam a régi szinkron parancsról, és külön kezelőket készítettem, amelyek mindegyike a maga dolgát végzi.
Az új webhely adatbázishoz való hozzáadásának folyamata így nézett ki:
És közvetlenül ezután sokkal könnyebbé vált számomra új funkciók hozzáadása, például az Rss kibontása és elemzése. Mert ehhez a folyamathoz is szükség van az eredeti tartalomra, majd az RSS-hivatkozás kibontó kezelője, mint a WebsiteIndexHistoryPersistor, feliratkozik a „Content/HtmlContent” üzenetre, feldolgozza azt, és továbbítja a kívánt üzenetet a folyamatban.
Végül több démonhoz jutottunk, amelyek mindegyike csak a szükséges erőforrásokkal tart kapcsolatot. Például egy démon csúszómászó tartalmazza az összes olyan kezelőt, amelyhez tartalomért az Internetre kell menni, és a démont kitartani kapcsolatot tart fenn az adatbázissal.
Most az adatbázisból történő kiválasztás helyett a szükséges azonosítók a perzisztor általi beillesztést követően egyszerűen elküldésre kerülnek a buszon keresztül minden érdeklődő kezelőhöz.
Forrás: will.com