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 <- P2

Hə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:

acksMənasıRisk
0Heç bir broker-i gözləmirRecord səssizcə itə bilər
1Leader yazdıFollower-lər kopyalamamış leader düşsə itir
allBü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.records və max.poll.interval.ms tə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ı

  1. Açar sırası vacib olan entity-dən seçilib.
  2. Partition sayı consumer paralelliyinə və gələcək artıma görə planlanıb.
  3. Vacib hadisələr üçün producer acks=all və idempotence ilə işləyir.
  4. Replication factor və min.insync.replicas düşünülmüş təyin olunub.
  5. Offset emaldan sonra commit olunur, əvvəl yox.
  6. Handler eyni record-u iki dəfə emal etməyə dözümlüdür.
  7. Retention ən uzun replay və ya fasilə müddətini əhatə edir.

Növbəti addım