Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

kata pengantar

Situs web saya, yang saya jalankan sebagai hobi, dirancang untuk menampung halaman beranda dan situs pribadi yang menarik. Topik ini mulai menarik minat saya di awal perjalanan pemrograman saya; pada saat itu saya terpesona menemukan profesional hebat yang menulis tentang diri mereka sendiri, hobi dan proyek mereka. Kebiasaan menemukannya sendiri masih ada hingga hari ini: di hampir setiap situs komersial dan bukan situs komersial, saya terus mencari di footer untuk mencari tautan ke penulisnya.

Implementasi ide

Versi pertama hanyalah halaman html di situs pribadi saya, tempat saya memasukkan link dengan tanda tangan ke dalam daftar ul. Setelah mengetik 20 halaman selama jangka waktu tertentu, saya mulai berpikir bahwa ini tidak terlalu efektif dan memutuskan untuk mencoba mengotomatiskan prosesnya. Di stackoverflow, saya perhatikan banyak orang menunjukkan situs di profil mereka, jadi saya menulis parser di php, yang hanya menelusuri profil, dimulai dengan yang pertama (alamat di SO hingga hari ini adalah seperti ini: `/users/1` ), mengekstrak tautan dari tag yang diinginkan dan menambahkannya di SQLite.

Ini bisa disebut versi kedua: kumpulan puluhan ribu URL dalam tabel SQLite, yang menggantikan daftar statis dalam HTML. Saya melakukan pencarian sederhana di daftar ini. Karena hanya ada URL, maka pencarian hanya didasarkan pada URL tersebut.

Pada tahap ini saya meninggalkan proyek tersebut dan kembali lagi setelah sekian lama. Pada tahap ini, pengalaman kerja saya sudah lebih dari tiga tahun dan saya merasa bisa melakukan sesuatu yang lebih serius. Selain itu, ada keinginan besar untuk menguasai teknologi yang relatif baru.

Versi modern

Proyek dikerahkan di Docker, database ditransfer ke mongoDb, dan baru-baru ini, lobak ditambahkan, yang pada awalnya hanya untuk caching. Salah satu microframework PHP digunakan sebagai dasar.

masalah

Situs baru ditambahkan dengan perintah konsol yang secara sinkron melakukan hal berikut:

  • Mengunduh konten berdasarkan URL
  • Menyetel tanda yang menunjukkan apakah HTTPS tersedia
  • Mempertahankan esensi situs web
  • HTML sumber dan header disimpan dalam riwayat "pengindeksan".
  • Parsing konten, ekstrak Judul dan Deskripsi
  • Menyimpan data ke koleksi terpisah

Ini cukup untuk menyimpan situs dan menampilkannya dalam daftar:

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Namun gagasan untuk mengindeks, mengkategorikan, dan memberi peringkat secara otomatis, menjaga semuanya tetap terkini, tidak cocok dengan paradigma ini. Bahkan sekadar menambahkan metode web untuk menambahkan halaman memerlukan duplikasi dan pemblokiran kode untuk menghindari potensi DDoS.

Secara umum, tentu saja, semuanya dapat dilakukan secara sinkron, dan dalam metode web Anda cukup menyimpan URL sehingga daemon raksasa tersebut melakukan semua tugas untuk URL dari daftar. Tapi tetap saja, bahkan di sini kata “antrian” tetap muncul. Dan jika antrian diterapkan, maka semua tugas dapat dibagi dan dilakukan setidaknya secara asinkron.

keputusan

Menerapkan antrian dan membuat sistem berbasis peristiwa untuk memproses semua tugas. Dan saya sudah lama ingin mencoba Redis Streams.

Menggunakan aliran Redis di PHP

Karena Karena kerangka kerja saya bukan salah satu dari tiga raksasa Symfony, Laravel, Yii, saya ingin mencari perpustakaan independen. Namun, ternyata (pada pemeriksaan pertama), tidak mungkin menemukan perpustakaan yang serius. Segala sesuatu yang berhubungan dengan antrian adalah proyek dari 3 komitmen lima tahun lalu, atau terikat pada kerangka kerja.

Saya telah mendengar banyak tentang Symfony sebagai pemasok komponen-komponen individual yang berguna, dan saya sudah menggunakan beberapa di antaranya. Dan juga beberapa hal dari Laravel juga bisa digunakan, misalnya ORM mereka, tanpa kehadiran framework itu sendiri.

symfony/messenger

Kandidat pertama langsung tampak ideal dan tanpa ragu saya memasangnya. Namun ternyata lebih sulit untuk mencari contoh penggunaan Google di luar Symfony. Bagaimana cara merakit dari sekumpulan kelas dengan nama universal dan tidak berarti, bus untuk menyampaikan pesan, dan bahkan di Redis?

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Dokumentasi di situs resminya cukup detail, tetapi inisialisasi hanya dijelaskan untuk Symfony menggunakan YML favorit mereka dan metode ajaib lainnya untuk non-simfonis. Saya tidak tertarik dengan proses instalasinya sendiri, apalagi saat liburan tahun baru. Tapi saya harus melakukan ini untuk waktu yang sangat lama.

