PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Müqəddimə

Hobbi kimi işlətdiyim veb saytım maraqlı ana səhifələr və şəxsi saytlar üçün nəzərdə tutulub. Bu mövzu məni proqramlaşdırma səyahətimin lap əvvəlində maraqlandırmağa başladı; o anda özləri, hobbiləri və layihələri haqqında yazan böyük peşəkarlar tapmaq məni valeh etdi. Onları özüm üçün kəşf etmək vərdişi bu günə qədər qalır: demək olar ki, hər bir kommersiya və çox kommersiya olmayan saytda müəlliflərə bağlantılar axtarmaq üçün altbilgilərə baxmağa davam edirəm.

İdeyanın həyata keçirilməsi

İlk versiya şəxsi veb saytımda sadəcə bir html səhifəsi idi, burada imzalarla bağlantıları ul siyahısına qoyurdum. Müəyyən müddət ərzində 20 səhifə çap etdikdən sonra bunun çox da effektli olmadığını düşünməyə başladım və prosesi avtomatlaşdırmağa çalışmaq qərarına gəldim. Stackoverflow-da gördüm ki, bir çox insanlar öz profillərində saytları göstərirlər, ona görə də mən php-də təhlilçi yazdım, o, birincidən başlayaraq sadəcə profillərdən keçdi (bu günə qədər SO-da ünvanlar belədir: `/users/1` ), istədiyiniz etiketdən bağlantıları çıxardı və SQLite-ə əlavə etdi.

Bunu ikinci versiya adlandırmaq olar: HTML-də statik siyahını əvəz edən SQLite cədvəlində on minlərlə URL-lərin toplusu. Bu siyahıda sadə bir axtarış etdim. Çünki yalnız URL-lər var idi, sonra axtarış sadəcə onlara əsaslanırdı.

Bu mərhələdə layihəni tərk etdim və uzun müddətdən sonra ona qayıtdım. Bu mərhələdə mənim iş təcrübəm artıq üç ildən çox idi və hiss etdim ki, daha ciddi bir iş görə bilərəm. Bundan əlavə, nisbətən yeni texnologiyalara yiyələnməyə böyük həvəs var idi.

Müasir versiya

Layihə Docker-də yerləşdirilmiş verilənlər bazası mongoDb-ə köçürüldü və bu yaxınlarda turp əlavə edildi ki, bu da əvvəlcə yalnız keşləmə üçün idi. PHP mikroçərçivələrindən biri əsas kimi istifadə olunur.

problem

Yeni saytlar aşağıdakıları sinxron şəkildə yerinə yetirən konsol əmri ilə əlavə olunur:

  • Məzmunu URL ilə yükləyir
  • HTTPS-in mövcud olub-olmadığını göstərən bayraq təyin edir
  • Veb saytın mahiyyətini qoruyur
  • Mənbə HTML və başlıqlar “indeksləmə” tarixçəsində saxlanılır
  • Məzmunu təhlil edir, Başlıq və Təsviri çıxarır
  • Məlumatları ayrıca kolleksiyada saxlayır

Bu, sadəcə saytları saxlamaq və onları siyahıda göstərmək üçün kifayət idi:

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Ancaq hər şeyi avtomatik olaraq indeksləşdirmək, kateqoriyalara ayırmaq və sıralamaq, hər şeyi aktual saxlamaq ideyası bu paradiqmaya o qədər də uyğun gəlmirdi. Səhifələri əlavə etmək üçün sadəcə veb metodu əlavə etmək belə, potensial DDoS-un qarşısını almaq üçün kodun təkrarlanması və bloklanmasını tələb edirdi.

Ümumiyyətlə, əlbəttə ki, hər şey sinxron şəkildə edilə bilər və veb metodunda sadəcə URL-i saxlaya bilərsiniz ki, dəhşətli demon siyahıdakı URL-lər üçün bütün tapşırıqları yerinə yetirsin. Ancaq yenə də burada "növbə" sözü özünü göstərir. Və bir növbə həyata keçirilirsə, bütün tapşırıqlar ən azı asinxron şəkildə bölünə və yerinə yetirilə bilər.

qərar

Növbələri həyata keçirin və bütün tapşırıqları emal etmək üçün hadisəyə əsaslanan sistem yaradın. Mən uzun müddətdir Redis Streams-i sınamaq istəyirdim.

PHP-də Redis axınlarından istifadə

Çünki Çərçivəm Symfony, Laravel, Yii üç nəhəngindən biri olmadığı üçün müstəqil kitabxana tapmaq istərdim. Lakin məlum olduğu kimi (ilk müayinə zamanı) fərdi ciddi kitabxanalar tapmaq mümkün deyil. Növbələrlə bağlı hər şey ya beş il əvvəl 3 öhdəliyin layihəsidir, ya da çərçivəyə bağlıdır.

Fərdi faydalı komponentlərin təchizatçısı kimi Symfony haqqında çox eşitmişəm və onlardan bəzilərini artıq istifadə edirəm. Həm də Laravel-dən bəzi şeylər də istifadə edilə bilər, məsələn, onların ORM, çərçivənin özü olmadan.

symfony/messenger

