Apache Kafka dan RabbitMQ: semantik dan jaminan pengiriman pesan



Kami menyiapkan terjemahan bagian selanjutnya dari artikel berseri, yang membandingkan fungsionalitas Apache Kafka dan RabbitMQ. Publikasi ini membahas tentang semantik dan jaminan pengiriman pesan. Harap dicatat bahwa penulis memperhitungkan Kafka hingga dan termasuk versi 0.10, dan persis sekali muncul di versi 0.11. Namun demikian, artikel tersebut tetap relevan dan penuh dengan poin-poin berguna dari sudut pandang praktis.
Bagian sebelumnya: pertama , kedua .

Baik RabbitMQ dan Kafka menawarkan jaminan pengiriman pesan yang andal. Kedua platform menawarkan jaminan pada prinsip "pengiriman paling banyak satu kali" dan "pengiriman setidaknya satu kali", tetapi dengan prinsip "pengiriman ketat satu kali", jaminan Kafka berlaku sesuai dengan skenario yang sangat terbatas.

Pertama, mari kita cari tahu apa arti jaminan ini:

  • Pengiriman paling banyak. Ini berarti bahwa pesan tidak dapat dikirim lebih dari sekali. Namun, pesannya mungkin hilang.
  • Pengiriman minimal satu kali. Ini berarti bahwa pesan itu tidak akan pernah hilang. Dalam hal ini, pesan dapat dikirim lebih dari sekali.
  • Pengiriman tepat sekali. Cawan suci sistem pesan. Semua pesan dikirim sekali saja.

Kata “pengiriman” di sini kemungkinan bukan istilah yang tepat. Akan lebih akurat untuk mengatakan "pemrosesan". Dalam kasus apa pun, yang menarik bagi kami sekarang adalah apakah konsumen dapat memproses pesan dan dengan prinsip apa ini terjadi: "tidak lebih dari satu", "setidaknya satu" atau "satu kali". Tetapi kata "pemrosesan" mempersulit persepsi, dan ungkapan "Pengiriman dengan prinsip 'sekali saja'" dalam hal ini tidak akan menjadi definisi yang akurat, karena mungkin perlu untuk menyampaikan pesan dua kali agar dapat memprosesnya dengan baik sekali. Jika penerima terputus selama pemrosesan, pesan harus dikirim lagi ke penerima baru.

Yang kedua. Membahas masalah pemrosesan pesan, kita sampai pada topik kegagalan parsial, yang merupakan sakit kepala bagi pengembang. Dalam proses pemrosesan pesan ada beberapa tahapan. Ini terdiri dari sesi komunikasi antara aplikasi dan sistem pesan di awal dan di akhir dan aplikasi itu sendiri bekerja dengan data di tengah. Skenario kegagalan aplikasi sebagian harus ditangani oleh aplikasi itu sendiri. Jika operasi yang dilakukan sepenuhnya transaksional dan hasilnya dirumuskan berdasarkan prinsip "semua atau tidak sama sekali," kegagalan sebagian dalam logika aplikasi dapat dihindari. Tetapi seringkali, banyak tahapan memasukkan interaksi dengan sistem lain, di mana transaksionalitas tidak mungkin. Jika kami menyertakan dalam interaksi hubungan antara sistem pengiriman pesan, aplikasi, cache, dan basis data, dapatkah kami menjamin pemrosesan berdasarkan "hanya sekali"? Jawabannya adalah tidak.

Strategi "hanya sekali" terbatas pada skenario di mana satu-satunya penerima pesan yang diproses adalah platform pengiriman pesan itu sendiri, dan platform ini sendiri menyediakan transaksi lengkap. Dalam skenario terbatas ini, Anda dapat memproses pesan, menulisnya, mengirim sinyal bahwa pesan itu telah diproses sebagai bagian dari transaksi yang dilakukan dengan prinsip "semua atau tidak sama sekali." Ini disediakan oleh perpustakaan Kafka Streams.

Tetapi jika pemrosesan pesan selalu idempoten, Anda dapat menghindari kebutuhan untuk menerapkan strategi "hanya sekali" melalui transaksi. Jika pemrosesan akhir pesan idempoten, Anda bisa khawatir menerima duplikat. Tetapi tidak semua tindakan dapat diimplementasikan secara idempoten.

Peringatan ujung ke ujung

