Pengalaman dalam mengembangkan layanan Alat Pengembalian Uang dengan API asinkron di Kafka

Apa yang dapat membuat perusahaan besar seperti Lamoda dengan proses yang efisien dan puluhan layanan yang saling berhubungan secara signifikan mengubah pendekatan? Motivasinya bisa sangat berbeda: dari legislatif ke keinginan yang melekat dalam semua programmer untuk percobaan.

Tetapi ini tidak berarti bahwa seseorang tidak dapat mengandalkan manfaat tambahan. Apa sebenarnya yang bisa dimenangkan jika Anda mengimplementasikan API berbasis-acara di Kafka, Sergey Zaika ( fewald ) akan memberi tahu. Tentang boneka benjolan dan penemuan menarik, juga, pasti akan ada - percobaan tidak dapat dilakukan tanpa mereka.



Penafian: Artikel ini didasarkan pada bahan-bahan mitap yang diadakan Sergey pada November 2018 pada HighLoad ++. Pengalaman langsung Lamoda dengan Kafka menarik perhatian pendengar tidak kurang dari laporan jadwal lainnya. Tampaknya bagi kami bahwa ini adalah contoh yang bagus dari fakta bahwa selalu mungkin dan perlu untuk menemukan orang yang berpikiran sama, dan penyelenggara HighLoad ++ akan terus berusaha menciptakan suasana yang kondusif untuk ini.

Tentang prosesnya


Lamoda adalah platform e-commerce besar yang memiliki pusat kontak sendiri, layanan pengiriman (dan banyak yang afiliasi), studio foto, gudang besar dan semuanya bekerja pada perangkat lunaknya. Ada puluhan metode pembayaran, mitra B2B yang dapat menggunakan sebagian atau semua layanan ini dan ingin mengetahui informasi terbaru tentang produk mereka. Selain itu, Lamoda beroperasi di tiga negara selain Federasi Rusia dan semuanya sedikit berbeda di sana. Secara total, mungkin ada lebih dari seratus cara untuk mengonfigurasi pesanan baru, yang harus diproses dengan caranya sendiri. Semua ini bekerja dengan bantuan puluhan layanan yang terkadang berkomunikasi dengan cara yang tidak terlihat. Ada juga sistem pusat yang tanggung jawab utamanya adalah status pesanan. Kami memanggilnya BOB, saya bekerja dengannya.

Alat Pengembalian Uang dengan API yang digerakkan oleh peristiwa


Kata yang digerakkan oleh peristiwa cukup basi, sedikit lebih jauh kita akan mendefinisikan secara lebih rinci apa yang dimaksud dengan ini. Saya akan mulai dengan konteks di mana kami memutuskan untuk mencoba pendekatan API berbasis-event Kafka.



Di toko mana pun, selain pesanan yang dibayar pelanggan, ada saatnya toko diharuskan mengembalikan uang, karena produk itu tidak sesuai dengan pelanggan. Proses yang relatif singkat ini: kami mengklarifikasi informasi, jika ada kebutuhan seperti itu, dan mentransfer uang.

Tetapi pengembaliannya rumit karena perubahan dalam undang-undang, dan kami harus menerapkan layanan mikro terpisah untuk itu.



Motivasi kami:

  1. Hukum FZ-54 - secara singkat, hukum mengharuskan Anda untuk melaporkan ke kantor pajak tentang setiap transaksi moneter, apakah itu pengembalian uang atau tanda terima, dalam SLA yang agak singkat dalam beberapa menit. Kami, sebagai e-commerce, melakukan beberapa operasi. Secara teknis, ini berarti tanggung jawab baru (dan karena itu layanan baru) dan peningkatan dalam semua sistem yang terlibat.
  2. BOB split - proyek internal perusahaan untuk menyingkirkan BOB dari sejumlah besar tanggung jawab non-inti dan mengurangi kompleksitas keseluruhannya.



Diagram ini menggambarkan sistem Lamoda utama. Sekarang kebanyakan dari mereka lebih seperti konstelasi 5-10 layanan mikro di sekitar monolit yang menurun . Mereka tumbuh lambat, tetapi kami mencoba untuk membuatnya lebih kecil, karena menakutkan untuk menyebarkan fragmen yang disorot di tengah - itu tidak dapat dibiarkan jatuh. Semua pertukaran (panah) yang kami terpaksa pesan dan berbaring pada kenyataan bahwa salah satu dari mereka mungkin tidak tersedia.

