PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Նախաբան

Իմ կայքը, որը ես աշխատում եմ որպես հոբբի, նախատեսված է հետաքրքիր գլխավոր էջեր և անձնական կայքեր տեղադրելու համար: Այս թեման սկսեց ինձ հետաքրքրել ծրագրավորման իմ ճամփորդության հենց սկզբում, այդ պահին ես հիացած էի գտնելով հիանալի մասնագետներ, ովքեր գրում են իրենց, իրենց հոբբիների և նախագծերի մասին: Ինձ համար դրանք բացահայտելու սովորությունը մնում է մինչ օրս. գրեթե բոլոր կոմերցիոն և ոչ շատ առևտրային կայքերում ես շարունակում եմ փնտրել էջատակում՝ հեղինակների հետ հղումներ փնտրելու համար:

Գաղափարի իրականացում

Առաջին տարբերակը պարզապես html էջ էր իմ անձնական կայքում, որտեղ ես ստորագրություններով հղումներ էի դնում ul ցուցակում: Ժամանակի ընթացքում մուտքագրելով 20 էջ, ես սկսեցի մտածել, որ դա այնքան էլ արդյունավետ չէ և որոշեցի փորձել ավտոմատացնել գործընթացը: Stackoverflow-ում ես նկատեցի, որ շատ մարդիկ իրենց պրոֆիլներում նշում են կայքեր, ուստի ես գրեցի php-ով վերլուծիչ, որը պարզապես անցավ պրոֆիլների միջով՝ սկսած առաջինից (SO-ի հասցեները մինչ օրս այսպիսին են՝ `/users/1`: ), հանեց հղումներ ցանկալի պիտակից և ավելացրեց այն SQLite-ում:

Սա կարելի է անվանել երկրորդ տարբերակը՝ տասնյակ հազարավոր URL-ների հավաքածու SQLite աղյուսակում, որը փոխարինեց ստատիկ ցուցակը HTML-ում։ Ես պարզ որոնում արեցի այս ցուցակում: Որովհետեւ կային միայն URL-ներ, ապա որոնումը պարզապես հիմնված էր դրանց վրա։

Այս փուլում ես լքեցի նախագիծը և երկար ժամանակ անց վերադարձա դրան։ Այս փուլում աշխատանքային փորձս արդեն երեք տարուց ավելի էր, և ես զգում էի, որ կարող եմ ավելի լուրջ բանով զբաղվել։ Բացի այդ, մեծ ցանկություն կար տիրապետելու համեմատաբար նոր տեխնոլոգիաներին։

Ժամանակակից տարբերակ

Ծրագիր տեղակայվելով Docker-ում, տվյալների բազան տեղափոխվել է mongoDb, իսկ վերջերս ավելացվել է բողկ, որը սկզբում միայն քեշավորման համար էր: Որպես հիմք օգտագործվում է PHP միկրոշրջանակներից մեկը։

խնդիր

Նոր կայքերն ավելացվում են վահանակի հրամանով, որը համաժամանակաբար կատարում է հետևյալը.

  • Ներբեռնում է բովանդակությունը ըստ URL-ի
  • Սահմանում է դրոշակ, որը ցույց է տալիս, թե արդյոք HTTPS-ը հասանելի է եղել
  • Պահպանում է կայքի էությունը
  • Աղբյուրի HTML-ը և վերնագրերը պահվում են «ինդեքսավորման» պատմության մեջ
  • Վերլուծում է բովանդակությունը, քաղվածքների վերնագիրը և նկարագրությունը
  • Տվյալները պահպանում է առանձին հավաքածուի մեջ

Սա բավական էր պարզապես կայքերը պահելու և դրանք ցուցակում ցուցադրելու համար.

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Բայց ամեն ինչ ավտոմատ կերպով ինդեքսավորելու, դասակարգելու և դասակարգելու, ամեն ինչ արդիական պահելու գաղափարը լավ չէր տեղավորվում այս պարադիգմում: Նույնիսկ էջեր ավելացնելու համար վեբ մեթոդ ավելացնելը պահանջում է կոդի կրկնօրինակում և արգելափակում՝ հնարավոր DDoS-ից խուսափելու համար:

