Backend

99 Ngày Spring — Ngày 78: Kafka với Spring

SSite Admin
13 tháng 10, 2026 2 phút đọc 7 lượt xem
99 Ngày Spring — Ngày 78: Kafka với Spring

Kafka ghi sự kiện vào topic phân vùng; producer gửi, consumer group đọc theo offset. Spring Kafka giảm phần cấu hình Java nhưng không thay đổi hợp đồng phân tán: message có thể được giao lại, schema phải tiến hóa và lỗi cần đường xử lý rõ. Ví dụ dùng Spring Boot 3.x và Java 21.

Kafka với Spring

1. Producer và key phân vùng

KafkaTemplate gửi bất đồng bộ và trả CompletableFuture. Dùng orderId làm key để các sự kiện của cùng đơn thường vào một partition và giữ thứ tự trong partition đó. Chờ future hoặc xử lý callback để biết broker xác nhận hay lỗi; gọi send() rồi bỏ qua kết quả không bảo đảm sự kiện đã được ghi.

record OrderPlaced(String eventId, String orderId) {}
CompletableFuture<SendResult<String, OrderPlaced>> sent =
    template.send("orders.placed", event.orderId(), event);
sent.whenComplete((result, error) -> {
    if (error != null) log.error("publish failed", error);
});

2. Consumer, group và idempotency

@KafkaListener đọc record trong một consumer group; mỗi partition được một member của group xử lý tại một thời điểm. Xử lý thành công rồi mới commit offset theo cấu hình container. Nếu DB ghi xong nhưng offset chưa commit, record có thể đến lại. Lưu eventId trong bảng có unique constraint cùng transaction nghiệp vụ để xử lý trùng an toàn.

@KafkaListener(topics = "orders.placed", groupId = "billing")
void onOrder(OrderPlaced event) {
    billingService.applyOnce(event.eventId(), event.orderId());
}

3. Serialization là một hợp đồng

Producer và consumer phải thống nhất kiểu dữ liệu, field và phiên bản. JSON đủ cho demo nhưng cần kiểm soát type headers hoặc cấu hình trusted packages khi đọc từ nguồn khác; không deserialise class tùy ý. Trong hệ thống lớn, cân nhắc schema registry và quy tắc tương thích. Đừng đổi tên/xóa field mà chưa nghĩ tới consumer cũ.

spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.value.default.type=com.example.OrderPlaced
spring.kafka.consumer.properties.spring.json.trusted.packages=com.example

4. Retry và dead-letter topic

Lỗi tạm thời có thể retry với backoff. Record hỏng vĩnh viễn cần đi vào DLT kèm ngữ cảnh để sửa và phát lại có kiểm soát. DefaultErrorHandler với DeadLetterPublishingRecoverer là một cách; @RetryableTopic dùng các topic retry riêng. Với batch listener, non-blocking retries không hỗ trợ như record listener. Theo dõi DLT và lag; DLT không phải nơi quên lỗi.

@Bean
DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) {
    var recoverer = new DeadLetterPublishingRecoverer(template);
    return new DefaultErrorHandler(recoverer, new FixedBackOff(1000L, 2L));
}

Kafka cho luồng sự kiện bền vững, nhưng không tự làm nghiệp vụ exactly once. Ngày 79 so sánh RabbitMQ với Kafka và dựng luồng hàng đợi theo routing key.

Tài liệu đối chiếu

  • https://docs.spring.io/spring-kafka/reference/kafka/sending-messages.html

  • https://docs.spring.io/spring-kafka/reference/kafka/annotation-error-handling.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 79: RabbitMQ với Spring

Exchange, queue, binding, @RabbitListener, retry/DLX và cách chọn RabbitMQ hay Kafka.

14 thg 10, 20262 phút5
99 Ngày Java — Ngày 79: JVM Flags và Tuning cơ bản

-Xms/-Xmx, chọn GC, GC log, heap dump và quy trình điều tra OutOfMemoryError.

14 thg 10, 20262 phút4
99 Ngày Java — Ngày 78: Bytecode và javap

Đọc javap -c -p -v, operand stack, constant pool và những gì compiler thật sự tạo ra.

13 thg 10, 20262 phút6