
Kata Pengantar
Situs saya, yang saya lakukan sebagai hobi, dirancang untuk menyimpan halaman rumah dan situs pribadi yang menarik. Topik ini mulai menarik minat saya di awal jalur saya dalam pemrograman, pada saat itu saya senang menemukan profesional hebat yang menulis tentang diri mereka sendiri, hobi dan proyek mereka. Kebiasaan menemukan mereka tetap ada untuk saat ini: di hampir setiap iklan dan tidak di situs web, saya terus mencari ke catatan kaki untuk mencari tautan ke penulis.
Implementasi ide
Versi pertama hanya halaman html di situs pribadi saya, tempat saya menaruh tautan dengan tanda tangan di daftar ul. Setelah mengetik 20 halaman untuk beberapa waktu, saya mulai berpikir bahwa itu tidak terlalu efektif dan memutuskan untuk mencoba mengotomatiskan proses. Pada stackoverflow, saya perhatikan bahwa banyak situs menunjukkan dalam profil mereka, jadi saya menulis parser php yang baru saja berjalan melalui profil, mulai dari yang pertama (alamat pada SO dan sampai hari ini seperti ini: `/ users / 1`), tautan yang diekstraksi dari tag yang diinginkan dan ditumpuk dalam SQLite.
Ini bisa disebut versi kedua: kumpulan puluhan ribu URL dalam plat SQLite yang menggantikan daftar statis dalam html. Saya melakukan pencarian sederhana pada daftar ini. Karena hanya ada url, maka pencarian hanya untuk mereka.
Pada tahap ini, saya meninggalkan proyek dan kembali ke sana, setelah waktu yang 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 untuk diri mereka sendiri.
Versi modern
Proyek ini ditempatkan di buruh pelabuhan, basis data dipindahkan ke mongoDb, dan, relatif baru-baru ini, lobak ditambahkan, yang pada awalnya hanya untuk caching. Sebagai dasar, salah satu mikroframe PHP digunakan.
Masalah
Situs baru ditambahkan oleh perintah konsol yang secara sinkron melakukan hal berikut:
- Unduh konten dengan URL
- Tandai apakah HTTPS tersedia
- Mempertahankan esensi situs web
- Sumber HTML dan Header Menyimpan ke Riwayat Pengindeksan
- Mengurai konten, mengambil Judul dan Deskripsi
- Menyimpan data ke koleksi terpisah.
Ini cukup untuk hanya menyimpan situs dan menampilkannya dalam daftar:

Tetapi gagasan untuk secara otomatis mengindeks, mengelompokkan, dan memberi peringkat pada segala sesuatu, menjaga agar semuanya selalu mutakhir, cocok dengan paradigma ini dengan lemah. Bahkan hanya menambahkan metode web untuk menambahkan halaman diperlukan duplikasi dan pemblokiran kode untuk menghindari potensi DDoS.
Secara umum, tentu saja, semuanya dapat dilakukan secara serempak, dan dalam metode web, cukup simpan URL untuk memastikan bahwa daemon dahsyat melakukan semua tugas untuk URL dari daftar. Tapi semua sama, bahkan di sini kata "turn" memohon. Dan jika antrian diimplementasikan, maka semua tugas dapat dibagi dan dilakukan setidaknya secara tidak sinkron.
Solusi
Memperkenalkan antrian dan membuat sistem pemrosesan yang digerakkan oleh peristiwa untuk semua tugas. Dan untuk waktu yang lama saya ingin mencoba Redis Streams.
Menggunakan aliran Redis di PHP
Karena Saya tidak memiliki kerangka kerja dari tiga raksasa Symfony, Laravel, Yii, dan saya ingin mencari perpustakaan independen. Tetapi, ternyata (pada pemeriksaan pertama), tidak mungkin menemukan perpustakaan yang serius. Segala sesuatu yang terkait dengan antrian adalah proyeksi 3 komit lima tahun lalu, atau terikat pada suatu kerangka kerja.
Saya telah mendengar tentang Symfony sebagai penyedia beberapa komponen yang berguna, dan saya sudah menggunakan beberapa. Dan juga dari Laravel, sesuatu juga dapat digunakan, misalnya, ORM mereka, tanpa kehadiran kerangka itu sendiri.
symfony / messenger
Kandidat pertama segera tampak ideal, dan tanpa ragu saya memasangnya. Tapi itu lebih sulit untuk google contoh penggunaan di luar Symfony. Bagaimana cara mengumpulkan dari tumpukan kelas dengan universal, tidak berbicara nama apa pun, bus untuk mengirim pesan, dan bahkan pada Redis?

