📨🧠 Kafka in Microservices Architecture – Khi Hệ Thống Biết Giao Tiếp Bằng Sự Kiện - Kafka 101 P16
Kafka in Microservices Architecture – Khi Hệ Thống Biết Giao Tiếp Bằng Sự Kiện
Hãy tưởng tượng bạn đang vận hành một hệ thống thương mại điện tử lớn. Khi khách hàng nhấn nút "Đặt hàng", ứng dụng OrderService (Dịch vụ Đơn hàng) sẽ ghi nhận thông tin vào database của nó, sau đó gọi một yêu cầu HTTP REST API đồng bộ sang InventoryService (Dịch vụ Kho) để giữ hàng, rồi tiếp tục gọi BillingService (Dịch vụ Thanh toán) để trừ tiền.
Đột nhiên, đường truyền mạng kết nối đến InventoryService bị chập chập chờn. Lời gọi HTTP bị timeout.
Lúc này, hệ thống rơi vào trạng thái lấp lửng: Đơn hàng đã được lưu trên cơ sở dữ liệu của OrderService, tiền của khách hàng có thể đã bị trừ tại BillingService, nhưng kho hàng lại không hề ghi nhận việc giữ hàng. Dữ liệu giữa các dịch vụ bị lệch pha nghiêm trọng. Khách hàng đã trả tiền nhưng không có hàng để giao.
Bạn vội vã cố gắng viết thêm code xử lý ngoại lệ (Exception Handling) và cơ chế gọi lại (Retry logic) bằng HTTP. Tuy nhiên, nó chỉ làm tăng thời gian phản hồi (latency) của API và trực tiếp biến các sự cố tạm thời thành hiện tượng nghẽn luồng (Thread exhaustion) sập toàn bộ các microservices liên quan.
Đây chính là lúc bạn cần thay đổi hoàn toàn tư duy thiết kế: Chuyển từ giao tiếp đồng bộ, ràng buộc chặt (Tight Coupling) sang giao tiếp bất đồng bộ, hướng sự kiện (Event-Driven Architecture) lấy Apache Kafka làm xương sống.
1. Niềm tin phổ biến: "Cứ đưa Kafka vào là hệ thống tự động hướng sự kiện"
Khi chuyển đổi sang kiến trúc Microservices, Kafka thường được coi là chiếc "đũa thần" giải quyết mọi bài toán giao tiếp. Dẫn đến hai lầm tưởng kinh điển:
- Cứ dùng Kafka là hệ thống tự động chuẩn Event-Driven: Chỉ cần cài đặt Kafka, chuyển đổi các api REST sang gửi tin nhắn qua Kafka Topic là hệ thống của bạn đã đạt được sự cô lập ranh giới (Decoupling) hoàn hảo.
- Có Kafka rồi thì khai tử hoàn toàn HTTP/gRPC: Khi đã xây dựng hệ thống event-driven, mọi giao tiếp giữa các service từ đầu đến cuối bắt buộc phải đi qua Kafka, không được phép dùng các API gọi trực tiếp.
Tại sao niềm tin này nghe có vẻ hợp lý?
Kafka hỗ trợ cơ chế Publish/Subscribe bất đồng bộ cực kỳ mạnh mẽ. Việc thay thế lời gọi REST API bằng việc gửi một message tạo ra cảm giác các service không còn "biết" về nhau nữa. Hơn nữa, các bài viết công nghệ thường ca ngợi Event-Driven như một đỉnh cao kiến trúc tối thượng, khiến lập trình viên có xu hướng cực đoan hóa, loại bỏ hoàn toàn các giao thức đồng bộ.
Tại sao nó lại đứt gãy trong môi trường thực tế?
Nếu bạn đưa Kafka vào hệ thống nhưng vẫn thiết kế message theo kiểu yêu cầu-phản hồi (Request-Response) giả lập – tức là Service A gửi message yêu cầu ghi dữ liệu vào Topic 1, rồi đứng im chờ đợi một message kết quả trả về từ Service B ở Topic 2 – thì hệ thống của bạn thực chất vẫn là giao tiếp đồng bộ bị bọc trong lớp vỏ bất đồng bộ. Bạn không những không đạt được decoupling, mà còn tự gánh thêm độ trễ mạng cực lớn của Kafka và độ phức tạp khi quản lý trạng thái luồng.
Ngược lại, việc loại bỏ hoàn toàn HTTP/gRPC cũng là một sai lầm. Với các tác vụ yêu cầu kết quả tức thời (như xác thực người dùng đăng nhập - Authentication, hoặc kiểm tra trạng thái tồn kho thời gian thực trước khi hiển thị lên giao diện người dùng), giao tiếp đồng bộ qua gRPC/HTTP vẫn là sự lựa chọn tối ưu và duy nhất phù hợp.
2. Thiết kế Event-Driven Microservices chuẩn chỉ với Kafka
Trong một hệ thống hướng sự kiện thực thụ, các service giao tiếp với nhau bằng cách công bố các Sự kiện (Events) – tức là những tuyên bố về một sự thật đã diễn ra trong quá khứ (ví dụ: OrderCreated, PaymentFailed).
+-------------------+
| Order Service |
+-------------------+
|
(Publishes: OrderCreated)
v
+-------------------+
| Kafka Topic |
| ("order-events") |
+-------------------+
/ \
(Subscribes) / \ (Subscribes)
v v
+-------------------+ +-------------------+
| Inventory Service | | Billing Service |
+-------------------+ +-------------------+
OrderServicesau khi lưu đơn hàng vào database của nó chỉ cần bắn duy nhất một eventOrderCreatedlên Kafka Topic"order-events". Công việc của nó kết thúc tại đây. Nó không cần biết dịch vụ nào sẽ tiêu thụ event này.InventoryServicevàBillingServicechủ động lắng nghe (subscribe) Topic"order-events". Khi thấy eventOrderCreated, mỗi service tự thực hiện nghiệp vụ của mình (trừ kho, trừ tiền) một cách độc lập và theo nhịp độ xử lý của riêng mình.- Nếu
InventoryServicetạm thời bị sập, Kafka Broker sẽ lưu trữ event đó một cách bền vững. Khi service này sống lại, nó tiếp tục đọc từ offset cũ và xử lý tiếp mà không làm mất mát bất kỳ thông tin nào của khách hàng.
3. Giải quyết giao dịch phân tán: Saga Pattern với Kafka
Khi không còn cơ chế khóa database tập trung (distributed lock/2PC vì chúng quá chậm và không chịu tải nổi), làm thế nào để đảm bảo tính nhất quán dữ liệu xuyên suốt nhiều microservices? Chúng ta sử dụng Saga Pattern – chia chuỗi giao dịch lớn thành một loạt các giao dịch cục bộ độc lập.
Kafka là hạ tầng hoàn hảo để điều phối Saga thông qua hai mô hình thiết kế:
A. Mô hình Choreography Saga (Điều phối phân tán)
Mỗi service tham gia vào Saga tự lắng nghe event của service trước đó, thực hiện hành động nghiệp vụ của mình, rồi bắn ra event mới để kích hoạt service tiếp theo.
[Order Service] -- OrderCreated --> [Inventory Service]
^ |
| ReserveStockOK
| v
[Confirm Order] <-- ChargePaymentOK <-- [Billing Service]
- Ưu điểm: Đơn giản, dễ thiết lập cho các luồng nghiệp vụ ngắn (2-3 bước), không có điểm nghẽn trung tâm (no single point of failure).
- Cạm bẫy thực chiến (Failure Case): "Saga Choreography Spaghetti". Khi luồng nghiệp vụ phình to lên 7-10 bước với nhiều nhánh rẽ điều kiện, các service sẽ tự bắn và nghe chéo event của nhau tạo thành một mạng lưới dependency rối rắm như một đĩa mì spaghetti. Không một ai trong dự án có thể nhìn thấy toàn cảnh luồng đi của dữ liệu, việc debug khi xảy ra lỗi rollback cực kỳ khó khăn.
B. Mô hình Orchestration Saga (Điều phối tập trung)
Chúng ta xây dựng một service chuyên biệt đóng vai trò điều phối trung tâm gọi là Saga Orchestrator. Orchestrator chứa toàn bộ máy trạng thái (State Machine) của giao dịch.
+-------------------------+
| Saga Orchestrator |
+-------------------------+
/ | \
ReserveStockCmd ChargeCmd ConfirmCmd
/ | \
v v v
[Inventory] [Billing] [Order]
- Orchestrator sẽ gửi các Chỉ thị (Commands) (ví dụ:
ReserveStock,ChargePayment) vào các Kafka topic riêng biệt cho từng service. - Các service thực hiện xong sẽ bắn event phản hồi về một topic chung của Orchestrator.
- Nếu một bước gặp lỗi (ví dụ: thẻ tín dụng bị từ chối), Orchestrator sẽ chủ động phát đi các chỉ thị bù trừ (Compensating Transactions) để hoàn tác (rollback) dữ liệu của các bước trước đó (ví dụ: trả lại hàng vào kho).
- Đánh đổi: Tăng độ phức tạp khi phải xây dựng state machine, nhưng mang lại khả năng kiểm soát luồng nghiệp vụ tuyệt đối cho các hệ thống lớn.
4. Quản lý hợp đồng dữ liệu với Schema Registry
Khi hệ thống có hàng chục nhóm phát triển viết các microservices khác nhau và cùng chia sẻ dữ liệu qua Kafka, việc cấu trúc dữ liệu bị thay đổi đột ngột có thể làm sập toàn bộ các consumer ở hạ tầng phía sau.
Nếu bạn chỉ dùng định dạng JSON thuần túy, một ngày nọ team phát triển OrderService đổi tên trường amount thành total_amount mà không báo trước. Lập tức, Consumer của BillingService đọc dữ liệu bị lỗi NullPointerException và crash hàng loạt.
Để giải quyết vấn đề này, chúng ta bắt buộc phải sử dụng Schema Registry kết hợp với các định dạng serialization có cấu trúc chặt chẽ như Apache Avro hoặc Protobuf.
Producer (App)
| -- (1) Check/Register Schema V2 --> [ Schema Registry ]
v |
Serialize Msg Store Schema
| |
+-------- (2) Send Msg (Avro payload) -------> +--------> Kafka Broker
|
(Raw binary)
|
v
Consumer (App) <--- (3) Fetch Schema V2 (if not cached) -----------+
- Cơ chế hoạt động: Trước khi Producer gửi message đi, nó gửi cấu trúc (schema) của message lên Schema Registry để kiểm tra tính tương thích (Compatibility rules). Nếu schema mới tương thích ngược (Backward Compatible) với schema cũ, Registry sẽ chấp nhận và trả về một ID duy nhất. Producer sẽ đính kèm ID này vào đầu message nhị phân (binary payload) gửi lên Kafka.
- Consumer khi nhận message nhị phân từ Kafka, nó nhìn vào ID schema ở đầu tin nhắn để truy vấn Schema Registry lấy về cấu trúc tương ứng để deserialize dữ liệu. Điều này đảm bảo các Consumer cũ vẫn đọc được dữ liệu phiên bản mới một cách an toàn mà không bị crash.
5. Idempotency – Nguyên tắc đạo đức trong thiết kế hệ thống hướng sự kiện
Do đặc tính phân tán và cơ chế truyền tin At-Least-Once Delivery mặc định của Kafka, một message hoàn toàn có thể bị gửi lặp lại nhiều lần.
Tình huống: Producer gửi thành công message ChargePayment sang Broker, nhưng do mạng chập chờn, gói tin ACK phản hồi từ Broker về Producer bị mất. Producer giả định việc gửi tin thất bại, nó kích hoạt cơ chế retry gửi lại message đó một lần nữa.
- Hậu quả: Consumer của
BillingServicesẽ nhận được 2 message giống hệt nhau. Nếu không được thiết kế tốt, tài khoản của khách hàng sẽ bị trừ tiền hai lần cho cùng một đơn hàng.
Do đó, Idempotency (Tính khả đơn) là yêu cầu bắt buộc đối với mọi Consumer:
- Giải pháp 1: Bảng kiểm tra Idempotency (Deduplication Table). Trong mỗi message gửi đi, hãy luôn đính kèm một ID duy nhất mang tính nghiệp vụ (ví dụ:
event_idhoặcorder_id). Trước khi Consumer thực hiện nghiệp vụ, nó ghi ID này vào một bảng có khóa chính (Primary Key/Unique Constraint) trong database. Nếu ghi thành công, tiếp tục xử lý. Nếu gặp lỗi trùng khóa chính, bỏ qua message đó vì nó đã được xử lý trước đó. - Giải pháp 2: Sử dụng các thao tác idempotent tự nhiên. Ví dụ, thay vì gửi command dạng tăng/giảm giá trị tương đối (
quantity = quantity - 1), hãy thiết kế command thiết lập giá trị tuyệt đối (set_quantity = 5).
6. Đánh đổi (Trade-offs): Eventual Consistency vs Strong Consistency
Khi chuyển sang kiến trúc Microservices hướng sự kiện với Kafka, bạn phải chấp nhận một thực tế phũ phàng: Sự nhất quán nhất thời (Eventual Consistency).
[Strong Consistency] [Eventual Consistency]
- Dữ liệu luôn đúng tức thời - Dữ liệu đồng bộ sau một khoảng trễ
- Độ trễ cao, khó scale ngang - Độ trễ cực thấp, khả năng scale vô hạn
Khi khách hàng nhấn đặt hàng thành công, giao diện có thể hiển thị trạng thái "Đơn hàng đang xử lý". Hệ thống sẽ mất vài trăm mili giây đến vài giây để đồng bộ dữ liệu qua các topic Kafka đến kho và cổng thanh toán. Trong khoảng thời gian này, dữ liệu ở các service không đồng nhất với nhau.
Nếu doanh nghiệp của bạn yêu cầu dữ liệu phải tuyệt đối chính xác ngay lập tức tại mọi điểm nhìn (như các giao dịch ngân hàng cốt lõi - Core Banking), việc áp dụng Event-Driven một cách vội vã có thể mang lại nhiều rủi ro hơn là lợi ích. Bạn phải cân bằng giữa khả năng chịu tải, tốc độ hệ thống và tính toàn vẹn tức thời của dữ liệu để đưa ra quyết định kiến trúc phù hợp.
7. Lời kết và chúc mừng hoàn thành chặng đường Kafka!
Chúng ta đã cùng đi qua một hành trình dài 16 tập của series Apache Kafka: từ những khái niệm hạt nhân ban đầu như Partition, Offset, cho đến những chi tiết cơ học sâu thẳm của Sequential I/O, Page Cache, các cơ chế nhân bản dữ liệu ISR, giao dịch Transactional API, CDC, hệ thống quan sát Observability, quy trình nâng cấp hạ tầng Rolling Upgrade và hôm nay là các mô hình thiết kế hệ thống hướng sự kiện Microservices.
Kafka không đơn thuần là một công cụ công nghệ để thêm vào CV xin việc. Kafka là một triết lý thiết kế hệ thống bắt buộc chúng ta phải thay đổi cách nhìn nhận về dòng chảy của dữ liệu thời gian thực.
Chúc mừng bạn đã hoàn thành trọn vẹn hành trình 16 tập! Hãy tiếp tục nâng cấp tư duy thiết kế hệ thống của mình thông qua các chặng đường tiếp theo của TechCraft.
Hẹn gặp lại bạn trong các chuỗi bài viết chuyên sâu về Cloud Architecture và Database System Design!
🧭 Học theo lộ trình
TechCraft không hướng tới việc chia sẻ những mẹo kỹ thuật rời rạc.
Mục tiêu của TechCraft là xây dựng một lộ trình học giúp Developer từng bước phát triển từ người biết implement feature thành người có thể thiết kế, vận hành và mở rộng các hệ thống production.
Nếu bạn muốn tiếp tục hành trình đó, Dev Insider sẽ là điểm đến tiếp theo.
🚀 Dev Insider https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113
📘 Facebook https://www.facebook.com/techcraft.official
🎥 YouTube https://www.youtube.com/@techcraft.official
🎵 TikTok https://www.tiktok.com/@techcraft.official
Hiểu hệ thống. Không chỉ framework.
All rights reserved