Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Perutusan

Laman web saya, yang saya jalankan sebagai hobi, direka untuk mengehoskan halaman utama dan laman peribadi yang menarik. Topik ini mula menarik minat saya pada awal perjalanan pengaturcaraan saya; pada masa itu saya terpesona dengan mencari profesional yang hebat yang menulis tentang diri mereka, hobi dan projek mereka. Kebiasaan untuk menemui mereka sendiri masih kekal hingga ke hari ini: di hampir setiap tapak komersil dan bukan sangat komersial, saya terus melihat di footer untuk mencari pautan kepada pengarang.

Pelaksanaan idea

Versi pertama hanyalah halaman html di laman web peribadi saya, di mana saya meletakkan pautan dengan tandatangan ke dalam senarai ul. Setelah menaip 20 halaman dalam tempoh masa tertentu, saya mula berfikir bahawa ini tidak begitu berkesan dan memutuskan untuk cuba mengautomasikan proses tersebut. Pada stackoverflow, saya perhatikan bahawa ramai orang menunjukkan tapak dalam profil mereka, jadi saya menulis parser dalam php, yang hanya melalui profil, bermula dengan yang pertama (alamat di SO hingga ke hari ini adalah seperti ini: `/users/1` ), mengekstrak pautan daripada teg yang dikehendaki dan menambahkannya dalam SQLite.

Ini boleh dipanggil versi kedua: koleksi puluhan ribu URL dalam jadual SQLite, yang menggantikan senarai statik dalam HTML. Saya melakukan carian mudah pada senarai ini. Kerana hanya terdapat URL, maka carian hanya berdasarkannya.

Pada peringkat ini saya meninggalkan projek itu dan kembali kepadanya selepas sekian lama. Pada peringkat ini, pengalaman kerja saya sudah melebihi tiga tahun dan saya rasa saya boleh melakukan sesuatu yang lebih serius. Di samping itu, terdapat keinginan yang besar untuk menguasai teknologi yang agak baru.

Versi moden

Projek digunakan di Docker, pangkalan data telah dipindahkan ke mongoDb, dan baru-baru ini, lobak telah ditambah, yang pada mulanya hanya untuk caching. Salah satu kerangka kerja mikro PHP digunakan sebagai asas.

masalah

Tapak baharu ditambah dengan arahan konsol yang melakukan perkara berikut secara serentak:

  • Memuat turun kandungan mengikut URL
  • Menetapkan bendera yang menunjukkan sama ada HTTPS tersedia
  • Memelihara intipati laman web
  • HTML sumber dan pengepala disimpan dalam sejarah "pengindeksan".
  • Menghuraikan kandungan, mengekstrak Tajuk dan Penerangan
  • Menyimpan data ke koleksi berasingan

Ini sudah cukup untuk menyimpan tapak dan memaparkannya dalam senarai:

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Tetapi idea untuk mengindeks secara automatik, mengkategorikan dan meletakkan kedudukan segala-galanya, memastikan segala-galanya dikemas kini, tidak sesuai dengan paradigma ini. Walaupun hanya menambah kaedah web untuk menambah halaman memerlukan penduaan dan penyekatan kod untuk mengelakkan potensi DDoS.

Secara umum, sudah tentu, segala-galanya boleh dilakukan secara serentak, dan dalam kaedah web anda hanya boleh menyimpan URL supaya daemon yang dahsyat melaksanakan semua tugas untuk URL dari senarai. Tetapi masih, walaupun di sini perkataan "baris gilir" mencadangkan dirinya sendiri. Dan jika baris gilir dilaksanakan, maka semua tugas boleh dibahagikan dan dilakukan sekurang-kurangnya secara tidak segerak.

keputusan

Laksanakan baris gilir dan buat sistem dipacu peristiwa untuk memproses semua tugas. Dan saya telah lama ingin mencuba Redis Streams.

Menggunakan aliran Redis dalam PHP

Kerana Memandangkan rangka kerja saya bukan salah satu daripada tiga gergasi Symfony, Laravel, Yii, saya ingin mencari perpustakaan bebas. Tetapi, ternyata (pada peperiksaan pertama), adalah mustahil untuk mencari perpustakaan individu yang serius. Segala-galanya yang berkaitan dengan baris gilir adalah sama ada projek daripada 3 commit lima tahun lalu, atau terikat dengan rangka kerja.

Saya telah mendengar banyak tentang Symfony sebagai pembekal komponen berguna individu, dan saya sudah menggunakan sebahagian daripadanya. Dan juga beberapa perkara dari Laravel juga boleh digunakan, contohnya ORM mereka, tanpa kehadiran rangka kerja itu sendiri.

symfony/messenger

