Topik ini membangun mental model Kafka yang utuh, yaitu producer sebagai penulis kejadian, consumer sebagai pembacanya, dan consumer group sebagai tim pembaca yang berbagi satu posisi baca. Perilaku ketiganya disimulasikan dengan Python murni agar offset per grup terlihat bergerak, kemudian wujud kode Kafka yang sesungguhnya diperkenalkan sebagai pembanding. Dengan model itu, arsitektur Kafka terbaca sebagai tiga peran dengan tanggung jawab yang jelas, bukan kumpulan istilah yang mengambang.
Producer menulis kejadian ke topik#
Producer adalah program yang menulis kejadian ke topik, dan tugas utamanya adalah memilih partisi tempat setiap kejadian disimpan. Pilihan itu dilakukan otomatis dengan menghitung partisi dari kunci kejadian, sehingga semua kejadian dengan kunci yang sama berakhir pada partisi yang sama dan urutannya terjaga. Apabila kunci tidak diberikan, kejadian disebar merata ke partisi-partisi yang tersedia.
Producer tidak menunggu consumer sebelum melanjutkan pekerjaan, karena keduanya tidak saling berbicara langsung. Kejadian yang sudah ditulis tersimpan pada log topiknya dan siap dibaca oleh siapa pun yang berhak, meskipun producer itu sudah lama berhenti. Sifat terpisah ini disebut decoupling, yakni pelepasan ketergantungan antara pihak yang menghasilkan data dan pihak yang menggunakannya, dan inilah alasan Kafka mampu menghubungkan banyak sistem tanpa membuat keduanya saling menunggu.
Consumer membaca dengan laju sendiri#
Consumer adalah program yang membaca kejadian dari topik dengan kecepatan yang ditentukan sendiri. Kafka menggunakan model tarik (pull), yaitu consumer yang meminta kejadian ketika siap, dan bukan broker yang mendorong data tanpa diminta. Broker sendiri adalah server penyimpan pusat pada klaster (cluster) Kafka. Kecepatan membaca boleh lebih lambat daripada kecepatan menulis, karena kelebihan kejadian menumpuk dengan aman pada log selama masa retensinya belum lewat.
Consumer juga dapat melompat ke posisi mana pun pada log. Membaca dari offset 0 mengulang seluruh kejadian yang masih tersimpan, sedangkan membaca dari ujung log hanya mengambil kejadian baru. Fleksibilitas ini menopang dua kebutuhan sekaligus, yaitu mengisi ulang data lama ketika logika pemrosesan berubah dan mengikuti aliran baru tanpa membaca masa lalunya. Kedua perilaku itu mungkin karena posisi baca disimpan di luar datanya, yakni sebagai angka offset yang dapat diubah kapan saja.
Consumer group membagi pekerjaan#
Consumer group adalah sekelompok consumer yang membaca satu topik sebagai tim, dan offset baca disimpan per grup, bukan per program. Kafka membagi partisi-partisi topik kepada anggota grup, dan satu partisi hanya diberikan kepada satu anggota pada satu waktu, sehingga setiap kejadian diproses tepat satu kali oleh grup itu. Posisi baca seluruh anggota grup tercatat sebagai satu daftar offset milik grup tersebut.
Grup yang berbeda membaca topik yang sama secara terpisah. Setiap grup menyimpan offsetnya sendiri, membaca dari posisinya sendiri, dan boleh tertinggal tanpa mengganggu grup lain. Satu topik dengan dua grup berarti kejadian yang sama melayani dua kebutuhan yang berbeda, misalnya pemantauan yang harus selalu segar dan analitik yang berjalan santai.
Retensi menutup model ini. Grup yang tertinggal terlalu jauh berisiko membaca kejadian yang sudah dibuang, sehingga laju baca tiap grup tetap harus diawasi terhadap masa retensi topiknya. Selain itu, jumlah anggota grup yang berguna dibatasi jumlah partisinya, karena anggota yang lebih banyak daripada partisi berarti sebagian anggota menganggur tanpa partisi untuk dibaca.
Simulasi dua consumer group#
Perilaku offset per grup disimulasikan dengan Python murni. Kelas TopikSederhana meniru topik satu partisi, sedangkan kelas ConsumerGroup menyimpan offset bacanya sendiri dan memajukannya setiap kali selesai membaca. Jalankan skripnya, lalu perhatikan dua grup yang membaca topik yang sama dari posisi masing-masing.
"""Dua consumer group membaca topik yang sama dengan posisi baca masing-masing."""
class TopikSederhana:
"""Topik satu partisi, hanya berupa daftar log tambah-saja."""
def __init__(self) -> None:
self.log: list[str] = []
def append(self, isi: str) -> int:
"""Tambahkan kejadian di ujung log, lalu kembalikan offsetnya."""
self.log.append(isi)
return len(self.log) - 1
class ConsumerGroup:
"""Consumer group yang menyimpan offset bacanya sendiri."""
def __init__(self, nama: str, topik: TopikSederhana) -> None:
self.nama = nama
self.topik = topik
self.offset = 0 # posisi baca milik grup ini, tidak dibagikan
def baca(self) -> list[tuple[int, str]]:
"""Baca semua kejadian sejak offset tersimpan, lalu majukan offsetnya."""
hasil = list(enumerate(self.topik.log[self.offset:], start=self.offset))
if hasil:
self.offset = hasil[-1][0] + 1
return hasil
topik = TopikSederhana()
topik.append("login ali")
topik.append("login budi")
pemantau = ConsumerGroup("pemantau", topik)
analitik = ConsumerGroup("analitik", topik)
print("pemantau membaca:", pemantau.baca())
print("analitik membaca:", analitik.baca())
print("posisi pemantau:", pemantau.offset, "posisi analitik:", analitik.offset)
# pemantau tetap berjalan, sedangkan analitik ditinggalkan sementara
topik.append("catatan_dibuat budi")
topik.append("login citra")
print("pemantau membaca:", pemantau.baca())
print("posisi pemantau:", pemantau.offset, "posisi analitik:", analitik.offset)
# analitik kembali dan mengejar dari posisinya sendiri
print("analitik membaca:", analitik.baca())
print("posisi analitik sekarang:", analitik.offset)pemantau membaca: [(0, 'login ali'), (1, 'login budi')]
analitik membaca: [(0, 'login ali'), (1, 'login budi')]
posisi pemantau: 2 posisi analitik: 2
pemantau membaca: [(2, 'catatan_dibuat budi'), (3, 'login citra')]
posisi pemantau: 4 posisi analitik: 2
analitik membaca: [(2, 'catatan_dibuat budi'), (3, 'login citra')]
posisi analitik sekarang: 4Keluarannya memperlihatkan mental model yang dibangun. Kedua grup mulai dari offset 0 dan membaca kejadian yang sama, lalu posisinya sama-sama maju menjadi 2 setelah putaran pertama. Ketika dua kejadian baru masuk, pemantau yang tetap berjalan langsung membacanya, sedangkan analitik yang ditinggalkan tetap diam di offset 2. Setelah analitik kembali, grup itu mengejar dari posisi tersimpannya sendiri dan berakhir di offset 4, tanpa satu pun kejadian yang terlewat maupun dibaca ganda.
Wujud nyata di kode Kafka#
Simulasi di atas sengaja menghilangkan hampir semua kerumitan Kafka, sehingga wujud aslinya perlu dilihat agar keduanya tidak tertukar. Kafka berjalan sebagai klaster yang terdiri atas broker, dan Kafka sendiri tidak terinstal di sandbox ini, sehingga tiga potongan berikut tidak dijalankan di sini. Ketiganya menggunakan library kafka-python dan tetap berguna sebagai gambaran bentuk aslinya. Pembuatan topik dilakukan lewat perintah CLI yang disertakan Kafka.
# tidak-dijalankan
# membuat topik pada klaster Kafka lokal
kafka-topics.sh --create --topic sensor-ruang-101 \
--partitions 1 --replication-factor 1 \
--bootstrap-server localhost:9092Skrip producer berikut mengirim tiga kejadian suhu ke topik yang sama tanpa mengetahui siapa yang membacanya.
# tidak-dijalankan
"""Producer dengan kafka-python: mengirim kejadian ke satu topik."""
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers="localhost:9092")
for i in range(3):
kejadian = f"suhu 24.{i}"
producer.send("sensor-ruang-101", value=kejadian.encode("utf-8"))
producer.flush()
producer.close()Skrip consumer berikut membaca topik yang sama sebagai anggota grup bernama pemantau, dan posisi baca grup itu disimpan oleh Kafka melalui mekanisme commit yang berjalan otomatis.
# tidak-dijalankan
"""Consumer dengan kafka-python: membaca topik dengan kelompok pemantau."""
from kafka import KafkaConsumer
consumer = KafkaConsumer(
"sensor-ruang-101",
group_id="pemantau",
bootstrap_servers="localhost:9092",
auto_offset_reset="earliest",
)
for kejadian in consumer:
print(kejadian.offset, kejadian.value.decode("utf-8"))Bentuk aslinya menegaskan peran yang sudah disimulasikan. Producer hanya memanggil send dan tidak tahu siapa yang membaca, sedangkan consumer menyebut nama grupnya, dan posisi baca grup itu disimpan oleh broker, bukan oleh programnya. Peta berpikir dari simulasi tetap berlaku sepenuhnya, yaitu log, partisi, offset, dan grup, sedangkan selebihnya adalah detail operasional yang dipikirkan Kafka.
Kesalahan umum#
- Menambah anggota consumer melebihi jumlah partisi. Penyebabnya, jumlah anggota dianggap bebas ditambah agar pembacaan lebih cepat. Akibatnya, anggota kelebihan menganggur karena tidak mendapat partisi, sedangkan biayanya tetap ada. Perbaikannya, buat jumlah partisi sekurang-kurangnya sama dengan jumlah anggota yang direncanakan.
- Menyimpan posisi baca di dalam program consumer. Penyebabnya, offset terasa seperti variabel biasa milik program. Akibatnya, program yang dimulai ulang kehilangan posisinya, atau lebih buruk, dua program dengan salinan posisi berbeda memproses kejadian yang sama. Perbaikannya, serahkan penyimpanan offset kepada Kafka melalui mekanisme
commityang disediakannya. - Mengabaikan jeda grup terhadap retensi. Penyebabnya, grup dianggap dapat tertinggal selamanya tanpa konsekuensi. Akibatnya, grup yang melambat melewati masa retensi, dan kejadian yang belum dibacanya sudah terhapus. Perbaikannya, pantau selisih offset terakhir grup terhadap ujung log, lalu naikkan retensi atau percepat grup sebelum selisihnya melewati batas.
Ringkasan#
- Producer menulis kejadian ke partisi yang dipilih dari kuncinya, dan pekerjaannya tidak bergantung pada consumer mana pun.
- Consumer membaca dengan laju sendiri dari posisi offset mana pun selama kejadian masih berada dalam masa retensi.
- Consumer group menyimpan offset per grup, membagi partisi di antara anggotanya, dan berjalan terpisah dari grup lain atas topik yang sama.
Referensi#
- Apache Kafka documentation — Apache Kafka — Penjelasan desain producer Kafka, termasuk pemilihan partisi dan pengiriman asinkron.
- Apache Kafka documentation — Apache Kafka — Penjelasan desain consumer Kafka, termasuk posisi baca dan model tarik.
- kafka-python documentation — kafka-python — Dokumentasi library Kafka untuk Python yang dipakai pada contoh kode di atas.