Mencoba mencari cara untuk membuat instance sistem menggunakan sumber Symfony juga bukan tugas yang paling sepele dalam tenggat waktu yang ketat:

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Setelah mempelajari semua ini dan mencoba melakukan sesuatu dengan tangan saya, saya sampai pada kesimpulan bahwa saya sedang melakukan semacam kruk dan memutuskan untuk mencoba sesuatu yang lain.

menyala/antrian

Ternyata perpustakaan ini terkait erat dengan infrastruktur Laravel dan banyak dependensi lainnya, jadi saya tidak menghabiskan banyak waktu untuk itu: saya menginstalnya, melihatnya, melihat dependensinya, dan menghapusnya.

yiisoft/yii2-antrian

Nah, di sini langsung diasumsikan dari namanya, sekali lagi, ada hubungannya dengan Yii2. Saya harus menggunakan perpustakaan ini dan itu tidak buruk, tapi saya tidak memikirkan fakta bahwa itu sepenuhnya bergantung pada Yii2.

Sisanya

Semua hal lain yang saya temukan di GitHub adalah proyek yang tidak dapat diandalkan, ketinggalan jaman, dan ditinggalkan tanpa bintang, fork, dan banyak komitmen.

Kembali ke symfony/messenger, detail teknis

Saya harus mencari tahu perpustakaan ini dan, setelah menghabiskan lebih banyak waktu, saya bisa melakukannya. Ternyata semuanya cukup ringkas dan sederhana. Untuk membuat instance bus, saya membuat pabrik kecil, karena... Saya seharusnya memiliki beberapa ban dan dengan penangan yang berbeda.

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Hanya beberapa langkah:

  • Kami membuat penangan pesan yang dapat dipanggil dengan mudah
  • Kami membungkusnya dalam HandlerDescriptor (kelas dari perpustakaan)
  • Kami membungkus “Deskriptor” ini dalam instance HandlersLocator
  • Menambahkan HandlersLocator ke instance MessageBus
  • Kami meneruskan satu set `SenderInterface` ke SendersLocator, dalam kasus saya contoh kelas `RedisTransport`, yang dikonfigurasi dengan cara yang jelas
  • Menambahkan SendersLocator ke instans MessageBus

MessageBus memiliki metode `->dispatch()` yang mencari penangan yang sesuai di HandlersLocator dan meneruskan pesan kepada mereka, menggunakan `SenderInterface` yang sesuai untuk mengirim melalui bus (aliran Redis).

Dalam konfigurasi container (dalam hal ini php-di), seluruh bundel ini dapat dikonfigurasi seperti ini:

        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);
        },

Di sini Anda dapat melihat bahwa di SendersLocator kami telah menetapkan “transportasi” yang berbeda untuk dua pesan berbeda, yang masing-masing memiliki koneksi sendiri ke aliran yang sesuai.

Saya membuat proyek demo terpisah yang mendemonstrasikan penerapan tiga daemon yang berkomunikasi satu sama lain menggunakan bus berikut: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Namun saya akan menunjukkan kepada Anda bagaimana konsumen dapat disusun:

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

Menggunakan infrastruktur ini dalam suatu aplikasi

Setelah mengimplementasikan bus di backend saya, saya memisahkan tahapan individual dari perintah sinkron lama dan membuat penangan terpisah, yang masing-masing melakukan urusannya sendiri.

Alur untuk menambahkan situs baru ke database tampak seperti ini:

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Dan segera setelah itu, menjadi lebih mudah bagi saya untuk menambahkan fungsionalitas baru, misalnya mengekstrak dan mengurai Rss. Karena proses ini juga memerlukan konten asli, kemudian pengendali ekstraktor tautan RSS, seperti WebsiteIndexHistoryPersistor, berlangganan pesan “Konten/HtmlContent”, memprosesnya dan meneruskan pesan yang diinginkan di sepanjang salurannya lebih lanjut.

Mentransfer backend PHP ke bus aliran Redis dan memilih perpustakaan yang tidak bergantung pada kerangka kerja

Pada akhirnya, kami mendapatkan beberapa daemon, yang masing-masing memelihara koneksi hanya ke sumber daya yang diperlukan. Misalnya setan crawler berisi semua penangan yang memerlukan akses Internet untuk mendapatkan konten, dan daemon bertahan memegang koneksi ke database.

Sekarang, alih-alih memilih dari database, id yang diperlukan setelah dimasukkan oleh persister cukup dikirimkan melalui bus ke semua penangan yang berkepentingan.

Sumber: www.habr.com

Tambah komentar