Ada juga banyak pertukaran di BOB: pembayaran, pengiriman, sistem pemberitahuan, dll.

Secara teknis, BOB adalah:

  • ~ 150 ribu baris kode + ~ 100 ribu baris tes;
  • php7.2 + Zend 1 & Komponen Symfony 3;
  • > 100 API & ~ 50 integrasi keluar;
  • 4 negara dengan logika bisnis mereka sendiri.

Menyebarkan BOB itu mahal dan menyakitkan, jumlah kode dan tugas-tugas yang diselesaikan sedemikian rupa sehingga tidak ada yang bisa memasukkannya ke dalam kepala mereka. Secara umum, ada banyak alasan untuk menyederhanakannya.

Proses pengembalian


Awalnya, dua sistem terlibat dalam proses: BOB dan Pembayaran. Sekarang dua lagi yang muncul:

  • Layanan Fiskalisasi, yang akan menangani masalah fiskalisasi dan komunikasi dengan layanan eksternal.
  • Alat Pengembalian Uang, di mana pertukaran baru dilakukan begitu saja agar tidak mengembang BOB.

Sekarang prosesnya terlihat seperti ini:



  1. BOB menerima permintaan pengembalian dana.
  2. BOB berbicara tentang Alat Pengembalian Uang ini.
  3. Alat Pengembalian Uang mengatakan Pembayaran: "Dapatkan uang kembali."
  4. Pembayaran mengembalikan uang.
  5. Alat Pengembalian Dana dan BOB menyinkronkan status satu sama lain, karena untuk saat ini keduanya memerlukannya. Kami belum siap untuk sepenuhnya beralih ke Alat Pengembalian Uang, karena BOB memiliki UI, laporan akuntansi, dan umumnya banyak data yang tidak dapat Anda transfer dengan mudah. Kita harus duduk di dua kursi.
  6. Permintaan fiskalisasi pergi.

Sebagai hasilnya, kami membuat semacam bis acara di Kafka - sebuah bis acara, tempat semuanya dimulai. Hore, sekarang kita memiliki satu titik kegagalan (sarkasme).



Pro dan kontra cukup jelas. Kami membuat bus, jadi sekarang semua layanan bergantung padanya. Ini menyederhanakan desain, tetapi memperkenalkan satu titik kegagalan ke dalam sistem. Kafka akan jatuh, prosesnya akan naik.

Apa itu API yang didorong oleh peristiwa


Jawaban yang bagus untuk pertanyaan ini ada dalam laporan oleh Martin Fowler (GOTO 2017) "The Many Arti of Event-Driven Architecture" .

Secara singkat, apa yang kami lakukan:

  1. Membungkus semua pertukaran asinkron melalui penyimpanan acara . Alih-alih memberi tahu setiap konsumen yang tertarik tentang perubahan status melalui jaringan, kami menulis acara perubahan status ke repositori terpusat, dan konsumen yang tertarik dengan topik membaca semua yang muncul dari sana.
  2. Suatu peristiwa dalam hal ini adalah pemberitahuan ( notifikasi ) bahwa ada sesuatu yang berubah di suatu tempat. Misalnya, status pesanan telah berubah. Seorang konsumen yang tertarik pada beberapa jenis data yang menyertai perubahan status dan yang tidak ada dalam pemberitahuan dapat mengetahui status mereka sendiri.
  3. Opsi maksimum adalah sumber acara lengkap, transfer negara , di mana acara berisi semua informasi yang diperlukan untuk diproses: dari mana dan ke status apa Anda beralih, bagaimana tepatnya data berubah, dll. Satu-satunya pertanyaan adalah apakah dan berapa banyak informasi yang Anda mampu untuk simpan.

Sebagai bagian dari peluncuran Alat Pengembalian Uang, kami menggunakan opsi ketiga. Ini menyederhanakan pemrosesan peristiwa, karena itu tidak perlu untuk mendapatkan informasi rinci, ditambah itu mengecualikan skenario ketika setiap peristiwa baru menghasilkan lonjakan klarifikasi permintaan-mendapatkan dari konsumen.

Layanan Alat Pengembalian Uang tidak dimuat , jadi Kafka lebih mirip tes pena daripada keharusan. Saya tidak berpikir bahwa jika layanan pengembalian dana menjadi proyek yang banyak, bisnis akan bahagia.

