Նախաբան
Իմ կայքը, որը ես աշխատում եմ որպես հոբբի, նախատեսված է հետաքրքիր գլխավոր էջեր և անձնական կայքեր տեղադրելու համար: Այս թեման սկսեց ինձ հետաքրքրել ծրագրավորման իմ ճամփորդության հենց սկզբում, այդ պահին ես հիացած էի գտնելով հիանալի մասնագետներ, ովքեր գրում են իրենց, իրենց հոբբիների և նախագծերի մասին: Ինձ համար դրանք բացահայտելու սովորությունը մնում է մինչ օրս. գրեթե բոլոր կոմերցիոն և ոչ շատ առևտրային կայքերում ես շարունակում եմ փնտրել էջատակում՝ հեղինակների հետ հղումներ փնտրելու համար:
Գաղափարի իրականացում
Առաջին տարբերակը պարզապես html էջ էր իմ անձնական կայքում, որտեղ ես ստորագրություններով հղումներ էի դնում ul ցուցակում: Ժամանակի ընթացքում մուտքագրելով 20 էջ, ես սկսեցի մտածել, որ դա այնքան էլ արդյունավետ չէ և որոշեցի փորձել ավտոմատացնել գործընթացը: Stackoverflow-ում ես նկատեցի, որ շատ մարդիկ իրենց պրոֆիլներում նշում են կայքեր, ուստի ես գրեցի php-ով վերլուծիչ, որը պարզապես անցավ պրոֆիլների միջով՝ սկսած առաջինից (SO-ի հասցեները մինչ օրս այսպիսին են՝ `/users/1`: ), հանեց հղումներ ցանկալի պիտակից և ավելացրեց այն SQLite-ում:
Սա կարելի է անվանել երկրորդ տարբերակը՝ տասնյակ հազարավոր URL-ների հավաքածու SQLite աղյուսակում, որը փոխարինեց ստատիկ ցուցակը HTML-ում։ Ես պարզ որոնում արեցի այս ցուցակում: Որովհետեւ կային միայն URL-ներ, ապա որոնումը պարզապես հիմնված էր դրանց վրա։
Այս փուլում ես լքեցի նախագիծը և երկար ժամանակ անց վերադարձա դրան։ Այս փուլում աշխատանքային փորձս արդեն երեք տարուց ավելի էր, և ես զգում էի, որ կարող եմ ավելի լուրջ բանով զբաղվել։ Բացի այդ, մեծ ցանկություն կար տիրապետելու համեմատաբար նոր տեխնոլոգիաներին։
Ժամանակակից տարբերակ
խնդիր
Նոր կայքերն ավելացվում են վահանակի հրամանով, որը համաժամանակաբար կատարում է հետևյալը.
- Ներբեռնում է բովանդակությունը ըստ URL-ի
- Սահմանում է դրոշակ, որը ցույց է տալիս, թե արդյոք HTTPS-ը հասանելի է եղել
- Պահպանում է կայքի էությունը
- Աղբյուրի HTML-ը և վերնագրերը պահվում են «ինդեքսավորման» պատմության մեջ
- Վերլուծում է բովանդակությունը, քաղվածքների վերնագիրը և նկարագրությունը
- Տվյալները պահպանում է առանձին հավաքածուի մեջ
Սա բավական էր պարզապես կայքերը պահելու և դրանք ցուցակում ցուցադրելու համար.
Բայց ամեն ինչ ավտոմատ կերպով ինդեքսավորելու, դասակարգելու և դասակարգելու, ամեն ինչ արդիական պահելու գաղափարը լավ չէր տեղավորվում այս պարադիգմում: Նույնիսկ էջեր ավելացնելու համար վեբ մեթոդ ավելացնելը պահանջում է կոդի կրկնօրինակում և արգելափակում՝ հնարավոր DDoS-ից խուսափելու համար:
Ընդհանուր առմամբ, իհարկե, ամեն ինչ կարելի է անել համաժամանակյա, իսկ վեբ մեթոդով դուք կարող եք պարզապես պահպանել URL-ը, որպեսզի հրեշավոր դեյմոնը կատարի ցուցակի URL-ների բոլոր առաջադրանքները: Բայց այնուամենայնիվ, նույնիսկ այստեղ «հերթ» բառն իրեն հուշում է։ Իսկ եթե հերթ է իրականացվում, ապա բոլոր առաջադրանքները կարելի է բաժանել և կատարել առնվազն ասինխրոն։
որոշում
Իրականացնել հերթեր և ստեղծել իրադարձությունների վրա հիմնված համակարգ բոլոր առաջադրանքների մշակման համար: Եվ ես վաղուց էի ցանկանում փորձել Redis Streams-ը:
PHP-ում Redis հոսքերի օգտագործումը
Որովհետեւ Քանի որ իմ շրջանակը Symfony, Laravel, Yii երեք հսկաներից մեկը չէ, ես կցանկանայի անկախ գրադարան գտնել: Բայց, ինչպես պարզվեց (առաջին փորձաքննության ժամանակ), անհնար է առանձին լուրջ գրադարաններ գտնել։ Հերթերի հետ կապված ամեն ինչ կա՛մ նախագիծ է հինգ տարվա վաղեմության 3 պարտավորություններից, կա՛մ կապված է շրջանակի հետ:
Ես շատ եմ լսել Symfony-ի մասին՝ որպես առանձին օգտակար բաղադրիչների մատակարար, և ես արդեն օգտագործում եմ դրանցից մի քանիսը: Եվ նաև Laravel-ից որոշ բաներ կարող են օգտագործվել, օրինակ, նրանց ORM-ը, առանց բուն շրջանակի առկայության:
սիմֆոնիա/մեսենջեր
Առաջին թեկնածուն անմիջապես թվաց իդեալական և առանց կասկածի ես այն տեղադրեցի։ Բայց պարզվեց, որ ավելի դժվար է Google-ում փնտրել Symfony-ից դուրս օգտագործման օրինակներ։ Ինչպե՞ս հավաքել համընդհանուր, անիմաստ անուններով դասերի մի փունջ, հաղորդագրություններ փոխանցելու ավտոբուս և նույնիսկ Redis-ում:
Պաշտոնական կայքում ներկայացված փաստաթղթերը բավականին մանրամասն էին, բայց սկզբնավորումը նկարագրված էր միայն Symfony-ի համար՝ օգտագործելով իրենց սիրելի YML-ը և այլ կախարդական մեթոդներ ոչ սիմֆոնիստի համար: Ինձ ոչ մի հետաքրքրություն չէր ներկայացնում բուն տեղադրման գործընթացը, հատկապես ամանորյա տոներին։ Բայց ես ստիպված էի դա անել անսպասելիորեն երկար ժամանակ:
Փորձել պարզել, թե ինչպես կարելի է համակարգել Symfony-ի աղբյուրները օգտագործելով, նույնպես ամենաչնչին գործը չէ սեղմ ժամկետի համար.
Այս ամենի մեջ խորանալով և ձեռքերով ինչ-որ բան անելուց հետո ես եկա այն եզրակացության, որ ինչ-որ հենակներ եմ անում և որոշեցի այլ բան փորձել։
լուսավորված/հերթ
Պարզվեց, որ այս գրադարանը սերտորեն կապված էր Laravel ենթակառուցվածքի և մի շարք այլ կախվածությունների հետ, ուստի ես շատ ժամանակ չէի ծախսում դրա վրա. ես տեղադրեցի այն, նայեցի դրան, տեսա կախվածությունները և ջնջեցի այն:
yiisoft/yii2-հերթ
Դե, այստեղ անմիջապես ենթադրվում էր անունից, կրկին խիստ կապ Yii2-ի հետ։ Ես ստիպված էի օգտագործել այս գրադարանը, և դա վատ չէր, բայց ես չէի մտածում այն մասին, որ այն ամբողջովին կախված է Yii2-ից:
Մնացածը
Մնացած ամեն ինչ, որ ես գտա GitHub-ում, անվստահելի, հնացած և լքված նախագծեր էին առանց աստղերի, պատառաքաղների և մեծ թվով պարտավորությունների:
Վերադարձ դեպի symfony/messenger, տեխնիկական մանրամասներ
Ես ստիպված էի պարզել այս գրադարանը և, ևս մի քիչ ժամանակ անցկացնելուց հետո, կարողացա: Պարզվեց, որ ամեն ինչ բավականին հակիրճ ու պարզ էր։ Ավտոբուսը օրինականացնելու համար ես փոքրիկ գործարան պատրաստեցի, քանի որ... Ես պետք է ունենայի մի քանի անվադողեր և տարբեր կարգավորիչներով:
Ընդամենը մի քանի քայլ.
- Մենք ստեղծում ենք հաղորդագրությունների մշակիչներ, որոնք պետք է պարզապես կանչելի լինեն
- Մենք դրանք փաթաթում ենք 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-ում մենք տարբեր «փոխադրումներ» ենք նշանակել երկու տարբեր հաղորդագրությունների համար, որոնցից յուրաքանչյուրն ունի իր սեփական կապը համապատասխան հոսքերի հետ:
Ես պատրաստեցի առանձին ցուցադրական նախագիծ՝ ցույց տալով երեք դևերի կիրառումը, որոնք միմյանց հետ շփվում են հետևյալ ավտոբուսի միջոցով.
Բայց ես ձեզ ցույց կտամ, թե ինչպես կարող է կառուցված լինել սպառողը.
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();
Այս ենթակառուցվածքի օգտագործումը հավելվածում
Գործարկելով ավտոբուսը իմ հետին մասում, ես առանձնացրեցի առանձին փուլերը հին համաժամանակյա հրամանից և պատրաստեցի առանձին կառավարիչներ, որոնցից յուրաքանչյուրն անում է իր գործը:
Տվյալների բազայում նոր կայք ավելացնելու խողովակաշարն այսպիսի տեսք ուներ.
Եվ դրանից անմիջապես հետո ինձ համար շատ ավելի հեշտ դարձավ նոր ֆունկցիոնալություն ավելացնելը, օրինակ՝ Rss-ի արդյունահանումը և վերլուծությունը։ Որովհետեւ այս գործընթացը նաև պահանջում է բնօրինակ բովանդակություն, այնուհետև RSS հղումը արդյունահանող կառավարիչը, ինչպես WebsiteIndexHistoryPersistor-ը, բաժանորդագրվում է «Content/HtmlContent» հաղորդագրությանը, մշակում և փոխանցում է ցանկալի հաղորդագրությունը իր խողովակաշարով:
Ի վերջո, մենք հայտնվեցինք մի քանի դևոնների մոտ, որոնցից յուրաքանչյուրը կապ է պահպանում միայն անհրաժեշտ ռեսուրսների հետ: Օրինակ՝ դև crawlers պարունակում է բոլոր այն մշակողները, որոնք պահանջում են բովանդակության համար մուտք գործել ինտերնետ, և դևոն համառել կապ է պահպանում տվյալների բազայի հետ:
Այժմ, տվյալների բազայից ընտրելու փոխարեն, պահանջվող id-ները ներդնելուց հետո շարունակվողի կողմից պարզապես ավտոբուսի միջոցով փոխանցվում են բոլոր շահագրգիռ մշակողներին:
Source: www.habr.com