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:
- A producer elküldi az üzenetet.
- A broker eltárolja vagy a megfelelő cél felé irányítja.
- 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:
- Producer elküldi az üzenetet.
- Exchange a bindingok alapján kiválasztja a cél queue-kat.
- Queue sorban tartja az üzeneteket.
- Consumer feldolgozza a kézbesített üzenetet.
Fő exchange típusok:
direct: routing key pontos egyezéstopic: wildcard alapú routingfanout: broadcastheaders: 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
- HTTP-t lecserélni mindenhol messagingre. Nem minden kommunikáció event. Sok esetben csak felesleges bonyolítás.
- Idempotencia hiánya. At-least-once delivery mellett ugyanaz az üzenet többször érkezhet.
- Kulcs nélküli Kafka üzenetek ordering elvárással. Ha fontos az ordering entitásonként, használd ugyanazt a partition key-t.
- DLQ mint szemetes. A dead letter queue nem végállomás, hanem diagnosztikai és újrafeldolgozási eszköz.
- Nagy payloadok küldése. Fájl vagy nagy blob helyett inkább objektumtár-referencia menjen üzenetben.
- 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