Haladó Olvasási idő: ~7 perc

Messaging

Kafka integration, RabbitMQ, message patterns

Messaging

Hogyan építs megbízható, skálázható és production-ready üzenetküldést Spring alatt Kafka, RabbitMQ és jól megválasztott message patternök segítségével.

1. Definíció / Definition

Mi ez? / What is it?

A messaging olyan architekturális megközelítés, ahol a komponensek nem közvetlen HTTP hívásokkal, hanem üzeneteken keresztül kommunikálnak. Spring világban ez tipikusan a spring-kafka és spring-amqp librarykkel jelenik meg, Kafka vagy RabbitMQ broker mögött.

Miért létezik? / Why does it exist?

Azért, mert a valós rendszerekben a szolgáltatások életciklusa, terhelése és hibái nem szinkronban mozognak. Az üzenetsor leválasztja a producer és consumer felet, kisimítja a burst forgalmat, és lehetővé teszi az async feldolgozást, retry-t, bufferinget és auditálható eventfolyamokat.

Hol helyezkedik el? / Where does it fit?

A messaging a klasszikus synchronous REST és a teljes event-driven architektúra között helyezkedik el. Használhatod integrációra, background processingre, domain event továbbításra, vagy cross-service workflow-khoz.

2. Alapfogalmak / Core Concepts

2.1 Producer, broker, consumer

A producer üzenetet küld, a broker tárolja/teríti, a consumer feldolgozza.

Tipikus folyamat:

  1. A producer elküldi az üzenetet.
  2. A broker eltárolja vagy a megfelelő cél felé irányítja.
  3. A consumer átveszi és feldolgozza.
Fogalom Kafka RabbitMQ Mire jó?
Producer KafkaTemplate RabbitTemplate Üzenet küldése
Consumer @KafkaListener @RabbitListener Üzenet fogadása
Tároló egység topic + partition exchange + queue Routing és delivery
Retry/DLQ error handler + DLT DLX + DLQ Hibás üzenetek kezelése

2.2 Kafka mental model

Kafka log-alapú rendszer. A topic partíciókra oszlik, a consumer group példányai osztoznak rajtuk.

Példa az orders topic partícióira:

  • partition-0 — offsetek: 0, 1, 2, 3; ezen belül garantált a sorrend.
  • partition-1 — offsetek: 0, 1, 2; egy másik consumer példány dolgozhat rajta.
  • partition-2 — offsetek: 0, 1; az offset mutatja, meddig jutott a feldolgozás.

Kulcspontok:

  • Az ordering csak partitionön belül garantált.
  • A throughput a partitionök számával skálázható.
  • Az offset jelzi, meddig jutott a consumer.
  • Consumer groupon belül egy partíciót egyszerre egy consumer példány olvas.

2.3 RabbitMQ mental model

RabbitMQ-ban az üzenet jellemzően exchange-be érkezik, majd binding szabályok alapján queue-kba kerül.

Tipikus routing lépések:

  1. Producer elküldi az üzenetet.
  2. Exchange a bindingok alapján kiválasztja a cél queue-kat.
  3. Queue sorban tartja az üzeneteket.
  4. Consumer feldolgozza a kézbesített üzenetet.

Fő exchange típusok:

  • direct: routing key pontos egyezés
  • topic: wildcard alapú routing
  • fanout: broadcast
  • headers: ritkább, header alapján route-ol

2.4 Message patternök

  • Point-to-point: egy queue, egy consumer instance dolgozza fel az adott üzenetet.
  • Publish/subscribe: több consumer kapja meg ugyanazt az eseményt.
  • Request/reply: async infrastruktúra mögött kvázi RPC, correlation ID-val.

2.5 Serialization

JSON gyors és egyszerű, de gyenge schema governance mellett könnyen driftel. Avro vagy Protobuf jobb választás, ha több csapat, sok verzió és erős kompatibilitási igény van.

2.6 Delivery semantics

Szint Jelentés Megjegyzés
At-most-once Elveszhet, de nem duplikálódik Ritkán vállalható
At-least-once Lehet duplikáció A leggyakoribb production modell
Exactly-once Egyszer és csak egyszer Drága, összetett, kontextusfüggő

3. Gyakorlati használat / Practical Usage

