Wykład 4 — Apache Kafka

Analiza danych w czasie rzeczywistym

Architektura Apache Kafka — brokerzy, tematy, partycje, producenci, konsumenci, grupy konsumentów i integracja z ML.
Note{{< fa clock >}} Czas trwania: 1,5h

Cel wykładu: Poznanie architektury Apache Kafka — brokerów, tematów, partycji, producentów, konsumentów i grup konsumentów. Zrozumienie, dlaczego Kafka jest fundamentem nowoczesnych systemów real-time.


1 Od monolitu do mikroserwisów — kontekst

Zanim przejdziemy do Kafki, krótki kontekst. Tradycyjna aplikacja to monolit — jeden duży program, który robi wszystko: obsługuje użytkowników, przetwarza dane, generuje raporty. Łatwy na start, ale trudny do skalowania i modyfikowania.

Mikroserwisy to podejście, w którym aplikację dzielimy na małe, niezależne usługi — każda odpowiedzialna za jedno zadanie. Usługa „płatności” nie zna szczegółów usługi „rekomendacji”. Komunikują się przez API lub kolejki wiadomości.

CautionProblem N×N

Gdy masz 20 mikroserwisów, które muszą wymieniać dane, tworzysz siatkę połączeń punkt-punkt. Każdy nowy serwis wymaga integracji z wieloma innymi. To nie skaluje się.

Rozwiązanie: centralny broker wiadomości — jedno miejsce, przez które przepływają wszystkie dane. I tu wchodzi Apache Kafka.


2 Co to jest Apache Kafka?

Kafka to rozproszona platforma strumieniowa stworzona w LinkedIn (2011), obecnie rozwijana jako projekt Apache. Nie jest „kolejnym systemem kolejkowym” — to coś więcej.

Tip{{< fa server >}} Rozproszona

Działa na wielu serwerach (brokerach) jako klaster.

Tip{{< fa hard-drive >}} Trwała

Wiadomości zapisywane na dysku, nie znikają po odczytaniu.

Tip{{< fa gauge-high >}} Szybka

Obsługuje miliony wiadomości na sekundę.

Tip{{< fa arrows-left-right >}} Skalowalna

Dodajesz brokerów i partycji w miarę wzrostu danych.

ImportantKafka to nie kolejka!

W klasycznym systemie kolejkowym (np. RabbitMQ) wiadomość jest usuwana po przetworzeniu. Kafka tego nie robi — wiadomości są przechowywane na dysku przez konfigurowalny czas (domyślnie 7 dni). Dzięki temu:

  • wielu konsumentów może odczytać te same dane,
  • konsument może wrócić do wcześniejszych wiadomości (replay),
  • awaria konsumenta nie powoduje utraty danych.

3 Architektura Kafki

3.1 Wzorzec Publish/Subscribe

Kafka implementuje wzorzec pub/sub (publikuj/subskrybuj). Nadawca (producent) nie wysyła wiadomości bezpośrednio do odbiorcy — publikuje ją do tematu. Odbiorcy (konsumenci) subskrybują tematy, które ich interesują.

flowchart LR
    PA["Producent A"] --> T["Temat:\nzamówienia"]
    PB["Producent B"] --> T
    PC["Producent C"] --> T
    T --> CX["Konsument X\n(Grupa 1)"]
    T --> CY["Konsument Y\n(Grupa 1)"]
    T --> CZ["Konsument Z\n(Grupa 2)"]

    style T fill:#FF9800,color:#fff
    style PA fill:#2196F3,color:#fff
    style PB fill:#2196F3,color:#fff
    style PC fill:#2196F3,color:#fff
    style CX fill:#4CAF50,color:#fff
    style CY fill:#4CAF50,color:#fff
    style CZ fill:#9C27B0,color:#fff

Wzorzec Publish/Subscribe w Kafce

3.2 Tematy i partycje

Temat to logiczny kanał danych — jak folder, do którego trafiają wiadomości określonego typu. Np. transakcje, zamowienia, logi-serwera, odczyty-sensorow.

