PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Önsöz

Hobi olarak işlettiğim web sitem ilgi çekici ana sayfalara ve kişisel sitelere ev sahipliği yapacak şekilde tasarlandı. Bu konu programlama yolculuğumun en başında ilgimi çekmeye başladı; o anda kendileri, hobileri ve projeleri hakkında yazan harika profesyonelleri bulmak beni büyüledi. Bunları kendim keşfetme alışkanlığım bugüne kadar devam ediyor: Hemen hemen her ticari ve çok ticari olmayan sitede, yazarların bağlantılarını bulmak için altbilgiye bakmaya devam ediyorum.

Fikrin uygulanması

İlk sürüm, kişisel web sitemdeki, imzalı bağlantıları bir ul listesine yerleştirdiğim bir html sayfasıydı. Bir süre boyunca 20 sayfa yazdıktan sonra bunun pek etkili olmadığını düşünmeye başladım ve süreci otomatikleştirmeye karar verdim. Stackoverflow'ta birçok kişinin profillerinde siteleri belirttiğini fark ettim, bu yüzden php'de ilkinden başlayarak profilleri gözden geçiren bir ayrıştırıcı yazdım (SO'daki adresler bugüne kadar şöyle: `/users/1` ), istenen etiketten bağlantıları çıkardı ve SQLite'a ekledi.

Buna ikinci versiyon denilebilir: HTML'deki statik listenin yerini alan, SQLite tablosundaki onbinlerce URL'den oluşan bir koleksiyon. Bu listede basit bir arama yaptım. Çünkü yalnızca URL'ler vardı ve arama yalnızca URL'lere dayanıyordu.

Bu aşamada projeden vazgeçip uzun bir aradan sonra geri döndüm. Bu aşamada iş tecrübem zaten üç yıldan fazlaydı ve daha ciddi bir şeyler yapabileceğimi hissettim. Ayrıca nispeten yeni teknolojilere hakim olma konusunda büyük bir istek vardı.

Modern versiyon

Proje Docker'da konuşlandırılan veritabanı mongoDb'ye aktarıldı ve yakın zamanda, ilk başta sadece önbelleğe alma amaçlı olan turp eklendi. PHP mikro çerçevelerinden biri temel olarak kullanılır.

Sorun

Yeni siteler, aşağıdakileri eşzamanlı olarak yapan bir konsol komutuyla eklenir:

  • İçeriği URL'ye göre indirir
  • HTTPS'nin mevcut olup olmadığını gösteren bir bayrak ayarlar
  • Web sitesinin özünü korur
  • Kaynak HTML ve başlıklar "dizin oluşturma" geçmişine kaydedilir
  • İçeriği ayrıştırır, Başlık ve Açıklamayı çıkarır
  • Verileri ayrı bir koleksiyona kaydeder

Bu, siteleri basitçe depolamak ve bir listede görüntülemek için yeterliydi:

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Ancak her şeyi otomatik olarak indeksleme, kategorize etme ve sıralama, her şeyi güncel tutma fikri bu paradigmaya pek uymuyordu. Sayfaları eklemek için basit bir web yöntemi eklemek bile potansiyel DDoS'u önlemek için kod çoğaltmayı ve engellemeyi gerektiriyordu.

Genel olarak, elbette, her şey eşzamanlı olarak yapılabilir ve web yönteminde, canavar arka plan programının listedeki URL'ler için tüm görevleri yerine getirmesi için URL'yi kolayca kaydedebilirsiniz. Ama yine de burada bile "sıra" kelimesi kendini gösteriyor. Ve eğer bir kuyruk uygulanırsa, tüm görevler en azından eşzamansız olarak bölünebilir ve gerçekleştirilebilir.

karar

Kuyrukları uygulayın ve tüm görevleri işlemek için olay odaklı bir sistem oluşturun. Uzun zamandır Redis Streams'i denemek istiyordum.

PHP'de Redis akışlarını kullanma

Çünkü Çerçevem ​​üç dev Symfony, Laravel, Yii'den biri olmadığı için bağımsız bir kütüphane bulmak istiyorum. Ancak ortaya çıktığı gibi (ilk incelemede), tek tek ciddi kütüphaneler bulmak imkansızdır. Kuyruklarla ilgili her şey ya beş yıl önceki 3 taahhütten gelen bir projedir ya da çerçeveye bağlıdır.

Bireysel kullanışlı bileşenlerin tedarikçisi olarak Symfony hakkında çok şey duydum ve bazılarını zaten kullanıyorum. Ayrıca Laravel'den bazı şeyler de kullanılabilir, örneğin ORM'leri, çerçevenin kendisi olmadan da kullanılabilir.

senfony/haberci

İlk aday hemen ideal görünüyordu ve hiç şüphesiz onu kurdum. Ancak Symfony dışındaki kullanım örneklerini Google'da aramanın daha zor olduğu ortaya çıktı. Evrensel, anlamsız isimlere sahip bir grup sınıftan, mesajları iletmek için bir otobüsten ve hatta Redis'ten nasıl bir araya gelinir?

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Resmi sitedeki belgeler oldukça ayrıntılıydı, ancak başlatma yalnızca Symfony için, en sevdikleri YML ve senfonist olmayanlar için diğer sihirli yöntemler kullanılarak anlatılmıştı. Özellikle Yeni Yıl tatillerinde kurulum süreciyle hiç ilgilenmedim. Ancak bunu beklenmedik derecede uzun bir süre yapmak zorunda kaldım.

Symfony kaynaklarını kullanarak bir sistemin nasıl başlatılacağını bulmaya çalışmak da kısa bir süre için en önemsiz görev değildir:

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Bütün bunları araştırıp ellerimle bir şeyler yapmaya çalıştıktan sonra, bir çeşit koltuk değneği yaptığım sonucuna vardım ve başka bir şey denemeye karar verdim.

aydınlatılmış/sıra

Bu kütüphanenin Laravel altyapısına ve diğer birçok bağımlılığa sıkı sıkıya bağlı olduğu ortaya çıktı, bu yüzden üzerinde fazla zaman harcamadım: Onu kurdum, ona baktım, bağımlılıkları gördüm ve sildim.

yiisoft/yii2 kuyruğu

Eh, burada yine adından Yii2 ile sıkı bir bağlantı olduğu varsayıldı. Bu kütüphaneyi kullanmak zorundaydım ve fena değildi ama bunun tamamen Yii2'ye bağlı olduğu gerçeğini düşünmedim.

Diğer

GitHub'da bulduğum diğer her şey güvenilmez, modası geçmiş ve yıldızları, çatalları ve çok sayıda taahhütleri olmayan terk edilmiş projelerdi.

Symfony/messenger'a dön, teknik detaylar

Bu kütüphaneyi çözmem gerekiyordu ve biraz daha zaman harcadıktan sonra başardım. Her şeyin oldukça özlü ve basit olduğu ortaya çıktı. Otobüsü örneklendirmek için küçük bir fabrika yaptım çünkü... Birkaç lastiğim ve farklı yol tutuşçularım olması gerekiyordu.

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Sadece birkaç adım:

  • Basitçe çağrılabilmesi gereken mesaj işleyicileri yaratıyoruz
  • Bunları HandlerDescriptor'a sarıyoruz (kütüphanedeki sınıf)
  • Bu “Tanımlayıcıları” bir HandlersLocator örneğine sarıyoruz
  • HandlersLocator'ı MessengerBus örneğine ekleme
  • SendersLocator'a bir dizi "SenderInterface" iletiyoruz; benim durumumda açık bir şekilde yapılandırılmış "RedisTransport" sınıflarının örnekleri
  • SendersLocator'ı MesajBus örneğine ekleme

MesajBus, HandlersLocator'da uygun işleyicileri arayan ve veri yolu (Redis akışları) aracılığıyla göndermek için ilgili "SenderInterface"i kullanarak mesajı onlara ileten bir "->dispatch()" yöntemine sahiptir.

Konteyner konfigürasyonunda (bu durumda php-di), bu paketin tamamı şu şekilde yapılandırılabilir:

        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 SendersLocator'da iki farklı mesaj için farklı "aktarımlar" atadığımızı görebilirsiniz; bunların her birinin ilgili akışlarla kendi bağlantısı vardır.

Aşağıdaki veri yolunu kullanarak birbirleriyle iletişim kuran üç arka plan programının uygulamasını gösteren ayrı bir demo proje yaptım: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Ama size bir tüketicinin nasıl yapılandırılabileceğini göstereceğim:

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

Bu altyapının bir uygulamada kullanılması

Bus'ı arka uçta uyguladıktan sonra, bireysel aşamaları eski senkronize komuttan ayırdım ve her biri kendi işini yapan ayrı işleyiciler yaptım.

Veritabanına yeni bir site ekleme hattı şuna benziyordu:

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Ve bundan hemen sonra, örneğin Rss'yi çıkarmak ve ayrıştırmak gibi yeni işlevler eklemek benim için çok daha kolay hale geldi. Çünkü bu süreç aynı zamanda orijinal içeriği de gerektirir, ardından WebsiteIndexHistoryPersistor gibi RSS bağlantı çıkarıcı işleyicisi "Content/HtmlContent" mesajına abone olur, onu işler ve istenen mesajı kendi hattı boyunca iletir.

PHP arka ucunu Redis akış veriyoluna aktarma ve çerçeveden bağımsız bir kitaplık seçme

Sonunda, her biri yalnızca gerekli kaynaklarla bağlantıları koruyan birkaç arka plan programı elde ettik. Mesela bir şeytan tarayıcıların içerik için İnternet'e gitmeyi gerektiren tüm işleyicileri ve arka plan programını içerir ısrar etmek veritabanına bağlantı sağlar.

Artık veritabanından seçim yapmak yerine, ısrarcı tarafından eklendikten sonra gerekli kimlikler veri yolu aracılığıyla ilgili tüm işleyicilere iletilir.

Kaynak: habr.com

Yorum ekle