Birinci namizəd dərhal ideal göründü və heç bir şübhə etmədən onu quraşdırdım. Lakin Symfony-dən kənar istifadə nümunələrini google-də axtarmaq daha çətin oldu. Universal, mənasız adlar, mesaj ötürmək üçün avtobus və hətta Redis-də bir dəstə sinifdən necə yığılmaq olar?

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Rəsmi saytdakı sənədlər kifayət qədər təfərrüatlı idi, lakin işə salınma yalnız Symfony üçün sevimli YML və simfonist olmayanlar üçün digər sehrli üsullardan istifadə edərək təsvir edilmişdir. Xüsusilə də Yeni il bayramlarında quraşdırma prosesinin özü ilə maraqlanmadım. Ancaq gözlənilmədən uzun müddət bunu etməli oldum.

Symfony mənbələrindən istifadə edərək bir sistemin necə qurulacağını anlamağa çalışmaq da son tarix üçün ən əhəmiyyətsiz iş deyil:

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Bütün bunları araşdırdıqdan və əllərimlə nəsə etməyə çalışdıqdan sonra belə bir nəticəyə gəldim ki, bir növ qoltuqağacı ilə məşğulam və başqa bir şey sınamaq qərarına gəldim.

işıqlı/növbə

Məlum oldu ki, bu kitabxana Laravel infrastrukturu və bir sıra digər asılılıqlarla sıx bağlıdır, ona görə də ona çox vaxt sərf etmədim: onu quraşdırdım, baxdım, asılılıqları gördüm və sildim.

yiisoft/yii2-növbəsi

Yaxşı, burada dərhal adından götürülmüşdür, yenə Yii2 ilə ciddi bir əlaqə. Bu kitabxanadan istifadə etməli oldum və pis deyildi, amma bunun Yii2-dən tamamilə asılı olduğunu düşünmədim.

Qalanlar

GitHub-da tapdığım hər şey ulduzları, çəngəlləri və çoxlu sayda öhdəlikləri olmayan etibarsız, köhnəlmiş və tərk edilmiş layihələr idi.

Symfony/messenger, texniki detallara qayıdın

Mən bu kitabxananı anlamalı idim və bir az daha vaxt sərf etdikdən sonra bacardım. Məlum oldu ki, hər şey kifayət qədər qısa və sadədir. Avtobusu yaratmaq üçün kiçik bir zavod tikdim, çünki... Mənim bir neçə təkərim və müxtəlif idarəediciləri olmalı idi.

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Sadəcə bir neçə addım:

  • Biz sadəcə çağırıla bilən mesaj işləyiciləri yaradırıq
  • Onları HandlerDescriptor-a bükürük (kitabxanadan sinif)
  • Biz bu “Təsvirləri” HandlersLocator instansiyasına yığırıq
  • MessageBus instansiyasına HandlersLocator əlavə edilməsi
  • Biz SendersLocator-a bir sıra `SenderInterface` ötürürük, mənim vəziyyətimdə aşkar şəkildə konfiqurasiya edilmiş `RedisTransport` sinifləri nümunələri
  • MessageBus instansiyasına SendersLocator əlavə edilir

MessageBus-da HandlersLocator-da müvafiq işləyiciləri axtaran və avtobus (Redis axınları) vasitəsilə göndərmək üçün müvafiq `SenderInterface` istifadə edərək mesajı onlara ötürən `->dispatch()` metodu var.

Konteyner konfiqurasiyasında (bu halda php-di) bütün bu paket belə konfiqurasiya edilə bilə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);
        },

Burada görə bilərsiniz ki, SendersLocator-da biz iki müxtəlif mesaj üçün müxtəlif “nəqliyyatlar” təyin etmişik, onların hər birinin müvafiq axınlarla öz əlaqəsi var.

Aşağıdakı avtobusdan istifadə edərək bir-biri ilə əlaqə saxlayan üç demonun tətbiqini nümayiş etdirən ayrıca demo layihəsi hazırladım: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Ancaq istehlakçının necə qurulacağını sizə göstərəcəyəm:

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

Tətbiqdə bu infrastrukturdan istifadə

Avtobusu arxa planımda tətbiq edərək, köhnə sinxron əmrdən ayrı-ayrı mərhələləri ayırdım və hər biri öz işini yerinə yetirən ayrıca işləyicilər yaratdım.

Verilənlər bazasına yeni sayt əlavə etmək üçün boru xətti belə görünürdü:

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Və bundan dərhal sonra yeni funksionallıq əlavə etmək, məsələn, Rss-ni çıxarmaq və təhlil etmək mənim üçün daha asan oldu. Çünki bu proses həm də orijinal məzmunu tələb edir, sonra WebsiteIndexHistoryPersistor kimi RSS keçid çıxarıcı işləyicisi “Məzmun/HtmlContent” mesajına abunə olur, onu emal edir və istədiyiniz mesajı boru xətti boyunca daha da ötürür.

PHP backendinin Redis axınları avtobusuna köçürülməsi və çərçivədən asılı olmayan kitabxananın seçilməsi

Nəhayət, hər biri yalnız lazımi mənbələrlə əlaqə saxlayan bir neçə demonla başa çatdıq. Məsələn, iblis Axtarış robotlarının məzmun üçün İnternetə getməyi tələb edən bütün işləyiciləri və demonu ehtiva edir israr etmək verilənlər bazası ilə əlaqə saxlayır.

İndi verilənlər bazasından seçmək əvəzinə, persister tərəfindən daxil edildikdən sonra tələb olunan identifikatorlar sadəcə avtobus vasitəsilə bütün maraqlı idarəçilərə ötürülür.

Mənbə: www.habr.com

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