Belajar Event-Driven Architecture: Queue, Log, dan Outbox
Belajar event-driven architecture: event vs command, queue vs log, at-least-once, consumer idempotent, urutan per partition, backlog, dan outbox pattern.
Oleh Matthews WongDitinjau pada
Poin utama
Event-driven architecture adalah desain tempat service berkomunikasi dengan mempublikasikan event, yaitu catatan bahwa sesuatu sudah terjadi, ke sebuah broker, lalu service lain bereaksi sesuai jadwalnya sendiri. Producer tidak pernah menunggu consumer. Queue memberikan setiap pesan ke satu worker dan menghapusnya setelah di-ack; log menyimpan event, jadi setiap consumer group membaca dengan kecepatannya sendiri. Pengiriman biasanya at-least-once, jadi consumer wajib idempotent.
Setelah pelajaran ini, Anda bisa
- Membedakan event dari command, dan memilih antara queue dan log untuk sebuah alur.
- Menjelaskan kenapa pengiriman at-least-once memaksa consumer menjadi idempotent, dan menjaga urutan event dengan partition key.
- Mempublikasikan event dengan aman lewat outbox pattern, dan membaca consumer lag sebelum backlog berubah menjadi gangguan.
Ikuti diagramnya langkah demi langkah
Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.
Satu event, tiga consumer, satu duplikat
Checkout mempublikasikan satu event OrderPlaced ke log; email dan analytics membacanya dengan kecepatan masing-masing saat stock sedang mati, stock kemudian mengejar dari posisi yang tersimpan, menerima satu event dua kali setelah crash, lalu melewati duplikat itu karena mencatat id event yang sudah diproses.
Tombol: ← → langkah · Home / End · K putar atau jeda
Checkout mempublikasikan event order ke sebuah log. Email, stock, dan analytics masing-masing membacanya lewat consumer group dan posisinya sendiri. Stock mencatat id event yang sudah diprosesnya.
Checkout menambahkan satu event OrderPlaced, evt-42, lalu lanjut bekerja. Ia tidak tahu siapa yang mendengarkan dan tidak menunggu siapa pun: log menyimpan event itu, dan setiap pembaca mengambilnya nanti.
XADD orders * event OrderPlaced id evt-42Email dan analytics masing-masing membaca evt-42 dengan kecepatannya sendiri dan memajukan posisinya sendiri. Stock sedang mati, jadi posisinya diam di tempat dan event itu menunggu di log.
XREADGROUP GROUP email email-1 STREAMS orders >Tebak langkah berikutnya Order 43 dan 44 masuk saat stock masih mati. Saat stock hidup lagi, apa yang dibacanya? (Hanya order yang masuk setelah ia hidup lagi · Ketiga event yang terlewat, dari yang terlama · Tidak ada, sampai checkout mengirim ulang)Sementara itu order 43 dan 44 sudah masuk. Stock hidup lagi di posisi yang tersimpan dan membaca backlog-nya, 42 sampai 44, secara berurutan. Lag-nya turun dari 3 ke 0.
XINFO GROUPS ordersStock memesan barang untuk evt-44, lalu crash sebelum mengirim ack. Broker tidak bisa tahu apakah pekerjaannya sudah terjadi, jadi evt-44 dikirim ulang: inilah pengiriman at-least-once.
XPENDING orders stock - + 10Tebak langkah berikutnya Stock sudah memesan barang untuk order 44. Apa yang mencegah pemesanan kedua? (Stock melewati id event yang sudah diprosesnya · Broker tidak pernah mengirim event dua kali · Checkout menyadarinya lalu membatalkan duplikat)Stock menemukan evt-44 di daftar id terproses, yang disimpan dalam transaksi yang sama dengan setiap pemesanan. Jadi pekerjaannya dilewati lalu di-ack. Satu pengiriman berlebih, tanpa pemesanan ganda.
INSERT INTO processed_events (event_id) VALUES ('evt-44') ON CONFLICT DO NOTHING; -- 0 rows: already done
Apa itu event-driven architecture?
Event-driven architecture adalah cara menghubungkan service: satu service mempublikasikan event, yaitu catatan bahwa sesuatu sudah terjadi, lalu service lain, berapa pun jumlahnya, bereaksi terhadapnya. Dokumentasi Kafka mendefinisikan event persis seperti itu, sebagai catatan bahwa sesuatu terjadi di dunia nyata atau di bisnis Anda, dan menyebut producer serta consumer benar-benar terpisah dan tidak saling mengenal: producer tidak pernah menunggu consumer. Di antara keduanya ada broker yang menyimpan event dan membagikannya.
Baca selengkapnya (1)Tutup
Bandingkan dengan rantai sinkron di pelajaran 6, tempat checkout memanggil stock, stock memanggil payment, dan satu hop yang lambat memperlambat semua request. Di sini checkout menulis OrderPlaced sekali lalu selesai. Email, stock, dan analytics masing-masing berlangganan, dan service keempat bisa ikut berlangganan bulan depan tanpa ada yang mengubah checkout. Martin Fowler menyebut harganya tepat di samping manfaatnya: event notification membuat coupling rendah dan mudah disiapkan, tetapi alur yang melintasi beberapa event bisa sulit dilihat, karena tidak tertulis eksplisit di kode program mana pun.
Apa beda event dan command?
Event menyatakan fakta yang sudah terjadi, jadi biasanya dinamai dalam bentuk lampau: OrderPlaced, PaymentFailed. Producer-nya tidak meminta apa pun kepada siapa pun dan, dalam kata-kata Fowler, tidak terlalu peduli pada responsnya. Command meminta satu penerima tertentu melakukan sesuatu: SendReceipt, ReserveStock. Command punya pemilik yang diharapkan bertindak, dan pemilik itu boleh menolaknya.
Baca selengkapnya (1)Tutup
Fowler mengingatkan soal event yang dipakai sebagai passive-aggressive command: pengirim berharap penerima melakukan sesuatu dan seharusnya mengirim command, tetapi pesannya dibungkus sebagai event. Kalau checkout mempublikasikan OrderPlaced lalu rusak saat struk tidak terkirim, sebenarnya ia sedang mengirim command, dan ketergantungan itu sebaiknya terlihat jelas sebagai command. Keduanya bisa lewat broker yang sama; bedanya ada pada siapa yang memutuskan langkah berikutnya.
Queue atau log: apa yang terjadi pada pesan setelah dibaca?
Queue klasik, seperti yang diterapkan RabbitMQ, adalah kumpulan pesan berurutan yang menunggu consumer. Consumer di satu queue saling bersaing: secara default RabbitMQ mengirim setiap pesan ke consumer berikutnya secara bergiliran, jadi menambah worker membuat queue lebih cepat habis. Begitu consumer mengirim ack, broker menandai pesan itu untuk dihapus. Queue mengingat pekerjaan yang belum dikerjakan, bukan apa yang sudah terjadi.
Baca selengkapnya (2)Tutup
Log tetap menyimpan event setelah dibaca. Kafka menyimpan event di topic, tidak menghapusnya setelah dikonsumsi, dan baru membuangnya setelah masa retensi topic itu lewat. Kemajuan consumer di setiap partition hanyalah satu angka, yaitu offset event berikutnya yang akan dibaca, dan consumer bisa mundur ke offset lama untuk membaca ulang. Setiap consumer group melihat semua event; di dalam satu group, setiap partition dibaca oleh satu consumer pada satu waktu. RabbitMQ streams dan Redis Streams bekerja dengan cara yang sama, dan RabbitMQ menyebutnya non-destructive consumer semantics.
Jadi pilihlah berdasarkan jenis pesannya. Job yang harus jalan sekali, misalnya membuat satu invoice, cocok untuk queue. Fakta yang dibaca beberapa service, yang mungkin perlu di-replay oleh service baru, atau yang ingin Anda audit nanti, cocok untuk log.
Kenapa consumer bisa menerima event yang sama dua kali?
Karena alternatifnya adalah kehilangan event. Dokumentasi desain Kafka mencantumkan tiga jaminan: at most once, pesan bisa hilang tetapi tidak pernah dikirim ulang; at least once, pesan tidak pernah hilang tetapi bisa dikirim ulang; dan exactly once. Secara default Kafka memberi at least once. Alasannya sederhana: consumer yang membaca, memproses, lalu menyimpan posisinya akan memproses ulang apa pun yang baru selesai tepat sebelum crash. Menyimpan posisi lebih dulu justru menghasilkan at most once, dan pekerjaan itu hilang.
Baca selengkapnya (2)Tutup
RabbitMQ berperilaku sama. Pengiriman yang belum di-ack saat channel atau koneksinya tertutup akan dimasukkan lagi ke queue dan dikirim ulang dengan flag redeliver, mungkin ke consumer lain, dan dokumentasinya meminta consumer siap menghadapi pengiriman ulang. Kafka bisa mencapai exactly once antar-topic Kafka dengan transaksi, tetapi dokumentasinya menambahkan bahwa exactly once ke sistem lain umumnya butuh kerja sama dari sistem tersebut. Tabel stok dan penyedia email Anda termasuk sistem lain.
Jadi buat consumer menjadi idempotent, sifat yang diajarkan pelajaran 10 untuk API. Pola idempotent consumer mencatat id setiap pesan yang sudah diproses dalam transaksi database yang sama dengan perubahan bisnisnya; jika insert id gagal karena id itu sudah ada, pesan itu pernah ditangani dan consumer melewatinya. Dokumentasi Kafka menjelaskan trik yang sama untuk posisi: biarkan consumer menyimpan offset-nya di tempat yang sama dengan hasil kerjanya.
Bagaimana partition menentukan urutan event?
Kafka menyebar satu topic ke beberapa partition supaya banyak consumer bisa membaca secara paralel. Event dengan key yang sama, misalnya id pelanggan atau id order, ditulis ke partition yang sama, dan consumer mana pun dari partition itu membaca event-nya persis sesuai urutan penulisan. Jaminan itu berlaku per partition. Dua event dengan key berbeda bisa berada di partition berbeda, yang dibaca secara terpisah.
Baca selengkapnya (1)Tutup
Tentukan key dengan bertanya: event mana yang wajib tetap berurutan? Untuk siklus hidup sebuah order, OrderPlaced, OrderPaid, OrderCancelled, key-nya adalah id order. Kalau OrderPlaced memakai key order sedangkan OrderCancelled memakai key lain, keduanya bisa jatuh ke partition berbeda dan consumer mungkin melihat pembatalannya lebih dulu. Consumer yang bersaing di satu queue punya celah yang sama: dokumentasi RabbitMQ mencatat bahwa pengiriman ulang bisa mengubah urutan, dan dokumentasi Redis mengingatkan bahwa dengan satu stream dan beberapa consumer, pesan tentang item yang sama bisa diproses tidak berurutan.
Apa yang terjadi saat consumer tertinggal?
Producer dan consumer berjalan dengan kecepatan berbeda, dan selisih di antara keduanya adalah backlog. Di log, selisih itu punya nama, yaitu lag: Redis melaporkan lag sebuah consumer group sebagai jumlah entri yang masih menunggu dikirim ke group itu, dan menyarankan menambah consumer saat angkanya terus tinggi. Consumer Kafka bekerja dengan cara pull, yang oleh dokumentasi desainnya disebut sifat yang lebih baik: consumer yang tertinggal tinggal mengejar saat sempat. Lag yang naik saat lonjakan lalu turun lagi itu sehat; lag yang terus naik adalah angka yang perlu dipasangi alert.
Baca selengkapnya (2)Tutup
Backpressure adalah cara consumer yang lambat membatasi apa yang diterimanya. Prefetch di RabbitMQ membatasi jumlah pengiriman yang belum di-ack di satu channel; saat batasnya tercapai, broker berhenti mengirim ke channel itu sampai ada yang di-ack. Ini melindungi consumer, bukan broker, karena backlog tetap bertambah di queue. Batas panjang queue bisa menghentikannya, tetapi pahami dulu apa yang terjadi saat penuh: overflow default RabbitMQ membuang atau me-dead-letter pesan dari depan queue, yang paling lama lebih dulu, sedangkan reject-publish menolak pesan baru dan memberi tahu publisher yang memakai confirm lewat nack, sehingga masalahnya dikembalikan ke producer.
Log juga punya batas, yaitu waktu. Kafka membuang event lama begitu masa retensi topic lewat, jadi consumer yang mati lebih lama dari masa itu kehilangan event yang belum sempat dibacanya. Redis stream yang dibatasi dengan MAXLEN memangkas entri lama dengan cara yang sama, dan group yang entri belum terbacanya ikut terpangkas melaporkan lag-nya sebagai tidak diketahui.
Bagaimana mempublikasikan event tanpa kehilangannya?
Jebakannya adalah dual write. Checkout harus menyimpan order ke database-nya dan mempublikasikan OrderPlaced ke broker: dua sistem tanpa transaksi bersama. Commit dulu lalu crash sebelum publish, maka order-nya ada tetapi tidak ada yang diberi tahu. Publish dulu lalu transaksinya di-rollback, maka email, stock, dan analytics bereaksi pada order yang tidak pernah ada. microservices.io menyatakan aturannya dari dua arah: jika transaksi commit, pesannya wajib terkirim; jika rollback, pesannya tidak boleh terkirim. Two-phase commit sering tidak bisa dipakai, karena database atau broker-nya mungkin tidak mendukung.
Baca selengkapnya (1)Tutup
Transactional outbox menjadikan event bagian dari transaksi lokal yang sama: insert baris order dan baris outbox bersamaan, lalu biarkan relay terpisah membaca outbox dan mempublikasikannya. Relay itu bisa mem-polling tabelnya, disebut polling publisher, atau membaca transaction log database. Relay bisa mempublikasikan satu baris dua kali, misalnya saat crash setelah mengirim dan sebelum menandai barisnya terkirim, jadi di sini pun consumer harus idempotent. Pola ini juga mensyaratkan pesan sampai ke broker sesuai urutan service menuliskannya.
Di mana posisi CQRS dan event sourcing?
Keduanya ide terpisah yang sering muncul bersama event. Event sourcing menyimpan event itu sendiri sebagai sumber kebenaran, sehingga, seperti kata Fowler, keadaan sistem bisa dibangun ulang dengan yakin dengan memproses ulang event kapan pun di masa depan. CQRS adalah gagasan memakai struktur data terpisah untuk membaca dan menulis, dan event stream adalah salah satu cara menjaga sisi baca tetap mutakhir. Langkah yang lebih ringan adalah event-carried state transfer: event membawa datanya, jadi consumer menyimpan salinannya sendiri dan tidak perlu memanggil balik producer. Penerima tetap bisa bekerja saat sumbernya tidak tersedia, dengan harga banyak salinan dan kerumitan tambahan di sisi penerima. Peringatan Fowler soal CQRS, bahwa manfaatnya harus ditimbang terhadap kerumitan memiliki model terpisah, berlaku juga untuk dua pola lainnya. Anda bisa mempublikasikan event tanpa memakai satu pun dari pola ini.
Kapan sebaiknya tidak memakai event-driven?
Saat pemanggil butuh jawabannya sekarang juga. Memeriksa password, menampilkan stok barang sebelum tombol Beli: ini adalah query, dan request dengan response lebih sederhana untuk dibuat dan di-debug. Saat satu database cukup menampung datanya, satu transaksi lokal lebih baik daripada outbox, broker, dan consumer idempotent. Dan saat alurnya berupa proses yang langkah-langkahnya harus berurutan, tampilkan urutan itu di satu tempat, lewat orchestrator atau fungsi biasa, alih-alih menyebarkannya ke banyak service sebagai rantai event yang menurut Fowler sulit dilihat.
Baca selengkapnya (1)Tutup
Event layak dipakai saat beberapa pihak harus bereaksi pada satu fakta, saat producer tidak boleh menunggu mereka, saat consumer mungkin mati sebentar tanpa disadari producer, atau saat lonjakan harus ditampung lalu dikerjakan belakangan. Broker adalah satu sistem lagi yang harus dijalankan, dipantau, dan di-upgrade, jadi mulailah dari yang paling kecil yang sudah memberi jaminan di atas.
Konfigurasi nyata di balik pelajaran ini
Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.
on:
push:
branches: [main]
workflow_dispatch:Push adalah event: fakta bahwa commit sudah masuk ke sebuah branch. GitHub lalu menjalankan setiap workflow yang nilai on:-nya cocok, jadi file ini hanyalah satu subscriber di antara mungkin banyak subscriber, dan branches: [main] adalah filternya. workflow_dispatch adalah jenis pesan lain: orang yang menekan Run workflow meminta workflow ini bertindak, dan itu adalah command.
# Concurrency is split per job rather than set once for the whole workflow.
#
# A single workflow-level group with cancel-in-progress: false made a burst of
# commits queue a burst of complete pipelines — 28 deep on 2026-09-24, 27 of
# them building code that was already superseded. Flipping that one group to
# true would have fixed the queue and allowed a run to be cancelled MID-SSH
# DEPLOY, which is the one place here that must never be interrupted.Setiap push adalah event tersendiri, dan setiap push memulai satu pipeline lengkap. Komentar ini adalah catatan repo sendiri tentang lonjakan pada 2026-09-24: antrean sedalam 28 pipeline, 27 di antaranya mem-build kode yang sudah digantikan commit yang lebih baru. Itulah backlog dalam skala kecil: consumer lebih lambat daripada producer, dan tidak ada yang memberi tahu event mana yang sudah tidak penting lagi. Perilaku default yang didokumentasikan GitHub lebih sempit daripada antrean: satu concurrency group menahan paling banyak satu run yang menunggu, dan run yang lebih baru membatalkannya.
# Superseded verification is pure waste; drop it the moment it is stale.
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: truePerbaikannya memberi tahu consumer event mana yang sudah tidak penting. Key group-nya adalah Git ref, jadi push ke branch yang sama berbagi satu group, mirip event dengan key yang sama berbagi satu partition, dan cancel-in-progress: true membatalkan build yang sedang berjalan saat ada yang lebih baru masuk. Perilakunya mirip log compaction di Kafka, yang menyimpan setidaknya nilai terakhir untuk setiap key. Ini aman hanya karena build dari commit terbaru sudah mencakup semua push sebelumnya; event yang masing-masing membawa perubahan terpisah, seperti pembayaran, tidak boleh dibuang dengan cara ini.
# Two runs racing would each diff against the same snapshot and the slower
# one would overwrite the other's; queue them instead.
concurrency:
group: indexnow
cancel-in-progress: falseJob IndexNow adalah consumer yang punya ingatan: setiap run membandingkan sitemap live dengan snapshot yang disimpan run sebelumnya. Dua run bersamaan akan membaca snapshot yang sama, lalu run yang lebih lambat menimpa hasil run lainnya, jadi group-nya tidak pernah membatalkan job yang sedang berjalan: run baru menunggu sampai run yang berjalan selesai. Secara default paling banyak satu run yang menunggu dan run yang datang lebih baru menggantikannya, dan itu aman di sini karena run terbaru membandingkan dengan snapshot terbaru. Satu partition Kafka dibaca oleh satu consumer per group karena alasan yang sama: saat penanganan sebuah event bergantung pada event sebelumnya, satu pembaca pada satu waktu menjaga urutannya.
Kesalahan yang benar-benar membuang waktu
Menyimpan dan publish dalam dua langkah terpisah
Commit order lalu publish, maka crash di antaranya membuat event hilang selamanya; publish dulu, maka rollback mengumumkan order yang tidak pernah ada. Tidak ada urutan yang aman, dan retry tidak menolong proses yang sudah mati. Tulis event ke tabel outbox dalam transaksi yang sama dan biarkan relay mempublikasikannya, dengan consumer idempotent untuk duplikat yang mungkin dikirimnya.
Poison message yang menghambat semua di belakangnya
Event yang selalu membuat consumer-nya crash akan dikirim ulang dan membuatnya crash lagi. Di log, posisi group tidak bisa melewatinya, jadi semua event di belakangnya dalam partition itu ikut menunggu. Hitung jumlah pengiriman dan sisihkan pesannya setelah batas tertentu: quorum queue RabbitMQ memakai default 20 sejak versi 4.0, lalu membuang atau me-dead-letter pesannya, dan Redis Streams menyediakan delivery counter untuk tugas yang sama. Pasang alert untuk apa pun yang masuk ke tempat dead-letter.
Backlog yang tidak dipantau
Saat consumer lebih lambat daripada producer, backlog bertambah diam-diam sampai ada yang jebol: memori, disk, atau masa retensi yang membuang event yang belum sempat dibaca group yang lambat. Queue RabbitMQ yang penuh karena batas panjang secara default membuang atau me-dead-letter pesan paling lama. Pasang alert untuk lag yang terus naik, tambah consumer, dan putuskan dengan sengaja apakah queue yang penuh harus membuang, me-dead-letter, atau menolak pesan baru.
Mengharapkan urutan lintas partition
OrderPlaced dan OrderCancelled untuk satu order yang dipublikasikan dengan key berbeda bisa jatuh ke partition berbeda dan terbaca dengan pembatalan lebih dulu. Jaminan urutan Kafka berlaku per partition. Beri key setiap event tentang satu entitas dengan id entitas itu, dan biarkan consumer menyimpan dulu event yang belum bisa diterapkan alih-alih gagal karenanya.
| Queue (gaya RabbitMQ) | Log (gaya Kafka) | |
|---|---|---|
| Setelah di-ack | Pesan ditandai untuk dihapus | Event tetap ada sampai masa retensi habis |
| Beberapa consumer | Bersaing: setiap pesan ke salah satunya | Setiap group melihat semua event; satu consumer per partition di dalam group |
| Kemajuan dicatat sebagai | Setiap pengiriman yang menunggu ack | Satu offset per partition per group |
| Replay | Tidak bisa setelah di-ack | Mundurkan offset lalu baca ulang |
| Urutan | First in, first out, sampai pengiriman ulang atau prioritas mengubahnya | Dijamin hanya di dalam satu partition |
| Cocok untuk | Job yang harus jalan sekali, misalnya membuat invoice | Fakta yang dibaca, di-replay, atau diaudit banyak service |
Lakukan di komputer Anda sendiri
Anda hanya butuh Docker. Redis Streams adalah log kecil dengan consumer group, cukup untuk melihat fan-out, backlog, pengiriman ulang, dan replay di laptop. Jalankan semua langkah di satu terminal, karena langkah 2 menyimpan id event di variabel shell.
Jalankan Redis lalu buat stream orders dengan satu consumer group per service, email dan stock. Setiap group menyimpan posisinya sendiri, mulai dari ujung stream.
docker run -d --name eda-redis redis:7-alpine docker exec eda-redis redis-cli XGROUP CREATE orders email '$' MKSTREAM docker exec eda-redis redis-cli XGROUP CREATE orders stock '$'Publikasikan tiga event OrderPlaced, masing-masing sekali. XADD mencetak id setiap entri; simpan di variabel untuk ack nanti.
A=$(docker exec eda-redis redis-cli XADD orders '*' event OrderPlaced id evt-41) B=$(docker exec eda-redis redis-cli XADD orders '*' event OrderPlaced id evt-42) C=$(docker exec eda-redis redis-cli XADD orders '*' event OrderPlaced id evt-43) echo $A $B $CEmail membaca ketiganya lalu mengirim ack; stock sedang mati dan tidak membaca apa pun. XINFO GROUPS menampilkan email dengan pending 0 dan lag 0, sedangkan stock lag 3: backlog-nya menunggu di stream.
docker exec eda-redis redis-cli XREADGROUP GROUP email email-1 COUNT 10 STREAMS orders '>' docker exec eda-redis redis-cli XACK orders email $A $B $C docker exec eda-redis redis-cli XINFO GROUPS ordersStock hidup lagi dan membaca backlog-nya, lalu anggap ia crash sebelum mengirim ack. Saat restart, ia membaca riwayat pending miliknya dengan id 0 dan mendapat ketiga event yang sama sekali lagi. XPENDING menunjukkan masing-masing terkirim dua kali.
docker exec eda-redis redis-cli XREADGROUP GROUP stock stock-1 COUNT 10 STREAMS orders '>' docker exec eda-redis redis-cli XREADGROUP GROUP stock stock-1 COUNT 10 STREAMS orders 0 docker exec eda-redis redis-cli XPENDING orders stock - + 10Buat consumer idempotent dengan mengingat setiap id event yang sudah ditanganinya. SET NX pertama mencetak OK; yang kedua mencetak balasan kosong, jadi duplikatnya dilewati. Lalu kirim ack. Di service sungguhan, simpan id itu dalam transaksi database yang sama dengan perubahan stok.
docker exec eda-redis redis-cli SET seen:stock:evt-41 1 NX docker exec eda-redis redis-cli SET seen:stock:evt-41 1 NX docker exec eda-redis redis-cli XACK orders stock $A $B $CReplay: service analytics yang baru memulai group-nya dari 0 dan membaca ketiga event, padahal email dan stock sudah mengirim ack. XLEN tetap menghitung 3, karena log menyimpan apa yang sudah dibaca. Queue tidak akan menyisakan apa pun untuknya.
docker exec eda-redis redis-cli XGROUP CREATE orders analytics 0 docker exec eda-redis redis-cli XREADGROUP GROUP analytics analytics-1 COUNT 10 STREAMS orders '>' docker exec eda-redis redis-cli XLEN ordersBersihkan container-nya.
docker rm -f eda-redis
Tiga pertanyaan sebelum lanjut
Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada nilai, jadi tebak saja dengan bebas; jawaban yang salah disimpan di browser ini agar kursus bisa menanyakannya lagi nanti.
Dua pertanyaan dari pelajaran sebelumnya
Mengingat kembali pelajaran lama setelah jeda membuatnya lebih melekat. Jawab dari ingatan sebelum mencari apa pun.
Jawab dalam hati, lalu cek
1.SendReceipt itu event atau command, dan bagaimana dengan ReceiptSent?Lihat jawabannya
Jawaban:SendReceipt adalah command: meminta satu penerima bertindak, dan penerima itu boleh menolak. ReceiptSent adalah event: fakta yang sudah terjadi, yang boleh ditanggapi atau diabaikan oleh service mana pun.
2.Kenapa service baru bisa me-replay order minggu lalu dari log, tetapi tidak dari queue?Lihat jawabannya
Jawaban:Queue menghapus pesan setelah di-ack. Log menyimpan event sampai masa retensinya habis dan mencatat kemajuan setiap group sebagai offset, jadi group baru bisa mulai dari awal.
3.Checkout menyimpan order lalu mempublikasikan OrderPlaced. Apa yang bisa salah, dan apa perbaikannya?Lihat jawabannya
Jawaban:Crash di antara keduanya menyisakan order yang tidak diketahui siapa pun, dan publish lebih dulu berisiko mengumumkan order yang kemudian di-rollback. Tulis event ke tabel outbox dalam transaksi yang sama dan biarkan relay mempublikasikannya, dengan consumer idempotent untuk duplikatnya.
4.Apa yang diukur consumer lag, dan kapan angka itu perlu dikhawatirkan?Lihat jawabannya
Jawaban:Jumlah event yang sudah ditulis tetapi belum dikirim ke sebuah group. Lag yang naik saat lonjakan lalu turun lagi itu normal. Lag yang terus naik berarti consumer lebih lambat daripada producer, dan dengan retensi berbasis waktu, event yang belum terbaca pada akhirnya akan dibuang.
Bahasan mendalam terkait di situs ini
Sumber
Sumber primer yang diperiksa pada tanggal riset. Ikuti tautannya untuk versi terbaru.
- Dokumentasi Apache Kafka 4.3: Introduction (event, topic, partition, retensi)
- Dokumentasi Apache Kafka 4.3: Design (semantik pengiriman, offset, compaction)
- Dokumentasi RabbitMQ: Queues
- Dokumentasi RabbitMQ: consumer acknowledgement dan publisher confirm
- Tutorial RabbitMQ: Work queues (pembagian round-robin)
- Dokumentasi RabbitMQ: Streams (consumer non-destruktif, replay)
- Dokumentasi RabbitMQ: Quorum queues (penanganan poison message)
- Dokumentasi RabbitMQ: batas panjang queue dan perilaku overflow
- Dokumentasi Redis: Streams dan consumer group
- Dokumentasi Redis: XINFO GROUPS (lag consumer group)
- Martin Fowler: What do you mean by “Event-Driven”?
- microservices.io: Transactional outbox
- microservices.io: Idempotent consumer
- GitHub Docs: Workflows (cara event memicu run)
- GitHub Docs: event yang memicu workflow (push, workflow_dispatch)
- GitHub Docs: mengatur concurrency workflow dan job