Każdy temat jest podzielony na jedną lub więcej partycji. Partycja to uporządkowana, niezmienna sekwencja wiadomości — każda wiadomość otrzymuje unikalny numer (offset).

Partycje służą dwóm celom:

  • Skalowalność — dane jednego tematu mogą być rozproszone na wielu brokerach.
  • Równoległość — wielu konsumentów może czytać różne partycje jednocześnie.

Broker to pojedynczy serwer Kafki. Klaster Kafki składa się z wielu brokerów. Każdy broker przechowuje część partycji.

Aby zapewnić niezawodność, Kafka replikuje partycje na wielu brokerach. Współczynnik replikacji (np. 3) oznacza, że każda partycja ma 3 kopie na różnych serwerach. Jeśli jeden broker padnie — dane nie giną.

flowchart LR
    subgraph "Temat: transakcje"
        direction TB
        P0["Partycja 0\noffset: 0,1,2,3..."]
        P1["Partycja 1\noffset: 0,1,2,3..."]
        P2["Partycja 2\noffset: 0,1,2,3..."]
    end
    P0 -.-> B1["Broker 1"]
    P1 -.-> B2["Broker 2"]
    P2 -.-> B3["Broker 3"]

    style P0 fill:#E3F2FD,stroke:#2196F3
    style P1 fill:#E3F2FD,stroke:#2196F3
    style P2 fill:#E3F2FD,stroke:#2196F3
    style B1 fill:#FFF3E0,stroke:#FF9800
    style B2 fill:#FFF3E0,stroke:#FF9800
    style B3 fill:#FFF3E0,stroke:#FF9800

Temat z partycjami rozproszonymi na brokerach


4 Producenci (Producers)

Producent to aplikacja, która wysyła wiadomości do Kafki. Producent wskazuje temat, a Kafka przypisuje wiadomość do partycji:

  • domyślnie: round-robin (kolejno do każdej partycji),
  • z kluczem: Kafka oblicza hash klucza i na tej podstawie wybiera partycję — wiadomości z tym samym kluczem zawsze trafiają do tej samej partycji.
from kafka import KafkaProducer
import json
from datetime import datetime

producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

# Wysyłanie transakcji do tematu "transakcje"
transakcja = {
    'id': 'TX0042',
    'kwota': 1250.00,
    'klient': 'K-1001',
    'czas': datetime.now().isoformat(),
    'sklep': 'Warszawa'
}

producer.send('transakcje', value=transakcja)
producer.flush()
print(f"Wysłano: {transakcja}")

Jeśli chcemy, żeby wszystkie transakcje tego samego klienta trafiały do jednej partycji (np. w celu analizy sesji):

# Klucz = ID klienta → ten sam klient zawsze w tej samej partycji
producer.send(
    'transakcje',
    key=b'K-1001',       # klucz partycjonowania
    value=transakcja
)
TipKiedy używać klucza?

Używaj klucza, gdy potrzebujesz gwarancji kolejności dla danej encji (np. wszystkie transakcje klienta K-1001 w kolejności) lub gdy dalsze przetwarzanie wymaga zgrupowania po kluczu.


5 Konsumenci (Consumers)

Konsument to aplikacja, która odczytuje wiadomości z tematu. Konsument śledzi swój offset — wie, którą wiadomość ostatnio przetworzył. Po awarii wznawia od tego miejsca.

from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    'transakcje',
    bootstrap_servers='localhost:9092',
    auto_offset_reset='earliest',
    value_deserializer=lambda x: json.loads(x.decode('utf-8'))
)

for message in consumer:
    tx = message.value
    if tx['kwota'] > 1000:
        print(f"Duża transakcja: {tx['id']} = {tx['kwota']} PLN ({tx['sklep']})")

5.1 Grupy konsumentów (Consumer Groups)

Gdy jeden konsument nie nadąża z przetwarzaniem — dodajesz kolejnych. Konsumenci w tej samej grupie dzielą między siebie partycje.

