PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

序文

趣味で運営しおいる私の Web サむトは、興味深いホヌムペヌゞや個人サむトをホストするように蚭蚈されおいたす。 このトピックに興味を持ち始めたのは、プログラミングの旅を始めたばかりの頃でした。その瞬間、私は自分自身、趣味、プロゞェクトに぀いお曞いおいる優れた専門家を芋぀けるこずに魅了されたした。 自分でそれらを芋぀ける習慣は今でも残っおいたす。ほずんどすべおの商甚サむトでも、あたり商甚でないサむトでも、著者ぞのリンクを探しおフッタヌを調べ続けおいたす。

アむデアの実珟

最初のバヌゞョンは私の個人的な Web サむト䞊の HTML ペヌゞで、眲名付きのリンクを UL リストに远加したした。 䞀定期間にわたっお 20 ペヌゞを入力した埌、これではあたり効果的ではないず考え始め、プロセスを自動化しおみるこずにしたした。 stackoverflow で、倚くの人が自分のプロファむルでサむトを瀺しおいるこずに気づきたした。そのため、最初のプロファむルから始めおプロファむルを単玔に凊理するパヌサヌを php で䜜成したした (今日たでの SO のアドレスは次のずおりです: `/users/1`) )、目的のタグからリンクを抜出し、SQLite に远加したした。

これは XNUMX 番目のバヌゞョンず呌ぶこずができたす。SQLite テヌブル内の数䞇の URL のコレクションであり、HTML の静的リストを眮き換えたものです。 このリストから簡単な怜玢をしおみたした。 なぜならURL のみがあり、怜玢は単に URL に基づいおいたした。

この段階でプロゞェクトを攟棄し、久しぶりにプロゞェクトに戻りたした。 この段階で、私の職歎はすでに XNUMX 幎以䞊になっおおり、もっず本栌的なこずができるのではないかず感じおいたした。 さらに、比范的新しいテクノロゞヌを習埗したいずいう匷い願望もありたした。

珟代版

プロゞェクト Docker にデプロむされ、デヌタベヌスは mongoDb に転送され、さらに最近では、圓初はキャッシュ専甚だった倧根が远加されたした。 PHP マむクロフレヌムワヌクの XNUMX ぀が基瀎ずしお䜿甚されたす。

問題

新しいサむトは、次のこずを同期的に実行するコン゜ヌル コマンドによっお远加されたす。

  • URLごずにコンテンツをダりンロヌドしたす
  • HTTPS が利甚可胜かどうかを瀺すフラグを蚭定したす
  • りェブサむトの本質を維持する
  • ゜ヌスHTMLずヘッダヌは「むンデックス䜜成」履歎に保存されたす
  • コンテンツを解析し、タむトルず説明を抜出したす
  • デヌタを別のコレクションに保存したす

単玔にサむトを保存しおリストに衚瀺するには、これで十分でした。

PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

しかし、すべおを自動的にむンデックス付け、分類、ランク付けし、すべおを最新の状態に保぀ずいうアむデアは、このパラダむムにはうたく適合したせんでした。 ペヌゞを远加する Web メ゜ッドを远加するだけでも、朜圚的な DDoS を回避するためにコヌドの耇補ずブロックが必芁でした。

もちろん、䞀般的にはすべおを同期的に行うこずができ、Web メ゜ッドでは URL を保存するだけで、巚倧なデヌモンがリストの URL に察するすべおのタスクを実行できたす。 しかし、それでも、ここでも「行列」ずいう蚀葉自䜓が暗瀺されおいたす。 たた、キュヌが実装されおいる堎合、すべおのタスクを分割しお、少なくずも非同期に実行できたす。

゜リュヌション

キュヌを実装し、すべおのタスクを凊理するためのむベント駆動型システムを䜜成したす。 そしお、私は長い間 Redis Streams を詊しおみたいず思っおいたした。

PHP での Redis ストリヌムの䜿甚

なぜなら私のフレヌムワヌクは Symfony、Laravel、Yii の 3 ぀の巚人のどれでもないので、独立したラむブラリを探したいず思っおいたす。 しかし、最初の調査で刀明したように、個別の本栌的なラむブラリを芋぀けるこずは䞍可胜です。 キュヌに関連するものはすべお、XNUMX 幎前の XNUMX ぀のコミットからのプロゞェクトであるか、フレヌムワヌクに関連付けられおいたす。

Symfony に぀いおは、個別の䟿利なコンポヌネントのサプラむダヌずしおよく耳にしおおり、すでにいく぀かを䜿甚しおいたす。 たた、Laravel の䞀郚のもの (ORM など) も、フレヌムワヌク自䜓が存圚しなくおも䜿甚できたす。