Apa yang tidak terwakili dalam perangkat apa pun dari semua sistem pengiriman pesan yang saya gunakan adalah konfirmasi end-to-end. Mengingat bahwa sebuah pesan dapat di-antri di RabbitMQ, notifikasi ujung ke ujung tidak masuk akal. Di Kafka, sama halnya, beberapa kelompok penerima yang berbeda dapat membaca informasi secara bersamaan dari satu topik. Dalam pengalaman saya, peringatan end-to-end adalah apa yang orang-orang yang baru mengenal konsep olahpesan sering meminta. Dalam kasus seperti itu, yang terbaik adalah segera menjelaskan bahwa ini tidak mungkin.

Rantai tanggung jawab

Secara umum, sumber pesan tidak dapat mengetahui bahwa pesan mereka dikirim ke penerima. Mereka hanya bisa tahu bahwa sistem pesan telah menerima pesan mereka dan telah mengambil tanggung jawab untuk memastikan penyimpanan dan pengiriman yang aman. Ada rantai tanggung jawab, yang dimulai dengan sumbernya, melewati sistem pesan dan berakhir di penerima. Setiap orang harus memenuhi tugasnya dengan benar dan menyampaikan pesan dengan jelas ke yang berikutnya. Ini berarti bahwa Anda, sebagai pengembang, harus merancang aplikasi Anda dengan benar untuk mencegah kehilangan atau penyalahgunaan pesan saat mereka berada di bawah kendali Anda.

Prosedur Transfer Pesan

Artikel ini terutama ditujukan untuk bagaimana setiap platform menyediakan strategi pengiriman "setidaknya satu" dan "tidak lebih dari satu". Namun masih ada pesanan pengiriman pesan. Pada bagian-bagian sebelumnya dari seri ini, saya menulis tentang urutan pengiriman pesan dan urutan pemrosesannya, dan saya menyarankan Anda untuk merujuk ke bagian-bagian ini.

Singkatnya, baik RabbitMQ dan Kafka memberikan jaminan masuk pertama keluar pertama (FIFO). RabbitMQ mempertahankan pesanan ini di tingkat antrian, dan Kafka di tingkat segmentasi. Implikasi dari keputusan desain seperti itu dibahas dalam artikel sebelumnya.

Jaminan pengiriman dalam RabbitMQ

Jaminan pengiriman disediakan:

  • keandalan pesan - pesan tidak akan hilang saat disimpan di RabbitMQ;
  • pemberitahuan pesan - RabbitMQ bertukar sinyal dengan pengirim dan penerima.

Elemen Keandalan


Mirroring Antrian

Antrian dapat dicerminkan (direplikasi) pada banyak node (server). Setiap antrian memiliki antrian utama di salah satu node. Misalnya, ada tiga node, 10 antrian, dan dua replika per antrian. 10 antrian kontrol dan 20 replika akan didistribusikan di tiga node. Distribusi antrian kontrol oleh node dapat dikonfigurasi. Dalam kasus pembekuan simpul:

  • alih-alih setiap antrian utama pada simpul yang digantung, sebuah replika dari antrian ini disediakan pada simpul lain;
  • replika baru dibuat pada node lain untuk menggantikan replika yang hilang pada node keluar, sehingga mendukung faktor replikasi.

Masalah toleransi kesalahan akan dibahas di bagian artikel selanjutnya.

Antrian Tepercaya

Ada dua jenis antrian di RabbitMQ: dapat diandalkan dan tidak dapat diandalkan. Antrian yang dapat dipercaya ditulis ke disk dan disimpan jika simpul reboot. Ketika node mulai, mereka ditimpa.

Posting persisten

Jika antriannya dapat diandalkan, ini tidak berarti bahwa pesannya disimpan ketika node di-reboot. Hanya pesan yang ditandai persisten oleh pengirim yang akan dipulihkan.

Saat mengerjakan RabbitMQ, semakin dapat diandalkan pesannya, semakin rendah kinerjanya. Jika ada aliran peristiwa waktu nyata dan tidak penting untuk kehilangan beberapa dari mereka atau interval waktu kecil dari aliran, lebih baik untuk tidak menggunakan replikasi antrian dan mengirimkan semua pesan sebagai tidak stabil. Tetapi jika tidak diinginkan untuk kehilangan pesan karena kegagalan fungsi node, lebih baik menggunakan antrian yang dapat diandalkan dengan replikasi dan pesan stabil.

Pemberitahuan Pesan


Olahpesan

Pesan mungkin hilang atau diduplikasi selama pengiriman. Itu tergantung pada perilaku pengirim.

"Ditembak dan dilupakan"

Sumber dapat memutuskan untuk tidak meminta konfirmasi dari penerima (pemberitahuan penerimaan pesan ke pengirim) dan cukup mengirim pesan secara otomatis. Pesan tidak akan digandakan, tetapi mungkin hilang (yang memenuhi strategi "sebagai pengiriman maksimum satu kali").

