Cuộc tìm kiếm “Hệ tuần hoàn” cho Microservices

Khi quyết định chuyển đổi hệ thống lõi sang kiến trúc Event-Driven Microservices, chúng tôi hiểu rằng việc chọn Message Queue (MQ) cũng quan trọng như chọn “hệ tuần hoàn máu” cho cơ thể. Nếu máu chảy chậm, cả cơ thể sẽ lờ đờ. Nếu mạch máu vỡ, hệ thống sẽ sụp đổ.
Chúng tôi cần một giải pháp phải đáp ứng được Iron Triangle của phía Infra:
- Tốc độ cao (Low Latency): Phải xử lý hàng ngàn request/giây với độ trễ tính bằng mili-giây (đặc biệt trong các đợt mở bán vé Flash Sale).
- Vận hành đơn giản (Low Ops): Không cần một đội ngũ 5 người chỉ để trực maintain cluster.
- Tin cậy tuyệt đối (Reliability): Không được mất message dù chỉ một.
Sau khi cân nhắc kỹ lưỡng giữa Kafka (rất mạnh nhưng cồng kềnh và tốn resource vận hành) và RabbitMQ (logic routing phức tạp, khó scale ngang), chúng tôi đã “đặt cược” vào NATS.
Tuy nhiên, công cụ tốt chỉ là 50% câu chuyện. 50% còn lại nằm ở tư duy kiến trúc để giải quyết các bài toán nghiệp vụ phức tạp.

Mô hình Event-Driven tại TechX Vietnam với NATS JetStream đóng vai trò “xương sống”
điều phối luồng dữ liệu.
Giải quyết bài toán Distributed Transaction bằng SAGA Pattern
Trong thế giới Monolith, ACID Transaction của Database xử lý mọi việc. Nhưng trong Microservices, “cái chết” của ACID Transaction là nỗi đau lớn nhất. Chúng tôi không thể COMMIT cùng lúc trên Database của Booking Service (Đặt vé) và Seat Inventory Service (Kho ghế).
Bài toán kinh điển:
- Khi người dùng bấm “Thanh toán”, ghế được giữ (Lock). Nếu thanh toán lỗi, làm sao “nhả” (Release) ghế ngay để người khác mua? (Đây là ví dụ mẫu vì hầu hết vấn đề khi làm microservice đều tương tự)
Chúng tôi kiên quyết nói KHÔNG với Two-Phase Commit (2PC) vì cơ chế locking của nó giết hiệu năng khi hàng nghìn người tranh một hạng vé.
Thay vào đó, chúng tôi áp dụng SAGA Pattern (dạng Choreography):
- Luồng xuôi (Happy Path): Service Booking bắn event
BookingCreated. NATS đẩy event này tới Service Seat (để Lock ghế) và Service Payment (để trừ tiền) song song. - Luồng ngược (Compensating Transaction): Phần thú vị nhất. Nếu Service Payment lỗi (ví dụ: thẻ hết hạn, không đủ số dư), nó bắn event
PaymentFailed. Service Seat ngay lập tức nghe event này và thực thi logic “bù trừ”: Release (nhả) ghế về trạng thái trống (Available) ngay.

Cơ chế “Compensating Transaction”: Khi thanh toán thất bại, hệ thống tự động kích hoạt quy trình “nhả ghế” để hoàn tác, đảm bảo không có ghế ảo (Ghost Seats)
Nhờ tốc độ xử lý nhanh của NATS, các bước “bù trừ” diễn ra gần như tức thì. Trải nghiệm người dùng (UX) được đảm bảo: ghế được trả về kho vé ngay lập tức cho người đến sau, tối ưu doanh thu sự kiện.
Vũ khí bí mật: NATS JetStream – Chiếc “hộp đen” an toàn
Nhiều kỹ sư ngại dùng NATS vì bản Core NATS hoạt động theo cơ chế Fire-and-Forget – rất nhanh nhưng nếu Consumer offline thì tin sẽ mất.
Đó là lý do chúng tôi nâng cấp lên JetStream.
Nếu Core NATS giống như xem Live TV (bạn đi vệ sinh là lỡ mất pha ghi bàn), thì JetStream giống như Netflix (bạn có thể pause, tua lại bất cứ lúc nào). JetStream lưu trữ message bền vững trên đĩa cứng.

