Problem: unudan növbə kifayət etmir
Sifariş servisi billing, bildiriş və analitika komandalarına sifarişin ödənildiyini bildirməlidir. Üç servisi sinxron çağırmaq onların əlçatanlığını sizinkinə bağlayır. Klassik message queue bu asılılığı götürür, amma mesaj oxunan kimi silinir: gələn ay əlavə olunan yeni consumer tarixçəni görə bilmir, səhvli consumer isə dünənki mesajları yenidən oxuya bilmir.
Kafka fərqli model təklif edir: o, paylanmış, replikasiya olunan, yalnız sona əlavə edilən logdur. Producer-lər record əlavə edir, consumer-lər isə öz sürətləri ilə oxuyur və öz mövqelərini özləri xatırlayır. Oxumaq heç nəyi silmir. Record-lar yalnız retention qaydası çatanda yox olur.
Əsas anlayışlar
- Topic: adlandırılmış log, məsələn
orders.paid. Bu, məntiqi kateqoriyadır, tək fayl deyil. - Partition: topic partition-lara bölünür. Hər partition sıralı və dəyişməz record ardıcıllığıdır. Paralellik və sıralama vahidi partition-dur.
- Offset: record-un bir partition daxilindəki mövqeyi. Offset yalnız həmin partition üçün məna daşıyır.
- Record: açar (key), dəyər, header-lər və timestamp. Açar göründüyündən vacibdir.
- Broker: klasterdəki server. Hər partition bir neçə broker-də saxlanır.
- Consumer group: topic-in oxunması işini bölüşən consumer instansları dəsti.
topic orders.paid (3 partition)
P0: [0][1][2][3][4] -> sona əlavə olunur
P1: [0][1][2]
P2: [0][1][2][3]
consumer group "billing" consumer group "analytics"
instance A <- P0, P1 instance X <- P0, P1, P2
instance B <- P2Hər partition eyni vaxtda group daxilində ən çoxu bir consumer instansına verilir. Fərqli group-lar bir-birindən asılı deyil: billing və analytics hər ikisi bütün record-ları alır, hər birinin öz commit olunmuş offset-ləri var.
Sıralama topic üzrə yox, partition üzrədir
Ən çox səhv başa düşülən fakt budur. Kafka sıranı yalnız bir partition daxilində saxlayır. Partition-lar arasında qlobal sıra yoxdur. Partition-u producer seçir: açar verildikdə default partitioner açarı hash edir, beləliklə eyni açarlı bütün record-lar eyni partition-a düşür və yazıldığı ardıcıllıqla oxunur.
Yəni "42 nömrəli sifarişin bütün hadisələri düzgün sırada" lazımdırsa, açar kimi orderId istifadə edin. Bütün sifarişlər üzrə qlobal sıra istəyirsinizsə, bir partition və group üçün bir consumer lazımdır, bu isə miqyaslanmanı qurban verir.
Retention, compaction və replikasiya
Retention vaxt və ya ölçü əsaslıdır: topic record-ları kimsə oxuyub-oxumamasından asılı olmayaraq müəyyən müddət saxlayır. Compacted topic isə hər açar üçün yalnız son record-u saxlayır; müştəri profili kimi "cari vəziyyət" topic-ləri üçün uyğundur.
Replikasiya topic səviyyəsində replication factor ilə qurulur. Hər partition üçün bir replika leader-dir; producer və consumer onunla işləyir, follower-lər ondan kopyalayır. Leader-ə çatmış follower-lər in-sync replica (ISR) dəstini təşkil edir. Leader düşərsə, ISR üzvlərindən biri leader olur. acks=all istifadə ediləndə min.insync.replicas yazının producer-ə uğurlu bildirilməsindən əvvəl neçə replikada olmalı olduğunu göstərir.
Producer acks
acks göndərişin nə vaxt uğurlu sayıldığını müəyyən edir:
| acks | Mənası | Risk |
|---|---|---|
0 | Heç bir broker-i gözləmir | Record səssizcə itə bilər |
1 | Leader yazdı | Follower-lər kopyalamamış leader düşsə itir |
all | Bütün in-sync replikalar yazdı | Ən güclü davamlılıq; gecikmə artır |
Biznes hadisələri üçün replication factor 3 olan topic-də acks=all və min.insync.replicas=2 istifadə edin. Producer retry-ları partition daxilində dublikat yaratmasın deyə enable.idempotence=true aktiv edin.
Nümunə: Spring Kafka producer və listener
spring: kafka: bootstrap-servers: localhost:9092 producer: acks: all properties: enable.idempotence: true consumer: group-id: billing enable-auto-commit: false auto-offset-reset: earliest listener: ack-mode: record@Serviceclass PaymentEvents { private final KafkaTemplate<String, String> kafka; PaymentEvents(KafkaTemplate<String, String> kafka) { this.kafka = kafka; } void publishPaid(String orderId, String json) { kafka.send("orders.paid", orderId, json) // key = orderId .whenComplete((result, ex) -> { if (ex != null) { /* log, alert, retry siyasəti */ } }); }}@Componentclass BillingListener { @KafkaListener(topics = "orders.paid", groupId = "billing") void on(ConsumerRecord<String, String> record) { // emal; offset metod exception-sız qayıdandan sonra commit olunur invoiceService.createFor(record.key(), record.value()); }}Listener offset-i yalnız metod exception atmadan qayıdandan sonra commit edir. Proses ortada çökərsə, record yenidən çatdırılır. Bu davranış at-least-once adlanır və handler-in dublikata dözməli olması deməkdir; seriyanın növbəti məqaləsi bunu ətraflı izah edir.
Consumer group və rebalance
Group-a instans qoşulanda və ya ondan çıxanda partition-lar rebalance ilə yenidən bölünür. Rebalance zamanı təsirlənən partition-larda oxuma durur. Tez-tez rastlanan səbəb yavaş emaldır: handler max.poll.interval.ms-dən uzun çəkərsə, broker consumer-i ölü hesab edir, partition-ları başqasına verir və record yenidən emal olunur.
Partition sayı group-un faydalı paralelliyinin yuxarı həddidir. Altı partition-lu topic-i on instans oxuyursa, dördü boş qalır. Partition sayını gözlədiyiniz maksimum consumer paralelliyinə və bir qədər ehtiyata görə planlayın, çünki sonradan partition əlavə etmək açarların yerləşməsinə təsir edir.
Tez-tez edilən səhvlər
- Partition-lar arasında qlobal sıra güman etmək və qarışıq hadisələrlə üzləşmək.
- Bir entity-yə aid hadisələr üçün təsadüfi və ya boş açar istifadə etmək.
- Listener daxilində yavaş I/O aparıb
max.poll.recordsvəmax.poll.interval.mstənzimləməmək; nəticədə rebalance fırtınası. enable-auto-commit-i açıq saxlamaq: offset iş bitməmiş commit oluna bilər.- Topic-ə verilənlər bazası kimi yanaşmaq: ixtiyari sahə üzrə sorğu log-un işi deyil.
- Böyük payload-ları record-a qoymaq, blob storage-ə istinad əvəzinə.
- Retention-u consumer-in söndüyü və ya replay etməli olduğu ən uzun müddətdən qısa təyin etmək.
Kompromislər və Kafka nə vaxt lazım deyil
Kafka davamlılıq, replay, yüksək ötürmə və ayrılmış consumer-lər verir. Əvəzində əməliyyat yükü (broker-lər, yenilənmələr, tutum planlaması), daha çətin zehni model (partition, offset, rebalance) və servislər arasında eventual consistency ödəyirsiniz. Bir consumer-i və kiçik həcmi olan sistem üçün verilənlər bazası cədvəli və ya sadə idarə olunan queue daha asan ola bilər. Request/response lazımdırsa, HTTP və ya gRPC istifadə edin; log bunun üçün zəif seçimdir.
Consumer lag: izlənməli əsas göstərici
Lag partition-dakı son offset ilə group-un commit etdiyi offset arasındakı məsafədir. Artan lag consumer-lərin producer-lərdən yavaş olduğunu göstərir. Bu, Kafka əsaslı axının ən faydalı sağlamlıq siqnalıdır, çünki görünməz yığılmanı alert qurula bilən ölçüyə çevirir. Yalnız cəmə yox, partition üzrə lag-a baxın: tıxanmış tək partition, çox vaxt zəhərli record və ya "isti" açar səbəbindən, sağlam görünən ortalamanın içində gizlənir.
Yoxlama siyahısı
- Açar sırası vacib olan entity-dən seçilib.
- Partition sayı consumer paralelliyinə və gələcək artıma görə planlanıb.
- Vacib hadisələr üçün producer
acks=allvə idempotence ilə işləyir. - Replication factor və
min.insync.replicasdüşünülmüş təyin olunub. - Offset emaldan sonra commit olunur, əvvəl yox.
- Handler eyni record-u iki dəfə emal etməyə dözümlüdür.
- Retention ən uzun replay və ya fasilə müddətini əhatə edir.
Növbəti addım
- Kafka delivery semantics: at-least-once və exactly-once əslində nəyi əhatə edir, outbox pattern.
- Event-driven architecture trade-offs: hadisələr nə vaxt kömək edir, nə vaxt mane olur.
- Middle Java developer roadmap: mesajlaşmanın ümumi yoldakı yeri.