PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Алғы сөз

Мен хобби ретінде басқаратын веб-сайтым қызықты басты беттер мен жеке сайттарды орналастыруға арналған. Бұл тақырып мені бағдарламалау саяхатымның ең басында қызықтыра бастады; сол кезде мені өздері, хоббилері мен жобалары туралы жазатын керемет мамандарды табу қатты қызықтырды. Оларды өзім үшін табу әдеті осы күнге дейін сақталады: кез келген коммерциялық және өте коммерциялық емес сайтта мен авторларға сілтемелерді іздеу үшін төменгі деректемелерді іздеуді жалғастырамын.

Идеяның жүзеге асуы

Бірінші нұсқа менің жеке веб-сайтымдағы html беті болды, онда мен ul тізіміне қолтаңбалары бар сілтемелерді қойдым. Белгілі бір уақыт ішінде 20 бетті тергеннен кейін мен бұл өте тиімді емес деп ойладым және процесті автоматтандыруға тырыстым. Stackoverflow кезінде мен көптеген адамдар өздерінің профильдерінде сайттарды көрсететінін байқадым, сондықтан мен php тілінде талдаушы жаздым, ол біріншіден бастап профильдерді аралап шықты (бүгінгі күнге дейін SO мекенжайлары келесідей: `/users/1` ), қажетті тегтен сілтемелерді шығарып, оны SQLite ішіне қосты.

Мұны екінші нұсқа деп атауға болады: HTML тіліндегі статикалық тізімді ауыстырған SQLite кестесіндегі ондаған мың URL мекенжайларының жинағы. Мен бұл тізімде қарапайым іздеу жасадым. Өйткені тек URL мекенжайлары болды, содан кейін іздеу оларға негізделген.

Осы кезеңде мен жобаны тастап, ұзақ уақыттан кейін оған қайта оралдым. Бұл кезеңде менің жұмыс тәжірибем үш жылдан астам болды және мен одан да маңызды нәрсе істей алатынымды сезіндім. Сонымен қатар, салыстырмалы түрде жаңа технологияларды меңгеруге деген ынта зор болды.

Қазіргі нұсқа

Жоба Docker-де орналастырылған, дерекқор mongoDb-ге ауыстырылды, ал жақында ғана редиш қосылды, ол алдымен кэштеу үшін ғана болды. Негіз ретінде РНР микрофремворктерінің бірі қолданылады.

проблема

Жаңа сайттар келесі әрекеттерді синхронды түрде орындайтын консоль пәрмені арқылы қосылады:

  • Мазмұнды URL мекенжайы бойынша жүктеп алады
  • HTTPS қолжетімділігін көрсететін жалаушаны орнатады
  • Веб-сайттың мәнін сақтайды
  • Бастапқы HTML және тақырыптар «индекстеу» тарихында сақталады
  • Мазмұнды талдайды, Тақырып пен Сипаттаманы шығарады
  • Деректерді бөлек жинаққа сақтайды

Бұл жай ғана сайттарды сақтау және оларды тізімде көрсету үшін жеткілікті болды:

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Бірақ барлығын автоматты түрде индекстеу, санаттау және рейтингтеу, бәрін жаңартып отыру идеясы бұл парадигмаға сәйкес келмеді. Беттерді қосу үшін жай ғана веб-әдіс қосу үшін кодты қайталау және ықтимал DDoS-ті болдырмау үшін блоктау қажет.

Тұтастай алғанда, әрине, барлығын синхронды түрде жасауға болады және веб-әдісінде сіз жай ғана URL мекенжайын сақтай аласыз, осылайша құбыжық демон тізімдегі URL мекенжайлары үшін барлық тапсырмаларды орындайды. Дегенмен, бұл жерде де «кезек» сөзі өзін көрсетеді. Ал егер кезек орындалса, онда барлық тапсырмаларды бөлуге және кем дегенде асинхронды түрде орындауға болады.

шешім

Кезектерді енгізіңіз және барлық тапсырмаларды өңдеу үшін оқиғаға негізделген жүйе жасаңыз. Мен Redis Streams-ті көптен бері қолданғым келеді.

PHP-де Redis ағындарын пайдалану

Өйткені Менің шеңберім Symfony, Laravel, Yii үш алыптарының бірі емес болғандықтан, мен тәуелсіз кітапхана тапқым келеді. Бірақ, белгілі болғандай (алғашқы тексеруде) жеке маңызды кітапханаларды табу мүмкін емес. Кезектерге қатысты барлық нәрсе бес жыл бұрынғы 3 тапсырманың жобасы немесе құрылымға байланысты.

Мен жеке пайдалы компоненттерді жеткізуші ретінде Symfony туралы көп естідім және олардың кейбірін қолданамын. Сондай-ақ, Laravel-тен кейбір нәрселерді, мысалы, олардың ORM-ін, рамканың өзінсіз пайдалануға болады.

symfony/messenger