JetStream lưu trữ message bền vững, cho phép Replay để xử lý lại sự cố bất cứ lúc nào.
Trong kiến trúc SAGA của chúng tôi, JetStream giữ vai trò quan trọng:
- Zero Data Loss (At-least-once Delivery): Đảm bảo mọi event quan trọng (như SeatLocked) đến đích. Nếu Worker xử lý vé bị crash, JetStream tự động gửi lại message cho Worker khác sau timeout.
- Replay & Debug: Khi phát sinh lỗi logic trên Production (ví dụ: lỗi tính giá vé), chúng tôi có thể “tua ngược thời gian”, yêu cầu JetStream phát lại toàn bộ event log đợt mở bán để tái hiện lỗi và sửa bug mà không làm sai lệch dữ liệu hiện tại.
Pro Tip: Kèm khả năng Replay/Retry, tất cả Consumer đều phải đảm bảo tính Idempotency (đẳng cấu). Nghĩa là dù nhận 1 hay 10 message PaymentSuccess giống nhau (do retry), vé chỉ được xuất 1 lần.
Dưới đây là cách chúng tôi publish event đặt vé bất đồng bộ (Async) vào JetStream bằng Go để tối ưu I/O:
// Ví dụ publish một Booking Event
js, _ := nc.JetStream()
bookingPayload := BookingEvent{
ID: "BKG-2024-XAE12",
SeatID: "A12",
EventID: "EVT-EDM-HCMC"
}
data, _ := json.Marshal(bookingPayload)
// Publish Async để không block main thread
future, _ := js.PublishAsync("BOOKINGS.created", data)
select {
case <-future.Ok():
// Message đã được JetStream xác nhận lưu thành công
log.Println("Booking request stored safely in JetStream!")
case err := <-future.Err():
// Xử lý lỗi (Retry hoặc Log)
log.Printf("Failed to publish booking: %v", err)
}
Kỷ luật “Payload Diet”: Bí quyết để hệ thống luôn nhanh
Hệ thống nhanh hay chậm không chỉ do công cụ mà còn do cách sử dụng. Một chiếc Ferrari (NATS) không thể chạy nhanh nếu phải chở cả tấn gạch đá.
Tại TechX Vietnam, chúng tôi thiết lập “luật sắt” về kích thước message (Payload Size). Dù NATS hỗ trợ payload lớn, chúng tôi giữ nó tối thiểu (Best practice dưới 1MB, giới hạn cứng 8MB cho trường hợp đặc biệt).
Tại sao? Đồ thị dưới đây minh họa ảnh hưởng của Message Size đến độ trễ (Latency).

Tương quan giữa kích thước Message và độ trễ: Giữ Payload nhỏ gọn là chìa khóa của High Performance.
Chiến thuật của chúng tôi: Thay vì gửi toàn bộ object EventDetails (bao gồm banner, sơ đồ ghế, danh sách nghệ sĩ…), chúng tôi chỉ gửi EventID, SeatID và các thông tin thay đổi (Delta). Các Service consumer sẽ tự quyết định truy vấn thêm dữ liệu khi cần. Cách làm này giảm tải băng thông và chi phí Serialization/Deserialization đáng kể.
Khả năng mở rộng (Scalability) với Queue Groups
Ngoài SAGA, chúng tôi tận dụng tính năng Queue Groups của NATS cho các tác vụ nặng như gửi Email điện tử và thống kê doanh thu real-time.

Queue Group đảm bảo message được phân tải đều cho các worker, giúp scale hệ thống chỉ bằng việc khởi tạo thêm instance.
Muốn xử lý nhanh hơn? Chỉ cần deploy thêm Pod Worker mới. NATS tự động nhận diện và chia tải sang Pod mới mà không cần cấu hình lại code hay khởi động lại hệ thống.
Kết luận
Sự kết hợp công nghệ mạnh mẽ (NATS JetStream), kiến trúc đúng đắn (SAGA Pattern) và kỷ luật vận hành (Payload Diet & Idempotency) giúp chúng tôi xây dựng hệ thống có độ decoupling cao, tin cậy và dễ mở rộng ngay cả trong đợt cao điểm traffic.
👉 Tại TechX Vietnam, chúng tôi không chỉ viết code, mà còn thiết kế giải pháp cho bài toán lớn.
Về danh sách Blog
Tuyển dụng
TechX Vietnam luôn tìm kiếm những thành viên giàu kinh nghiệm thực tế, có khát vọng phát triển, đam mê công nghệ, và biết trân trọng đồng nghiệp cũng như cuộc sống của chính mình.
Hiện tại, chúng tôi chưa có vị trí tuyển dụng nào đang mở, nhưng sẽ có kế hoạch tuyển dụng các vị trí mới theo sự phát triển của công ty.
Vui lòng kiểm tra lại thông tin tuyển dụng của chúng tôi trong thời gian tới.
Địa chỉ
Tầng 20, Tháp A, Toà nhà Viettel,
285 Cách Mạng Tháng Tám, Phường Hòa Hưng,
TP Hồ Chí Minh, Việt Nam
Giờ làm việc: 9:00 – 18:00
(Nghỉ Thứ Bảy, Chủ Nhật và các ngày Lễ)