Async exchange SEBAGAIMANA ADANYA


Untuk pertukaran asinkron, departemen PHP biasanya menggunakan RabbitMQ. Kami mengumpulkan data untuk permintaan, memasukkannya ke dalam antrian, dan konsumen dari layanan yang sama membacanya dan mengirimnya (atau tidak mengirimnya). Untuk API itu sendiri, Lamoda aktif menggunakan Swagger. Kami merancang API, mendeskripsikannya dalam Swagger, menghasilkan kode klien dan server. Kami juga menggunakan JSON RPC 2.0 yang sedikit canggih.

Di sana-sini, esb bus digunakan, seseorang tinggal di activeMQ, tetapi, secara umum, RabbitMQ adalah standarnya .

Pertukaran Async MENJADI


Ketika merancang pertukaran melalui bus peristiwa, analogi dilacak. Kami juga menggambarkan pertukaran data di masa mendatang melalui deskripsi struktur acara. Format yaml, pembuatan kode harus dilakukan sendiri, generator membuat DTO sesuai dengan spesifikasi dan mengajari klien dan server cara bekerja dengannya. Generasi masuk ke dua bahasa - golang dan php . Ini membuat perpustakaan konsisten. Generator ditulis dalam golang, yang menerima nama gogi.

Pengadaan-acara di Kafka adalah hal yang biasa. Ada solusi dari versi perusahaan utama Kafka Confluent, ada nakadi , solusi dari "saudara" kami di wilayah domain Zalando. Motivasi kami untuk memulai dengan vanilla Kafka adalah membiarkan solusinya gratis sampai kami akhirnya memutuskan apakah akan menggunakannya di mana-mana, dan juga meninggalkan ruang untuk manuver dan perbaikan: kami ingin dukungan untuk JSON RPC 2.0 kami , generator untuk dua bahasa, dan melihat apa lagi.

Sungguh ironis bahwa bahkan dalam kasus bahagia seperti itu, ketika ada bisnis serupa dengan Zalando, yang membuat keputusan serupa, kita tidak dapat menggunakannya secara efektif.

Secara arsitektur, pada saat startup, polanya adalah sebagai berikut: membaca langsung dari Kafka, tetapi menulis hanya melalui peristiwa-bus. Ada banyak yang siap dibaca di Kafka: broker, penyeimbang dan lebih atau kurang siap untuk penskalaan horizontal, saya ingin menyimpannya. Catatannya, kami ingin membungkus satu Gateway alias Events-bus, dan itulah sebabnya.

Acara-bus


Atau bis acara. Ini hanya gateway http stateless yang mengambil beberapa peran penting:

  • Validasi produksi - kami memverifikasi bahwa acara memenuhi spesifikasi kami.
  • Suatu sistem master peristiwa , yaitu, itu adalah sistem utama dan satu-satunya di perusahaan yang menjawab pertanyaan peristiwa mana dengan struktur mana yang dianggap sah. Validasi hanya mencakup tipe data dan enums untuk spesifikasi konten yang ketat.
  • Fungsi hash untuk sharding - struktur pesan Kafka adalah nilai-kunci, dan di sini dihitung oleh hash dari kunci tempat meletakkannya.

Mengapa


Kami bekerja di perusahaan besar dengan proses yang efisien. Mengapa mengubah sesuatu? Ini adalah eksperimen , dan kami berharap mendapatkan beberapa manfaat.

Pertukaran 1: n + 1 (satu ke banyak)


Dengan Kafka, sangat mudah untuk menghubungkan konsumen baru ke API.

Misalkan Anda memiliki direktori yang perlu selalu diperbarui di beberapa sistem sekaligus (dan di beberapa yang baru). Sebelumnya, kami menemukan bundel yang mengimplementasikan set-API, dan alamat konsumen dilaporkan ke sistem master. Sekarang sistem master mengirimkan pembaruan ke topik, dan semua orang yang tertarik membaca. Sebuah sistem baru telah muncul - mereka menandatanganinya pada topik. Ya, juga bundel, tetapi lebih sederhana.

Dalam hal alat pengembalian uang, yang merupakan bagian dari BOB, kami merasa nyaman untuk menyinkronkannya melalui Kafka. Pembayaran mengatakan bahwa mereka mengembalikan uang itu: BOB, RT mengetahuinya, mengubah status mereka, Layanan Fiskalisasi mengetahuinya dan mengeluarkan cek.