Ընդհանուր առմամբ, իհարկե, ամեն ինչ կարելի է անել համաժամանակյա, իսկ վեբ մեթոդով դուք կարող եք պարզապես պահպանել URL-ը, որպեսզի հրեշավոր դեյմոնը կատարի ցուցակի URL-ների բոլոր առաջադրանքները: Բայց այնուամենայնիվ, նույնիսկ այստեղ «հերթ» բառն իրեն հուշում է։ Իսկ եթե հերթ է իրականացվում, ապա բոլոր առաջադրանքները կարելի է բաժանել և կատարել առնվազն ասինխրոն։

որոշում

Իրականացնել հերթեր և ստեղծել իրադարձությունների վրա հիմնված համակարգ բոլոր առաջադրանքների մշակման համար: Եվ ես վաղուց էի ցանկանում փորձել Redis Streams-ը:

PHP-ում Redis հոսքերի օգտագործումը

Որովհետեւ Քանի որ իմ շրջանակը Symfony, Laravel, Yii երեք հսկաներից մեկը չէ, ես կցանկանայի անկախ գրադարան գտնել: Բայց, ինչպես պարզվեց (առաջին փորձաքննության ժամանակ), անհնար է առանձին լուրջ գրադարաններ գտնել։ Հերթերի հետ կապված ամեն ինչ կա՛մ նախագիծ է հինգ տարվա վաղեմության 3 պարտավորություններից, կա՛մ կապված է շրջանակի հետ:

Ես շատ եմ լսել Symfony-ի մասին՝ որպես առանձին օգտակար բաղադրիչների մատակարար, և ես արդեն օգտագործում եմ դրանցից մի քանիսը: Եվ նաև Laravel-ից որոշ բաներ կարող են օգտագործվել, օրինակ, նրանց ORM-ը, առանց բուն շրջանակի առկայության:

սիմֆոնիա/մեսենջեր

Առաջին թեկնածուն անմիջապես թվաց իդեալական և առանց կասկածի ես այն տեղադրեցի։ Բայց պարզվեց, որ ավելի դժվար է Google-ում փնտրել Symfony-ից դուրս օգտագործման օրինակներ։ Ինչպե՞ս հավաքել համընդհանուր, անիմաստ անուններով դասերի մի փունջ, հաղորդագրություններ փոխանցելու ավտոբուս և նույնիսկ Redis-ում:

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Պաշտոնական կայքում ներկայացված փաստաթղթերը բավականին մանրամասն էին, բայց սկզբնավորումը նկարագրված էր միայն Symfony-ի համար՝ օգտագործելով իրենց սիրելի YML-ը և այլ կախարդական մեթոդներ ոչ սիմֆոնիստի համար: Ինձ ոչ մի հետաքրքրություն չէր ներկայացնում բուն տեղադրման գործընթացը, հատկապես ամանորյա տոներին։ Բայց ես ստիպված էի դա անել անսպասելիորեն երկար ժամանակ:

Փորձել պարզել, թե ինչպես կարելի է համակարգել Symfony-ի աղբյուրները օգտագործելով, նույնպես ամենաչնչին գործը չէ սեղմ ժամկետի համար.

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Այս ամենի մեջ խորանալով և ձեռքերով ինչ-որ բան անելուց հետո ես եկա այն եզրակացության, որ ինչ-որ հենակներ եմ անում և որոշեցի այլ բան փորձել։

լուսավորված/հերթ

Պարզվեց, որ այս գրադարանը սերտորեն կապված էր Laravel ենթակառուցվածքի և մի շարք այլ կախվածությունների հետ, ուստի ես շատ ժամանակ չէի ծախսում դրա վրա. ես տեղադրեցի այն, նայեցի դրան, տեսա կախվածությունները և ջնջեցի այն:

yiisoft/yii2-հերթ

Դե, այստեղ անմիջապես ենթադրվում էր անունից, կրկին խիստ կապ Yii2-ի հետ։ Ես ստիպված էի օգտագործել այս գրադարանը, և դա վատ չէր, բայց ես չէի մտածում այն ​​մասին, որ այն ամբողջովին կախված է Yii2-ից:

Մնացածը

Մնացած ամեն ինչ, որ ես գտա GitHub-ում, անվստահելի, հնացած և լքված նախագծեր էին առանց աստղերի, պատառաքաղների և մեծ թվով պարտավորությունների:

Վերադարձ դեպի symfony/messenger, տեխնիկական մանրամասներ

Ես ստիպված էի պարզել այս գրադարանը և, ևս մի քիչ ժամանակ անցկացնելուց հետո, կարողացա: Պարզվեց, որ ամեն ինչ բավականին հակիրճ ու պարզ էր։ Ավտոբուսը օրինականացնելու համար ես փոքրիկ գործարան պատրաստեցի, քանի որ... Ես պետք է ունենայի մի քանի անվադողեր և տարբեր կարգավորիչներով:

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Ընդամենը մի քանի քայլ.

  • Մենք ստեղծում ենք հաղորդագրությունների մշակիչներ, որոնք պետք է պարզապես կանչելի լինեն
  • Մենք դրանք փաթաթում ենք HandlerDescriptor-ում (դաս գրադարանից)
  • Մենք այս «Նկարագրիչները» փաթաթում ենք HandlersLocator օրինակով
  • HandlersLocator-ի ավելացում MessageBus օրինակին
  • Մենք «SenderInterface»-ի մի շարք փոխանցում ենք «SenderLocator»-ին, իմ դեպքում «RedisTransport» դասերի օրինակներ, որոնք կազմաձևված են ակնհայտ ձևով:
  • SendersLocator-ի ավելացում MessageBus օրինակին

MessageBus-ն ունի «->dispatch()» մեթոդ, որը փնտրում է համապատասխան մշակողներին HandlersLocator-ում և փոխանցում նրանց հաղորդագրությունը՝ օգտագործելով համապատասխան «SenderInterface»՝ ավտոբուսի միջոցով ուղարկելու համար (Redis հոսքերը):

Կոնտեյների կազմաձևում (այս դեպքում 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 backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Եվ դրանից անմիջապես հետո ինձ համար շատ ավելի հեշտ դարձավ նոր ֆունկցիոնալություն ավելացնելը, օրինակ՝ Rss-ի արդյունահանումը և վերլուծությունը։ Որովհետեւ այս գործընթացը նաև պահանջում է բնօրինակ բովանդակություն, այնուհետև RSS հղումը արդյունահանող կառավարիչը, ինչպես WebsiteIndexHistoryPersistor-ը, բաժանորդագրվում է «Content/HtmlContent» հաղորդագրությանը, մշակում և փոխանցում է ցանկալի հաղորդագրությունը իր խողովակաշարով:

PHP backend-ի փոխանցում Redis streams ավտոբուսին և ընտրել շրջանակից անկախ գրադարան

Ի վերջո, մենք հայտնվեցինք մի քանի դևոնների մոտ, որոնցից յուրաքանչյուրը կապ է պահպանում միայն անհրաժեշտ ռեսուրսների հետ: Օրինակ՝ դև crawlers պարունակում է բոլոր այն մշակողները, որոնք պահանջում են բովանդակության համար մուտք գործել ինտերնետ, և դևոն համառել կապ է պահպանում տվյալների բազայի հետ:

Այժմ, տվյալների բազայից ընտրելու փոխարեն, պահանջվող id-ները ներդնելուց հետո շարունակվողի կողմից պարզապես ավտոբուսի միջոցով փոխանցվում են բոլոր շահագրգիռ մշակողներին:

Source: www.habr.com

Добавить комментарий