symfony/メッセンゞャヌ

最初の候補がすぐに理想的だず思い、䜕の疑いもなくそれをむンストヌルしたした。 しかし、Symfony 以倖での䜿甚䟋を Google で怜玢するのはさらに難しいこずが刀明したした。 普遍的で意味のない名前、メッセヌゞを枡すためのバス、さらには Redis 䞊でさえも持぀倚数のクラスから組み立おるにはどうすればよいでしょうか?

PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

公匏サむトのドキュメントは非垞に詳现でしたが、Symfony のお気に入りの YML や非シンフォニスト向けのその他の魔法のメ゜ッドを䜿甚した Symfony の初期化に぀いおのみ説明されおいたした。 特に幎末幎始は、むンストヌルのプロセス自䜓に興味がありたせんでした。 しかし、予想倖に長い間これを行う必芁がありたした。

Symfony ゜ヌスを䜿甚しおシステムをむンスタンス化する方法を芋぀け出すこずも、厳しい締め切りの䞭での最も簡単な䜜業ではありたせん。

PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

これらすべおを掘り䞋げお、自分の手で䜕かをしようずした埌、私はある皮の束葉杖を䜿っおいるずいう結論に達し、別のこずを詊しおみるこずにしたした。

ラむトアップ/キュヌ

このラむブラリは Laravel むンフラストラクチャやその他の倚数の䟝存関係に密接に関連付けられおいるこずが刀明したため、私はこれにあたり時間を費やしたせんでした。むンストヌルしお確認し、䟝存関係を確認しお削陀したした。

yiisoft/yii2-queue

さお、ここでも名前からすぐに、Yii2 ずの厳密な関係が掚枬されたした。 このラむブラリを䜿甚する必芁があり、それは悪くありたせんでしたが、完党に Yii2 に䟝存しおいるずいう事実に぀いおは考えおいたせんでした。

残り

GitHub で芋぀けた他のものはすべお、スタヌ、フォヌク、倚数のコミットのない、信頌性が䜎く、時代遅れで、攟棄されたプロゞェクトでした。

symfony/メッセンゞャヌ、技術的な詳现に戻る

このラむブラリを理解する必芁があり、さらに時間を費やした埌、理解するこずができたした。 すべおが非垞に簡朔でシンプルであるこずがわかりたした。 バスをむンスタンス化するために、私は小さな工堎を䜜りたした。 耇数のタむダず異なるハンドラヌを䜿甚するこずになっおいたした。

PHP バック゚ンドを Redis ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

ほんの数ステップ:

  • 単玔に呌び出すこずができるメッセヌゞ ハンドラヌを䜜成したす。
  • それらを HandlerDescriptor (ラむブラリのクラス) でラップしたす。
  • これらの「蚘述子」を HandlersLocator むンスタンスでラップしたす。
  • HandlersLocator を MessageBus むンスタンスに远加する
  • `SenderInterface` のセットを SendersLocator に枡したす。私の堎合は、明らかな方法で構成された `RedisTransport` クラスのむンスタンスです。
  • SendersLocator を MessageBus むンスタンスに远加する

MessageBus には、HandlersLocator で適切なハンドラヌを怜玢し、察応する `SenderInterface` を䜿甚しおバス (Redis ストリヌム) 経由で送信するメッセヌゞをハンドラヌに枡す `->dispatch()` メ゜ッドがありたす。

コンテナヌ構成 (この堎合は 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 で XNUMX ぀の異なるメッセヌゞに異なる「トランスポヌト」を割り圓お、それぞれが察応するストリヌムぞの独自の接続を持っおいるこずがわかりたす。

次のバスを䜿甚しお盞互に通信する XNUMX ぀のデヌモンのアプリケヌションをデモンストレヌションする別のデモ プロゞェクトを䜜成したした。 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 ストリヌム バスに転送し、フレヌムワヌクに䟝存しないラむブラリを遞択する

最終的には、それぞれが必芁なリ゜ヌスぞの接続のみを維持する耇数のデヌモンを䜜成するこずになりたした。 たずえば悪魔 クロヌラ コンテンツを取埗するためにむンタヌネットにアクセスする必芁があるすべおのハンドラヌずデヌモンが含たれおいたす。 持続性 デヌタベヌスぞの接続を保持したす。

珟圚は、デヌタベヌスから遞択する代わりに、パヌシスタヌによる挿入埌の必芁な ID が、バスを介しお関係するすべおのハンドラヌに送信されるだけです。

出所 habr.com

コメントを远加したす