Konfirmasi kepada Pengirim

Ketika pengirim membuka saluran untuk broker antrian, ia dapat menggunakan saluran yang sama untuk mengirim konfirmasi. Sekarang, sebagai tanggapan terhadap pesan yang diterima, broker antrian harus menyediakan satu dari dua hal:

  • basic.ack. Konfirmasi positif. Pesan diterima, tanggung jawab untuk sekarang terletak pada RabbitMQ;
  • basic.nack Konfirmasi negatif Sesuatu terjadi dan pesan itu tidak diproses. Tanggung jawab untuk itu tetap pada sumbernya. Jika diinginkan, ia dapat mengirim pesan untuk kedua kalinya.

Selain pemberitahuan pengiriman positif dan negatif, pesan basic.return disediakan. Terkadang pengirim tidak hanya tahu bahwa pesan itu tiba di RabbitMQ, tetapi juga bahwa pesan itu benar-benar jatuh ke dalam satu atau lebih antrian. Mungkin terjadi bahwa sumber mengirim pesan ke sistem pertukaran topik, di mana pesan tidak dialihkan ke antrian pengiriman mana pun. Dalam situasi ini, broker hanya membuang pesan. Dalam beberapa skenario, ini normal, pada yang lain, sumber harus tahu apakah pesan telah dibuang, dan melanjutkan lebih lanjut sesuai dengan ini. Anda dapat mengatur bendera "Wajib" untuk setiap pesan, dan jika pesan belum ditentukan dalam antrian pengiriman apa pun, basic.return akan dikembalikan ke pengirim.

Sumber dapat menunggu konfirmasi setelah mengirim setiap pesan, tetapi ini akan sangat mengurangi kinerjanya. Alih-alih, sumber dapat mengirim aliran pesan yang stabil, menetapkan batas jumlah pesan yang tidak diakui. Ketika batas pesan interim tercapai, pengiriman akan dijeda hingga semua konfirmasi diterima.

Sekarang ada banyak pesan yang sedang dalam perjalanan dari pengirim ke RabbitMQ, konfirmasi dikelompokkan menggunakan beberapa bendera untuk meningkatkan kinerja. Semua pesan yang dikirim melalui saluran diberi nilai integer yang meningkat secara monoton, "Nomor Urutan". Pemberitahuan pesan termasuk nomor urut pesan yang sesuai. Dan jika pada saat yang sama nilainya adalah multiple = true, pengirim harus melacak nomor urut pesannya untuk mengetahui pesan mana yang berhasil dikirim dan mana yang tidak. Saya menulis artikel terperinci tentang topik ini.

Berkat konfirmasi, kami menghindari kehilangan pesan dengan cara berikut:

  • mengirim ulang pesan jika pemberitahuan negatif;
  • melanjutkan penyimpanan pesan di suatu tempat jika terjadi pemberitahuan negatif atau basic.return.

Transaksi

Transaksi jarang digunakan di RabbitMQ karena alasan berikut:

  • Jaminan lemah. Jika pesan dialihkan ke beberapa antrian atau memiliki ikon wajib, kontinuitas transaksi tidak akan didukung;
  • Produktivitas rendah.

Sejujurnya, saya tidak pernah menggunakannya, mereka tidak memberikan jaminan tambahan, kecuali untuk konfirmasi kepada pengirim, dan hanya meningkatkan ketidakpastian dalam pertanyaan tentang bagaimana menafsirkan pengakuan penerimaan pesan yang timbul dari penyelesaian transaksi.

Kesalahan komunikasi / saluran

Selain pemberitahuan penerimaan pesan, pengirim harus mengingat kegagalan alat komunikasi dan broker. Kedua faktor ini menyebabkan hilangnya saluran komunikasi. Dengan hilangnya saluran, kesempatan untuk menerima pemberitahuan penerimaan pesan yang belum diterima hilang. Di sini, pengirim harus memilih antara risiko kehilangan pesan dan risiko duplikasi.

Kegagalan broker dapat terjadi ketika pesan berada di buffer sistem operasi atau pra-diproses, dan kemudian pesan akan hilang. Atau, mungkin pesannya sudah diantrikan, tetapi pialang pesan meninggal sebelum mengirim konfirmasi. Dalam hal ini, pesan akan terkirim dengan sukses.

Demikian pula, kegagalan alat komunikasi mempengaruhi situasi. Apakah kegagalan terjadi selama pengiriman pesan? Atau setelah pesan antri, tetapi sebelum menerima pemberitahuan positif?