Calon pertama serta-merta kelihatan ideal dan tanpa sebarang keraguan saya memasangnya. Tetapi ternyata lebih sukar untuk menggoogle contoh penggunaan di luar Symfony. Bagaimana untuk memasang dari sekumpulan kelas dengan nama universal, tidak bermakna, bas untuk menghantar mesej, dan juga pada Redis?

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Dokumentasi di tapak rasmi agak terperinci, tetapi permulaan hanya diterangkan untuk Symfony menggunakan YML kegemaran mereka dan kaedah sihir lain untuk bukan simfoni. Saya tidak berminat dengan proses pemasangan itu sendiri, terutamanya semasa cuti Tahun Baru. Tetapi saya terpaksa melakukan ini untuk masa yang lama yang tidak dijangka.

Mencuba untuk memikirkan cara membuat instantiate sistem menggunakan sumber Symfony juga bukanlah tugas yang paling remeh untuk tarikh akhir yang ketat:

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Selepas menyelidiki semua ini dan cuba melakukan sesuatu dengan tangan saya, saya membuat kesimpulan bahawa saya sedang melakukan beberapa jenis tongkat dan memutuskan untuk mencuba sesuatu yang lain.

bercahaya / beratur

Ternyata perpustakaan ini terikat rapat dengan infrastruktur Laravel dan sekumpulan kebergantungan lain, jadi saya tidak menghabiskan banyak masa untuknya: saya memasangnya, melihatnya, melihat kebergantungan dan memadamkannya.

yiisoft/yii2-queue

Nah, di sini ia segera diandaikan dari namanya, sekali lagi, sambungan ketat kepada Yii2. Saya terpaksa menggunakan perpustakaan ini dan ia tidak buruk, tetapi saya tidak memikirkan hakikat bahawa ia bergantung sepenuhnya kepada Yii2.

Selebihnya

Segala-galanya yang saya temui di GitHub adalah projek yang tidak boleh dipercayai, lapuk dan terbengkalai tanpa bintang, garpu dan sejumlah besar komitmen.

Kembali ke symfony/messenger, butiran teknikal

Saya terpaksa memikirkan perpustakaan ini dan, selepas meluangkan sedikit masa, saya dapat melakukannya. Ternyata semuanya cukup ringkas dan ringkas. Untuk melancarkan bas, saya membuat kilang kecil, kerana... Saya sepatutnya mempunyai beberapa tayar dan dengan pengendali yang berbeza.

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Hanya beberapa langkah:

  • Kami mencipta pengendali mesej yang sepatutnya boleh dipanggil
  • Kami membungkusnya dalam HandlerDescriptor (kelas dari perpustakaan)
  • Kami membungkus "Deskriptor" ini dalam contoh HandlersLocator
  • Menambah HandlersLocator pada contoh MessageBus
  • Kami menghantar satu set `SenderInterface` kepada SendersLocator, dalam contoh kes saya bagi kelas `RedisTransport`, yang dikonfigurasikan dengan cara yang jelas
  • Menambah SendersLocator pada contoh MessageBus

MessageBus mempunyai kaedah `->dispatch()` yang mencari pengendali yang sesuai dalam HandlersLocator dan menghantar mesej kepada mereka, menggunakan `SenderInterface` yang sepadan untuk dihantar melalui bas (strim Redis).

Dalam konfigurasi kontena (dalam kes ini php-di), keseluruhan berkas ini boleh dikonfigurasikan 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 boleh melihat bahawa dalam SendersLocator kami telah menetapkan "pengangkutan" yang berbeza untuk dua mesej yang berbeza, setiap satunya mempunyai sambungan sendiri ke strim yang sepadan.

Saya membuat projek demo berasingan yang menunjukkan aplikasi tiga daemon berkomunikasi antara satu sama lain menggunakan bas berikut: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

Tetapi saya akan menunjukkan kepada anda cara pengguna boleh distrukturkan:

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 aplikasi

Setelah melaksanakan bas di bahagian belakang saya, saya memisahkan peringkat individu daripada arahan segerak lama dan membuat pengendali berasingan, yang masing-masing melakukan perkara mereka sendiri.

Saluran paip untuk menambah tapak baharu pada pangkalan data kelihatan seperti ini:

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Dan selepas itu, menjadi lebih mudah bagi saya untuk menambah fungsi baharu, contohnya, mengekstrak dan menghuraikan Rss. Kerana proses ini juga memerlukan kandungan asal, kemudian pengendali pengekstrak pautan RSS, seperti WebsiteIndexHistoryPersistor, melanggan mesej "Kandungan/HtmlContent", memprosesnya dan menghantar mesej yang dikehendaki di sepanjang saluran paipnya dengan lebih lanjut.

Memindahkan bahagian belakang PHP ke bas aliran Redis dan memilih perpustakaan bebas rangka kerja

Akhirnya, kami mendapat beberapa daemon, yang masing-masing mengekalkan sambungan hanya kepada sumber yang diperlukan. Contohnya syaitan crawler mengandungi semua pengendali yang memerlukan pergi ke Internet untuk kandungan, dan daemon berterusan memegang sambungan ke pangkalan data.

Kini, daripada memilih daripada pangkalan data, id yang diperlukan, selepas dimasukkan oleh persister, hanya dihantar melalui bas kepada semua pengendali yang berminat.

Sumber: www.habr.com

Tambah komen