Бірінші үміткер бірден идеалды болып көрінді және мен оны орнаттым. Бірақ Symfony-дан тыс қолдану мысалдарын Google арқылы іздеу қиынырақ болып шықты. Әмбебап, мағынасыз атаулары бар, хабарламаларды жіберуге арналған автобуспен және тіпті Redis-те бірнеше сыныптан қалай жиналуға болады?

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Ресми сайттағы құжаттама өте егжей-тегжейлі болды, бірақ инициализация Symfony үшін олардың сүйікті YML және симфонист емес үшін басқа сиқырлы әдістерді қолдану арқылы ғана сипатталды. Орнату процесінің өзіне, әсіресе, жаңа жылдық мерекелерде мені қызықтырмады. Бірақ мен мұны күтпеген ұзақ уақыт бойы істеуге тура келді.

Symfony көздерін пайдаланып жүйені құру жолын анықтауға тырысу да қысқа мерзім үшін ең тривиальды тапсырма емес:

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Осының бәріне тереңірек үңіліп, қолыммен бірдеңе жасауға тырысқаннан кейін, мен балдақтардың бір түрін жасап жатырмын деген қорытындыға келдім және басқа нәрсемен айналысуды шештім.

жарықтандырылған/кезекте

Бұл кітапхана Laravel инфрақұрылымымен және басқа да көптеген тәуелділіктермен тығыз байланысты екені белгілі болды, сондықтан мен оған көп уақыт жұмсамадым: мен оны орнаттым, қарадым, тәуелділіктерді көрдім және оны жойдым.

yiisoft/yii2-кезегі

Міне, бұл бірден атаудан алынды, қайтадан Yii2-ге қатаң байланыс. Мен бұл кітапхананы пайдалануым керек болды және ол жаман емес еді, бірақ ол толығымен Yii2-ге байланысты екенін ойламадым.

Қалған

Мен GitHub сайтында тапқан барлық нәрселер жұлдызсыз, шанышқысыз және көптеген міндеттемелерсіз сенімсіз, ескірген және тасталған жобалар болды.

Symfony/messenger, техникалық мәліметтерге оралу

Мен бұл кітапхананы анықтауға тура келді және тағы біраз уақыт өткізгеннен кейін мен мүмкін болдым. Барлығы өте қысқа және қарапайым болып шықты. Автобусты жасау үшін мен шағын зауыт жасадым, өйткені... Менде бірнеше шиналар және әртүрлі өңдеушілер болуы керек еді.

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Бірнеше қадамдар:

  • Біз жай ғана шақыруға болатын хабар өңдеушілерін жасаймыз
  • Біз оларды HandlerDescriptor ішіне орап аламыз (кітапханадан сынып)
  • Біз бұл «Дескрипторларды» HandlersLocator данасына орамыз
  • MessageBus данасына HandlersLocator қосу
  • Біз «SenderInterface» жинағын SendersLocator қызметіне береміз, менің жағдайда анық түрде конфигурацияланған «RedisTransport» сыныптарының даналары
  • MessageBus данасына SendersLocator қосу

MessageBus жүйесінде HandlersLocator ішінен сәйкес өңдеушілерді іздейтін `->dispatch()` әдісі бар, ол шина (Redis ағындары) арқылы жіберу үшін сәйкес `SenderInterface` арқылы хабарды жібереді.

Контейнер конфигурациясында (бұл жағдайда php-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);
        },

Мұнда сіз SendersLocator бағдарламасында біз екі түрлі хабар үшін әртүрлі «тасымалдауларды» тағайындағанымызды көре аласыз, олардың әрқайсысының сәйкес ағындарға өз қосылымы бар.

Мен келесі автобус арқылы бір-бірімен байланысатын үш демонның қосымшасын көрсететін жеке демонстрациялық жоба жасадым: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Бірақ мен сізге тұтынушыны қалай құрылымдауға болатындығын көрсетемін:

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

Бұл инфрақұрылымды қолданбада пайдалану

Өзімнің серверімде автобусты енгізгеннен кейін, мен ескі синхронды командадан жеке кезеңдерді бөліп, әрқайсысы өз істерін жасайтын бөлек өңдеушілер жасадым.

Дерекқорға жаңа сайт қосу құбыры келесідей болды:

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Осыдан кейін бірден маған жаңа функционалдылықты қосу оңайырақ болды, мысалы, Rss алу және талдау. Өйткені бұл процесс сонымен қатар түпнұсқа мазмұнды қажет етеді, содан кейін WebsiteIndexHistoryPersistor сияқты RSS сілтемесін шығарушы өңдеушісі "Content/HtmlContent" хабарына жазылады, оны өңдейді және қажетті хабарды өз құбыры бойымен әрі қарай жібереді.

PHP серверін Redis ағындары шинасына тасымалдау және құрылымға тәуелсіз кітапхананы таңдау

Соңында біз бірнеше демондармен аяқталдық, олардың әрқайсысы тек қажетті ресурстармен байланыс жасайды. Мысалы, жын сканерлер мазмұн үшін Интернетке өтуді қажет ететін барлық өңдеушілерді және демонды қамтиды сақтау деректер қорымен байланысын сақтайды.

Енді дерекқордан таңдаудың орнына, персистер енгізгеннен кейін қажетті идентификаторлар жай ғана автобус арқылы барлық мүдделі өңдеушілерге жіберіледі.

Ақпарат көзі: www.habr.com

пікір қалдыру