Kami memiliki rencana untuk membuat Layanan Pemberitahuan tunggal, yang akan memberi tahu klien tentang berita tentang pesanan / pengembaliannya. Sekarang tanggung jawab ini tersebar di antara sistem. Cukup bagi kami untuk mengajarkan Layanan Notifikasi untuk menangkap dan menanggapi informasi yang relevan dari Kafka (dan menonaktifkan notifikasi ini di sistem lain). Tidak diperlukan pertukaran langsung baru.

Didorong data


Informasi antar sistem menjadi transparan - tidak peduli seberapa berdarah perusahaan yang Anda miliki dan seberapa bengkaknya jaminan simpanan Anda. Lamoda memiliki departemen Analisis Data yang mengumpulkan data pada sistem dan menempatkannya dalam bentuk yang dapat digunakan kembali, baik untuk bisnis maupun untuk sistem cerdas. Kafka memungkinkan Anda memberi mereka banyak data dengan cepat dan terus memperbarui informasi ini.

Log replikasi


Pesan tidak hilang setelah membaca, seperti pada RabbitMQ. Saat acara berisi informasi yang cukup untuk diproses, kami memiliki riwayat perubahan terbaru pada objek, dan, jika diinginkan, kemampuan untuk menerapkan perubahan ini.

Periode penyimpanan log replikasi tergantung pada intensitas penulisan topik ini. Kafka memungkinkan Anda untuk secara fleksibel menetapkan batas waktu penyimpanan dan volume data. Untuk topik intensif, penting bahwa semua konsumen punya waktu untuk membaca informasi sebelum menghilang, bahkan dalam kasus ketidakmampuan operasi jangka pendek. Biasanya ternyata untuk menyimpan data selama satuan hari , yang cukup untuk mendukung.



Kemudian sedikit menceritakan kembali dokumentasi, untuk mereka yang tidak terbiasa dengan Kafka (gambar juga dari dokumentasi)

Ada antrian di AMQP: kami menulis pesan ke antrian untuk konsumen. Sebagai aturan, satu antrian diproses oleh satu sistem dengan logika bisnis yang sama. Jika Anda perlu memberi tahu beberapa sistem, Anda dapat mengajarkan aplikasi untuk menulis dalam beberapa antrian atau mengkonfigurasi pertukaran dengan mekanisme fanout, yang dengan sendirinya mengkloningnya.

Kafka memiliki abstraksi topik serupa di mana Anda menulis pesan, tetapi mereka tidak hilang setelah membaca. Secara default, ketika Anda terhubung ke Kafka, Anda menerima semua pesan, dan pada saat yang sama ada kesempatan untuk menyimpan tempat di mana Anda tinggalkan. Artinya, Anda membaca secara berurutan, Anda tidak dapat menandai pesan sebagai sudah dibaca, tetapi simpan id, yang kemudian dilanjutkan membaca. Id yang Anda hentikan disebut offset, dan mekanisme komit offset.

Dengan demikian, logika yang berbeda dapat diimplementasikan. Misalnya, kami memiliki BOB dalam 4 contoh untuk berbagai negara - Lamoda ada di Rusia, Kazakhstan, Ukraina, Belarus. Karena mereka dikerahkan secara terpisah, mereka memiliki sedikit konfigurasi sendiri dan logika bisnis mereka sendiri. Dalam pesan tersebut kami menunjukkan negara mana yang dimaksud. Setiap konsumen BOB di masing-masing negara membaca dengan groupId yang berbeda, dan jika pesan itu tidak berlaku untuknya, lewati saja, mis. segera lakukan offset +1. Jika topik yang sama dibaca oleh Layanan Pembayaran kami, maka topik ini dilakukan dengan grup terpisah, dan oleh karena itu offset tidak tumpang tindih.