Pengirim tidak dapat menentukan ini, jadi ia harus memilih salah satu dari opsi berikut:

  • Jangan teruskan pesan, membuat risiko kehilangannya;
  • kirim ulang pesan dan buat risiko duplikasi.

Jika banyak pesan pengirim dalam perjalanan, masalahnya menjadi lebih rumit. Satu-satunya hal yang dapat dilakukan pengirim adalah memberi petunjuk kepada penerima dengan menambahkan tajuk khusus ke pesan, yang menunjukkan bahwa pesan sedang dikirim untuk kedua kalinya. Penerima dapat memutuskan untuk memeriksa pesan untuk keberadaan tajuk tersebut dan, jika ditemukan, juga memeriksa pesan yang diterima untuk duplikat (jika pemeriksaan seperti itu belum pernah dilakukan sebelumnya).

Penerima


Ada dua opsi yang tersedia untuk penerima untuk menerima pemberitahuan:

  • tidak ada mode pemberitahuan;
  • mode pemberitahuan manual.

Tidak ada mode pemberitahuan

Dia adalah mode pemberitahuan otomatis. Dan dia berbahaya. Pertama-tama, karena ketika sebuah pesan masuk ke aplikasi Anda, itu dihapus dari antrian. Ini dapat menyebabkan hilangnya pesan jika:

  • koneksi terputus sebelum pesan diterima;
  • pesan masih dalam buffer internal, dan aplikasi dinonaktifkan;
  • pemrosesan pesan gagal.

Selain itu, kami kehilangan mekanisme tekanan balik sebagai alat untuk memonitor kualitas pengiriman pesan. Dengan mengatur mode pengiriman pemberitahuan secara manual, Anda dapat menetapkan prefetch (atau mengatur tingkat layanan yang disediakan, QoS) untuk membatasi jumlah pesan satu kali yang belum dikonfirmasi oleh sistem. Tanpa ini, RabbitMQ mengirim pesan secepat koneksi memungkinkan, dan ini bisa lebih cepat daripada penerima dapat memprosesnya. Akibatnya, buffer penuh dan terjadi kesalahan memori.

Mode pemberitahuan manual

Penerima harus secara manual mengirim pemberitahuan penerimaan setiap pesan. Ia dapat menetapkan prefetch jika jumlah pesan lebih dari satu, dan memproses banyak pesan secara bersamaan. Dia dapat memutuskan untuk mengirim pemberitahuan untuk setiap pesan, atau dia dapat menerapkan beberapa bendera dan mengirim beberapa pemberitahuan sekaligus. Pemberitahuan pengelompokan meningkatkan kinerja.

Ketika penerima membuka saluran, pesan yang melewatinya mengandung parameter Tag Pengiriman, yang nilainya bilangan bulat, secara monoton meningkat. Ini termasuk dalam setiap notifikasi penerimaan dan digunakan sebagai pengidentifikasi pesan.

Pemberitahuan dapat sebagai berikut:

  • basic.ack. Setelah itu, RabbitMQ menghapus pesan dari antrian. Beberapa bendera dapat diterapkan di sini.
  • basic.nack Penerima harus menetapkan bendera untuk memberi tahu RabbitMQ apakah akan mengantri pesan lagi. Saat mengatur ulang pesan pergi ke awal antrian. Dari sana itu dikirim ke penerima lagi (bahkan ke penerima yang sama). Notifikasi basic.nack mendukung beberapa flag.
  • basic.reject. Sama seperti basic.nack, tetapi tidak mendukung banyak flag.

Jadi, semantik basic.ack dan basic.nack dengan requeue = false adalah sama. Kedua operator berarti menghapus pesan dari antrian.

Pertanyaan selanjutnya adalah kapan mengirim pemberitahuan penerimaan. Jika pesan diproses dengan cepat, Anda mungkin ingin mengirim pemberitahuan segera setelah menyelesaikan operasi ini (berhasil atau tidak berhasil). Tetapi jika pesan itu dalam antrian RabbitMQ dan pemrosesan memakan waktu beberapa menit? Mengirim pemberitahuan setelah ini akan bermasalah, karena jika saluran ditutup, semua pesan yang tidak ada pemberitahuan akan dikembalikan ke antrian, dan pengiriman akan dilakukan untuk yang kedua kalinya.

Kesalahan Broker Koneksi / Pesan

Jika koneksi terputus atau terjadi kesalahan pada broker, setelah itu saluran berhenti bekerja, maka semua pesan yang kwitansinya belum dikonfirmasi kembali diantrekan dan diteruskan. Ini bagus karena mencegah kehilangan data, tetapi buruk karena menyebabkan duplikasi yang berlebihan.