NoteZasady grup konsumentów
  • Kafka automatycznie przypisuje partycje do konsumentów w grupie.
  • Każdą partycję czyta dokładnie jeden konsument z grupy.
  • Jeśli konsument padnie — jego partycje przejmuje inny.
  • Ideał: liczba konsumentów w grupie = liczba partycji.
  • Jeśli konsumentów jest więcej niż partycji — nadmiarowi będą bezczynni.
  Temat: transakcje (3 partycje)
  Grupa konsumentów: "fraud-detection"

  Partycja 0 ──→ Konsument A
  Partycja 1 ──→ Konsument B
  Partycja 2 ──→ Konsument C
TipWiele grup = niezależny odczyt

Różne grupy konsumentów mogą czytać te same dane niezależnie. Np. grupa fraud-detection analizuje transakcje pod kątem oszustw, a grupa reporting buduje raporty — obie czytają temat transakcje, ale mają osobne offsety.


6 Mikroserwis z modelem ML — FastAPI

W praktyce konsument Kafki często przekazuje dane do modelu ML serwowanego przez API. FastAPI to lekki framework Pythonowy idealny do tego celu.

from fastapi import FastAPI
import numpy as np
import pickle

app = FastAPI()

# Wczytanie wytrenowanego modelu
# with open("model.pkl", "rb") as f:
#     model = pickle.load(f)

@app.get("/predict/")
def predict_price(area: float, bedrooms: int, age: int):
    """Prognoza ceny nieruchomości."""
    features = np.array([[area, bedrooms, age]])
    # price = model.predict(features)[0]
    price = area * 8500 + bedrooms * 50000 - age * 2000  # uproszczony model
    return {"estimated_price": round(price, 2)}

# Uruchomienie: uvicorn main:app --reload
# Zapytanie: http://localhost:8000/predict/?area=75&bedrooms=3&age=10

Zapytanie (Request):

  • URL: http://localhost:8000/predict/?area=75&bedrooms=3&age=10
  • Metoda HTTP: GET lub POST
  • Nagłówki: Content-Type, Authorization
  • Ciało (body): dane w formacie JSON

Odpowiedź (Response):

  • Status HTTP: 200 OK, 400 Bad Request, 500 Internal Server Error
  • Ciało: wynik w formacie JSON
{"estimated_price": 787500.0}

7 Kafka + ML — kompletny obraz

Łącząc elementy z ostatnich wykładów, oto schemat typowego systemu real-time analytics:

flowchart LR
    subgraph Źródła
        WEB["Aplikacja webowa"]
        IOT["Sensory IoT"]
        PAY["System płatności"]
    end
    subgraph Kafka
        T["Temat:\nevents"]
    end
    subgraph Przetwarzanie
        SP["Spark Streaming"]
        API["FastAPI + ML"]
        DB["Zapis do bazy"]
    end
    subgraph Wyniki
        DASH["Dashboard"]
        ALERT["Alerty"]
        REP["Raporty"]
    end

    WEB --> T
    IOT --> T
    PAY --> T
    T --> SP --> DASH
    T --> API --> ALERT
    T --> DB --> REP

    style T fill:#FF9800,color:#fff
    style SP fill:#2196F3,color:#fff
    style API fill:#4CAF50,color:#fff
    style DB fill:#9C27B0,color:#fff

Typowy system real-time analytics z Kafką

Producenci (aplikacje, sensory, systemy) wysyłają zdarzenia do Kafki. Konsumenci (Spark, FastAPI, systemy raportowe) odczytują je i przetwarzają — każdy na swój sposób, niezależnie od pozostałych.


8 Podsumowanie

Kafka to centralny element architektury strumieniowej. Jej siła tkwi w trwałości danych (wiadomości nie znikają), skalowalności (partycje, replikacja) i elastyczności (wielu niezależnych konsumentów). Na laboratoriach sami uruchomicie klaster Kafki w Dockerze i napiszecie producentów i konsumentów w Pythonie.

Note{{< fa forward >}} Na następnym wykładzie

Apache Spark i Structured Streaming — przetwarzanie danych strumieniowych na dużą skalę, integracja z Kafką.

Tip{{< fa brain >}} Do przemyślenia

Projektujesz system monitorowania cen konkurencji dla sieci sklepów. Jakie tematy Kafki byś utworzył? Ile partycji? Jakie grupy konsumentów?