Persyaratan Acara:

  • Kelengkapan data. Saya berharap ada cukup data di acara tersebut sehingga bisa diproses.

  • Integritas Kami mendelegasikan bus Acara untuk memverifikasi bahwa acara tersebut konsisten dan dapat menanganinya.
  • Ketertiban itu penting. Dalam kasus pengembalian, kami dipaksa untuk bekerja dengan sejarah. Dengan notifikasi, pesanan tidak penting, jika notifikasi homogen, email akan tetap sama, tidak peduli order mana yang lebih dulu. Dalam kasus pengembalian, ada proses yang jelas, jika Anda mengubah pesanan, maka akan ada pengecualian, pengembalian dana tidak akan dibuat atau diproses - kami akan berakhir dalam status yang berbeda.
  • Koherensi Kami memiliki repositori, dan sekarang alih-alih API kami membuat acara. Kami membutuhkan cara untuk dengan cepat dan murah mentransfer informasi tentang acara baru dan perubahan yang sudah ada ke layanan kami. Ini dicapai dengan menggunakan spesifikasi umum dalam repositori git dan generator kode terpisah. Oleh karena itu, klien dan server di berbagai layanan dikoordinasikan dengan kami.

Kafka di Lamoda


Kami memiliki tiga instalasi Kafka:

  1. Log
  2. R&D;
  3. Acara-bus.

Hari ini kita hanya berbicara tentang poin terakhir. Di event-bus, kami tidak memiliki instalasi yang sangat besar - 3 broker (server) dan total 27 topik. Sebagai aturan, satu topik adalah satu proses. Tetapi ini adalah saat yang sulit, dan sekarang kita akan menyentuhnya.



Di atas adalah grafik rps. Proses pengembalian dana ditandai dengan garis pirus (ya, yang terletak di sumbu X), dan merah muda adalah proses pembaruan konten.

Katalog Lamoda berisi jutaan produk, dengan data yang diperbarui setiap saat. Beberapa koleksi keluar dari mode, yang baru dirilis bukan mereka, model baru terus muncul dalam katalog. Kami mencoba memprediksi apa yang akan menarik bagi pelanggan kami besok, jadi kami terus membeli barang baru, memotretnya, dan memperbarui jendela.

Puncak merah muda adalah pembaruan produk, yaitu perubahan dalam produk. Dapat dilihat bahwa orang-orang itu mengambil gambar, mengambil gambar, dan sekali lagi! - mengunduh paket acara.

Lamoda Events menggunakan case


Kami menggunakan arsitektur yang dibangun untuk operasi tersebut:

  • Pelacakan status pengembalian : ajakan untuk bertindak dan pelacakan status dari semua sistem yang terlibat. Pembayaran, status, fiskalisasi, notifikasi. Di sini kami mencoba pendekatannya, membuat alat, mengumpulkan semua bug, menulis dokumentasi dan memberi tahu kolega bagaimana cara menggunakannya.
  • Memperbarui kartu produk: konfigurasi, meta-data, karakteristik. Satu sistem membaca (yang menampilkan), dan beberapa menulis.
  • Email, push dan sms : pesanan dikumpulkan, pesanan telah tiba, pengembalian telah diterima, dll., Banyak dari mereka.
  • Stok, pembaruan gudang - pembaruan kuantitatif barang, hanya nomor: tanda terima di gudang, kembalikan. Semua sistem yang terkait dengan reservasi barang harus beroperasi dengan data yang paling relevan. Sekarang sistem upgrade saluran cukup rumit, Kafka akan menyederhanakannya.
  • Analisis Data (departemen R&D), alat-ML, analitik, statistik. Kami ingin informasinya transparan - untuk Kafka ini sangat cocok.

Sekarang, bagian yang lebih menarik adalah tentang kerucut yang diisi dan penemuan menarik yang terjadi selama enam bulan.

Masalah desain


Misalkan kita ingin membuat hal baru - misalnya, mentransfer seluruh proses pengiriman ke Kafka. Bagian dari proses sekarang sedang dilaksanakan dalam Pemrosesan Pesanan di BOB. Di belakang transfer pesanan ke layanan pengiriman, transfer ke gudang perantara, dll. Ada model status. Ada satu keseluruhan monolit, bahkan dua, ditambah banyak API pengiriman. Mereka tahu lebih banyak tentang pengiriman.

Ini tampaknya area yang serupa, tetapi untuk Pemrosesan Pesanan di BOB dan untuk sistem pengiriman, statusnya berbeda. Misalnya, beberapa layanan kurir tidak mengirim status perantara, tetapi hanya yang terakhir: "dikirim" atau "hilang". Yang lain, sebaliknya, melaporkan pergerakan barang dengan sangat rinci. Setiap orang memiliki aturan validasi sendiri: untuk seseorang, email valid, sehingga akan diproses; untuk orang lain, ini tidak valid, tetapi pesanan masih akan diproses, karena ada telepon untuk komunikasi, dan seseorang akan mengatakan bahwa pesanan seperti itu tidak akan diproses sama sekali.