Semakin lama penerima memiliki pesan untuk waktu yang lama, tanda terima yang tidak dikonfirmasikan, semakin tinggi risiko penerusan. Ketika sebuah pesan dikirim ulang, RabbitMQ untuk bendera penerusan disetel ke true. Karena itu, penerima setidaknya memiliki indikasi bahwa pesan tersebut mungkin telah diproses.

Idempotensi

Jika idempotensi diperlukan dan menjamin bahwa tidak ada pesan yang hilang, beberapa duplikat cek atau skema idempoten lainnya harus dibangun. Jika memeriksa pesan duplikat terlalu mahal, Anda dapat menerapkan strategi di mana pengirim selalu menambahkan header khusus untuk pesan yang dikirim kembali, dan penerima memeriksa pesan yang diterima untuk keberadaan header seperti itu dan bendera pengiriman ulang.

Kesimpulan


RabbitMQ memberikan jaminan pengiriman pesan jangka panjang yang andal, tetapi ada banyak situasi di mana mereka tidak akan membantu.

Berikut adalah daftar poin yang perlu diingat:

  • Anda harus menggunakan pencerminan antrian, antrian yang dapat diandalkan, pesan persisten, ucapan terima kasih untuk pengirim, tanda konfirmasi, dan pemberitahuan paksa dari penerima jika jaminan yang dapat diandalkan diperlukan dalam strategi “setidaknya pengiriman satu kali”.
  • Jika pengiriman dilakukan sebagai bagian dari strategi “setidaknya satu kali pengiriman”, Anda mungkin perlu menambahkan mekanisme untuk deduplikasi atau idempotensi saat menduplikasi data yang dikirim.
  • Jika masalah kehilangan pesan tidak sepenting masalah kecepatan pengiriman dan skalabilitas tinggi, maka pikirkan sistem tanpa redundansi, tanpa pesan yang persisten dan tanpa pengakuan di sisi sumber. Meskipun demikian, saya lebih suka meninggalkan pemberitahuan paksa dari penerima untuk mengontrol aliran pesan yang diterima dengan mengubah batasan prefetch. Dalam hal ini, Anda harus mengirim pemberitahuan secara berkelompok dan menggunakan bendera "banyak".

Jaminan pengiriman di Kafka

Jaminan pengiriman disediakan:

  • daya tahan pesan - pesan yang disimpan dalam suatu segmen tidak hilang;
  • Pemberitahuan pesan - pertukaran sinyal antara Kafka (dan mungkin repositori Apache Zookeeper) di satu sisi dan sumber / penerima di sisi lain.

Dua kata tentang pengemasan pesan

Salah satu perbedaan antara RabbitMQ dan Kafka adalah penggunaan paket untuk pengiriman pesan.

RabbitMQ menyediakan sesuatu yang mirip dengan pengemasan berkat:

  • Tangguhkan pengiriman setiap pesan X hingga semua pemberitahuan diterima. RabbitMQ biasanya mengelompokkan pemberitahuan menggunakan bendera "banyak".
  • «prefetch» «multiple».

. “multiple”. TCP.

Kafka . , . RabbitMQ, , , . , .

Kafka , , . , . RabbitMQ API , . RabbitMQ .

,



Kafka - , , . . , , , , , .

Kafka (In Sync Replicas, ISR). . , , ( 10 ). , . - , .. . .



, Kafka , , , Kafka .



, Kafka, , :

  • , . Acks=0.
  • . Acks=1
  • . Acks=All

, RabbitMQ. , , ( , ). , .

Kafka . :

  • enable.idempotence “true”,
  • max.in.flight.requests.per.connection 5 ,
  • retries 1 ,
  • acks “all”.

, acks=0/1 , .



, , . ZooKeeper Kafka.

(), , :

  • . . . . , .
  • , . “ ”. , ; , . , 10 , 4 , , , ;
  • , . “ ”. , , , . , 10 , , 4 ;
  • . , .

“ ” Kafka Streams, Java. Java . “ ”, , , . , , “ ” . , , () .

, Kafka Streams, , , “ ”. Kafka: . , . , , , ( ), .



Kafka “--”. . , , .

“ ”, , (, , ). “ ”, , . .

: “ ” ? . , , . . (Last Stable Offset, LSO) — ; “ ” .

Kesimpulan

. , , . , Kafka , .

Untuk meringkas


  • “ ” “ ”.
  • .
  • , . Kafka , .
  • , , .
  • .
  • Kafka , “--”. .
  • Kafka, - , ( ). RabbitMQ .
  • Kafka , RabbitMQ , .

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


All Articles