Dokumentasi di situs resmi cukup rinci, tetapi inisialisasi hanya dijelaskan untuk Symfony menggunakan YML favorit mereka dan metode sihir lainnya untuk non-symphonist. Saya tidak tertarik dengan proses instalasi, terutama selama liburan Tahun Baru. Tapi saya harus melakukan ini untuk waktu yang lama.
Mencoba mencari cara bagaimana membuat sistem menggunakan sumber Symfony juga bukan tugas yang paling sepele untuk tenggat waktu yang ketat:

Mengaduk-aduk hal 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.
menerangi / mengantri
Ternyata perpustakaan ini terikat erat dengan infrastruktur Laravel dan banyak dependensi lainnya, jadi saya tidak menghabiskan banyak waktu untuk itu: menginstal, melihat, melihat dependensi, dan menghapusnya.
yiisoft / yii2-antrian
Nah, ini dia langsung diasumsikan dari namanya, lagi-lagi pengikat ketat untuk Yii2. Saya harus menggunakan perpustakaan ini dan itu tidak buruk, tapi saya tidak berpikir itu sepenuhnya tergantung pada Yii2.
Sisanya
Semua hal lain yang saya temukan di github adalah proyeksi usang dan ditinggalkan yang tidak dapat diandalkan tanpa bintang, garpu dan sejumlah besar komitmen.
Kembali ke symfony / messenger, detail teknis
Saya harus berurusan dengan perpustakaan ini dan setelah menghabiskan lebih banyak waktu, saya bisa melakukannya. Ternyata semuanya cukup ringkas dan sederhana. Untuk instantiation bus, saya membuat pabrik kecil, karena Saya punya beberapa ban dengan penangan yang berbeda.

Hanya beberapa langkah:
- Buat penangan pesan yang seharusnya bisa dipanggil
- Bungkus mereka dalam HandlerDescriptor (kelas dari perpustakaan)
- Kami membungkus "Deskriptor" ini dalam contoh HandlersLocator.
- Tambahkan HandlerLocator ke instance MessageBus
- Kami memberikan kepada SendersLocator seperangkat `SenderInterface`, dalam kasus saya contoh dari kelas` RedisTransport`, yang dikonfigurasi dengan cara yang jelas.
- Tambahkan SendersLocator ke instance MessageBus
MessageBus memiliki metode `-> dispatch ()`, yang mencari penangan yang tepat di HandlersLocator dan meneruskan pesan kepada mereka, menggunakan `SenderInterface` yang sesuai untuk mengirim melalui bus (Redis stream).
Dalam konfigurasi wadah (dalam hal ini php-di), seluruh kumpulan ini dapat dikonfigurasi sebagai berikut:
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([ \App\Messages\SecretJsonMessages::class => [CONTAINER_REDIS_TRANSPORT_SECRET], \App\Messages\DaemonLogMessage::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); },
Dapat dilihat bahwa di SendersLocator kami menetapkan "transport" yang berbeda untuk dua pesan yang berbeda, masing-masing memiliki koneksi sendiri ke stream yang sesuai.
Saya membuat proyek demo terpisah yang menunjukkan aplikasi tiga setan berkomunikasi satu sama lain menggunakan bus semacam itu:
https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus .
Tetapi saya akan menunjukkan kepada Anda bagaimana konsumen dapat diatur:
use App\Messages\DaemonLogMessage; use Symfony\Component\Messenger\Handler\HandlerDescriptor; use Symfony\Component\Messenger\Handler\HandlersLocator; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; use Symfony\Component\Messenger\Middleware\SendMessageMiddleware; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; require_once __DIR__ . '/../vendor/autoload.php'; $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 \Symfony\Component\Messenger\Worker($receivers, $bus, $container->get(CONTAINER_EVENT_DISPATCHER)); $w->run();
Menggunakan infrastruktur ini dalam suatu aplikasi
Setelah menerapkan bus di backend saya, saya memilih langkah-langkah individu dari tim sinkron lama dan membuat penangan terpisah, yang masing-masing terlibat dalam bisnisnya sendiri.
Pipa untuk menambahkan situs baru ke database adalah sebagai berikut:

Dan tepat setelah itu menjadi lebih mudah bagi saya untuk menambahkan fungsionalitas baru, misalnya, mengekstraksi dan mem-parsing Rss. Karena Karena proses ini juga memerlukan konten sumber, penangan-ekstraktor dari tautan rss, serta WebsiteIndexHistoryPersistor, berlangganan pesan "Content / HtmlContent", memprosesnya, dan meneruskan pesan yang diinginkan pada pipeline lebih lanjut.

Pada akhirnya, ternyata beberapa setan, yang masing-masing hanya memiliki koneksi ke sumber daya yang diperlukan. Sebagai contoh, daemon
perayap berisi semua penangan yang perlu pergi ke Internet untuk konten, dan daemon
persister menjaga koneksi ke database.
Sekarang, alih-alih memilih dari database, id yang diperlukan setelah dimasukkan oleh persister hanya dilewatkan melalui bus ke semua penangan yang tertarik.