Aliran data


Dalam kasus Kafka, muncul pertanyaan untuk mengatur aliran data. Tugas ini terhubung dengan pilihan strategi untuk beberapa poin, kita akan membahas semuanya.

Dalam satu topik atau berbeda?


Kami memiliki spesifikasi acara. Di BOB, kami menulis bahwa pesanan seperti itu harus dikirimkan, dan menunjukkan: nomor pesanan, komposisinya, beberapa SKU dan kode batang, dll. Ketika barang tiba di gudang, pengiriman akan dapat menerima status, stempel waktu, dan semua yang diperlukan. Tetapi selanjutnya kami ingin menerima pembaruan tentang data ini dalam BOB. Kami dihadapkan dengan proses sebaliknya untuk mendapatkan data dari pengiriman. Apakah ini acara yang sama? Atau apakah ini merupakan pertukaran terpisah yang layak mendapatkan topik terpisah?

Kemungkinan besar, mereka akan sangat mirip, dan godaan untuk membuat satu topik tidak masuk akal, karena topik yang terpisah adalah konsumen yang terpisah, konfigurasi yang terpisah, generasi yang terpisah dari semua ini. Tapi bukan fakta.

Bidang baru atau acara baru?


Tetapi jika Anda menggunakan acara yang sama, maka masalah lain muncul. Misalnya, tidak semua sistem pengiriman dapat menghasilkan DTO yang dapat menghasilkan BOB. Kami mengirimi mereka id, tetapi mereka tidak menyimpannya, karena mereka tidak membutuhkannya, dan dari sudut pandang memulai proses event-bus, bidang ini diperlukan.

Jika kami memperkenalkan aturan untuk bus peristiwa bahwa bidang ini wajib diisi, maka kami terpaksa menetapkan aturan validasi tambahan di BOB atau di pengendali kejadian awal. Validasi mulai merayap di layanan - tidak nyaman.

— . , - , , , , . — . — , . JSON .

refunds . -, refund update, type, , update . «» , , type.


Kafka Avro , Confluent. . replication log, «». , , : , . , , , .

partitions


Kafka partitions. , , , .

Kafka . partition, . . , , , . , , , Kafka partition, Kafka — , .

Kafka ? ( JSON) key. -, , partition .

refunds , partition, , . - , partition.

Events vs commands


, . Event — : , - - (something_happened), , item refund. - , «item » refund , « refund» - .

, , — , - . something_happened (item_canceled, refund_refunded), something_should_be_done. , item .

, , . , . , do_something. , - ; , ; , -, - . , do_something, , .



RabbitMQ, , http, response — , . Kafka, , Kafka, , , .

, - , - . , , - . , «item_ready_to_refund», , refund , , «money_refunded». , .

Nuansa


: , - , , . , offset , .

, , . , events-bus, , PostgreSQL, MySQL UNSIGNED INT, PostgreSQL INT. , Id . Symfony . , , , , offset, , . , Symfony , offset.

- — , Kafka , . . .

Kafka tooling offset. , — , , redeployments. Kafka tooling offset, .

— replication log vs rdkafka.so — . PHP, PHP, , , Kafka rdkafka.so, - . , , , - . , .

partitions, consumers >= topic partitions . , . , partitions. , partition, 20 , , . , , partitions.

Pemantauan


, , , , .

, , , , , , . Kafka , . , .



, , , events-bus , . , Refund Tool , BOB - ( ).



consumer-group lag. , . , 0, . Kafka , .

Burrow , Kafka. API consumer-group , . Failed warning, , — , . , .



API. bob-live-fifa, partition refund.update.v1, , lag 0 — offset -.



updated_at SLA (stuck) . , , . Cron, , 5 refund ( ), - , . Cron, , 0, .

, , :

  • ;
  • ;
  • .
, — API Kafka, .
-, HighLoad++ , , .
-, KnowledgeConf . , 26 , .
PHP Russia ++ ( DevOpsConf ) — , .

Source: https://habr.com/ru/post/id445424/


All Articles