Tipikus production use case egy webshop. Az order-service nem akar minden downstream rendszert HTTP-n sorban hívogatni. Inkább publikál egy OrderCreated eventet Kafka topicra. Erre reagálhat a payment, inventory, notification és analytics szolgáltatás külön életciklussal.

RabbitMQ akkor jó, ha klasszikus work queue vagy finom routing kell. Például email küldés, PDF generálás, backoffice feladatok, ahol fontos a per-message ack, TTL, priority vagy DLQ kezelés.

Request/reply pattern hasznos lehet, ha az egyik rendszer csak brokeren keresztül érhető el, de itt vigyázni kell: ez könnyen „RPC over messaging” szagot kap. Ha a hívás valójában synchronous üzleti függés, REST vagy gRPC sokszor tisztább.

Batch importoknál gyakori, hogy a beolvasott rekordok queue-ba kerülnek, a consumer pool pedig párhuzamosan dolgozza fel őket. Ilyenkor a backpressure-t a broker és a consumer concurrency együtt adja.

Financial vagy inventory jellegű folyamatoknál soha ne bízz kizárólag a broker megbízhatóságában. Idempotens feldolgozás, deduplikációs kulcs, outbox pattern és üzleti audit együtt adnak robusztus megoldást.

4. Kód példák / Code Examples

4.1 Kafka producer és listener

@Configuration
class KafkaConfig {

    @Bean
    ProducerFactory<String, OrderCreatedEvent> producerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
        return new DefaultKafkaProducerFactory<>(props);
    }

    @Bean
    KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate(
            ProducerFactory<String, OrderCreatedEvent> producerFactory) {
        return new KafkaTemplate<>(producerFactory);
    }
}

@Service
class OrderEventPublisher {
    private final KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate;

    OrderEventPublisher(KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publish(OrderCreatedEvent event) {
        kafkaTemplate.send("orders.created", event.orderId(), event);
    }
}

@Component
class BillingListener {

    @KafkaListener(topics = "orders.created", groupId = "billing-service")
    public void onOrderCreated(OrderCreatedEvent event,
                               @Header(KafkaHeaders.RECEIVED_KEY) String key) {
        // idempotens feldolgozás orderId alapján
        System.out.println("Processing order " + key);
    }
}

record OrderCreatedEvent(String orderId, BigDecimal totalAmount, Instant createdAt) {}

4.2 Kafka error handling és DLT

@Bean
ConcurrentKafkaListenerContainerFactory<String, OrderCreatedEvent> kafkaListenerContainerFactory(
        ConsumerFactory<String, OrderCreatedEvent> consumerFactory,
        KafkaTemplate<String, Object> template) {

    var factory = new ConcurrentKafkaListenerContainerFactory<String, OrderCreatedEvent>();
    factory.setConsumerFactory(consumerFactory);

    var recoverer = new DeadLetterPublishingRecoverer(template,
            (record, ex) -> new TopicPartition(record.topic() + ".DLT", record.partition()));

    factory.setCommonErrorHandler(new DefaultErrorHandler(recoverer, new FixedBackOff(1000L, 3)));
    return factory;
}

4.3 RabbitMQ exchange, queue, binding

@Configuration
class RabbitConfig {

    @Bean
    TopicExchange notificationExchange() {
        return new TopicExchange("notification.exchange");
    }

    @Bean
    Queue emailQueue() {
        return QueueBuilder.durable("notification.email")
                .deadLetterExchange("notification.dlx")
                .build();
    }

    @Bean
    Binding emailBinding(Queue emailQueue, TopicExchange notificationExchange) {
        return BindingBuilder.bind(emailQueue)
                .to(notificationExchange)
                .with("notification.email");
    }
}

@Component
class EmailListener {

    @RabbitListener(queues = "notification.email")
    public void handle(NotificationMessage message) {
        System.out.println("Sending email to " + message.recipient());
    }
}

record NotificationMessage(String recipient, String subject, String body) {}

4.4 Transactional outbox gondolat

@Service
class OrderService {

    @Transactional
    public void createOrder(CreateOrderCommand command) {
        Order order = orderRepository.save(Order.from(command));
        outboxRepository.save(OutboxMessage.forTopic(
                "orders.created",
                order.getId().toString(),
                JsonUtils.toJson(new OrderCreatedEvent(order.getId().toString(), order.total(), Instant.now()))
        ));
    }
}

5. Trade-offok / Trade-offs

Előnyök:

  • laza csatolás szolgáltatások között
  • jobb terheléskezelés és async feldolgozás
  • replay és audit lehetőség Kafka esetén
  • rugalmas routing RabbitMQ esetén

Hátrányok:

  • eventual consistency
  • nehezebb debugging és tracing
  • duplikáció és ordering problémák
  • operációs overhead: broker, schema, monitoring

Mikor használd?

  • ha a feldolgozás nem kell azonnal a felhasználói válaszba
  • ha több downstream consumer van
  • ha burst terhelést kell kisimítani

Mikor ne?

  • ha egyszerű synchronous CRUD kapcsolat kell
  • ha az üzleti folyamat erősen request/response jellegű
  • ha a csapat még nem tudja üzemeltetni a messaging infrastruktúrát

6. Gyakori hibák / Common Mistakes

  1. HTTP-t lecserélni mindenhol messagingre. Nem minden kommunikáció event. Sok esetben csak felesleges bonyolítás.
  2. Idempotencia hiánya. At-least-once delivery mellett ugyanaz az üzenet többször érkezhet.
  3. Kulcs nélküli Kafka üzenetek ordering elvárással. Ha fontos az ordering entitásonként, használd ugyanazt a partition key-t.
  4. DLQ mint szemetes. A dead letter queue nem végállomás, hanem diagnosztikai és újrafeldolgozási eszköz.
  5. Nagy payloadok küldése. Fájl vagy nagy blob helyett inkább objektumtár-referencia menjen üzenetben.
  6. Schema verziózás elhanyagolása. Consumer töréshez és rollout kockázathoz vezet.

7. Senior szintű meglátások / Senior-level Insights

A production messaging nem arról szól, hogy „el tudok küldeni egy JSON-t”. Hanem arról, hogy mi történik részleges hibánál, deploy közben, duplikációnál, consumer lag esetén vagy hónapokkal később replay-nél.

Senior szemmel a legfontosabb döntések:

  • mi az üzenet szerződése és ki a tulajdonosa;
  • milyen delivery semantics kell üzletileg, nem technikailag;
  • lehet-e a consumert idempotenssé tenni;
  • kell-e outbox pattern a DB és broker közötti atomikusság közelítésére;
  • hogyan mérjük a lagot, retry-t, poison message arányt.

Kafka erős event backbone, de nem varázslat. Exactly-once tipikusan csak a Kafka ökoszisztémán belül értelmezhető szépen. Ha adatbázis is része a folyamatnak, az üzleti exactly-once már inkább outbox + idempotens consumer kombináció.

RabbitMQ sokszor jobb operatív workflow engine jellegű feladatokra. Rövidebb életű taskokhoz, finom routinghoz, TTL-hez, priority-hoz és klasszikus work queue modellekhez nagyon kényelmes.

8. Szószedet / Glossary

  • Broker: köztes rendszer, ami fogadja és továbbítja az üzeneteket.
  • Topic: Kafka logikai csatorna, partíciókkal.
  • Queue: RabbitMQ vagy általános üzenetsor fogyasztásra.
  • Partition: Kafka topic feldarabolt egysége orderinggel.
  • Offset: consumer pozíciója egy partíción belül.
  • Consumer group: együtt dolgozó consumer példányok halmaza.
  • DLQ/DLT: hibás üzenetek elkülönített célhelye.
  • Idempotencia: többszöri feldolgozás ugyanazt az eredményt adja.
  • Outbox pattern: domain változás és event publikálás biztonságos összekötése.

9. Gyorsreferencia / Cheatsheet

Téma Kafka RabbitMQ Tipp
Fő modell log + replay queue + routing válassz use case alapján
Skálázás partition consumer/prefetch mérd a lagot
Ordering partitionön belül queue fogyasztási sorrend key fontos
DLQ DLT topic DLQ queue ne felejts monitorozni
Serialization JSON / Avro JSON / binary schema governance kell
Spring API KafkaTemplate, @KafkaListener RabbitTemplate, @RabbitListener config legyen explicit
Jó gyakorlat idempotens consumer retry + DLQ outbox sokat segít

🎮 Játékok

8 kérdés