Hàng đợi tin nhắn và Event-Driven thực chiến cho backend
Sê-ri nâng cao về message queue và kiến trúc event-driven cho lập trình viên backend: ngữ nghĩa giao nhận, consumer group, idempotency, outbox, dead letter, consumer lag, ordering, exactly-once — mỗi bài demo THẬT trên Kafka (KRaft) + Go + Redis trong Docker, đo số liệu thật.
12/12 phần đã đăng
Lập trình
1
Vì sao cần hàng đợi tin nhắn: đo thật việc tách rời producer khỏi consumer theo thời gian
Gọi service trực tiếp thì caller bị chặn tới khi downstream xong — và sập theo nếu nó chết. Hàng đợi đảo ngược điều đó. Bài này đo thật trên Kafka: producer đẩy 100.000 message ở 377.000 msg/s, chỉ chờ broker ack 1.49ms mà KHÔNG cần consumer nào tồn tại; 100.000 message nằm chờ an toàn trong topic; rồi một consumer đến muộn đọc hết và bắt kịp về lag 0. Đó là tách rời theo thời gian.
22/09/2026
· 7 phút đọc
2
At-most-once, at-least-once, exactly-once: đo thật message bị mất và bị trùng
Ba ngữ nghĩa giao nhận nghe trừu tượng cho tới khi bạn mất tiền của khách vì xử lý đơn hai lần. Bài này chạy thật trên Kafka với 1000 message đánh số: at-least-once cho đọc lại khi chưa commit — trùng đúng 1000; at-most-once dời offset vượt trước xử lý — mất đúng 1000 (mà LAG vẫn báo 0, 'khoẻ' giả); idempotent producer ghi đúng 1000 không trùng. Khác biệt nằm gọn ở chỗ commit offset trước hay sau khi xử lý.
22/09/2026
· 7 phút đọc
3
Consumer group và partition: mở rộng throughput, và vì sao thêm consumer có trần
Consumer xử lý không kịp? Thêm consumer. Nhưng không phải thêm bao nhiêu cũng nhanh hơn. Bài này chạy thật trên Kafka topic 6 partition: 3 consumer trong một group chia đều mỗi cái 2 partition; nhưng khi lên 7 consumer trên 6 partition, consumer thứ 7 nhận đúng 0 partition — ngồi không, lãng phí. Partition là đơn vị song song thật, và nó là trần cứng của việc scale consumer.
22/09/2026
· 7 phút đọc
4
Idempotency: xử lý message trùng mà vẫn đúng — demo trừ tiền hai lần và cách chặn
At-least-once chắc chắn sẽ giao message trùng. Nếu consumer cứ thế xử lý, khách bị trừ tiền hai lần. Bài này chạy thật: 1000 thanh toán được Kafka giao 2000 lượt (1000 trùng); consumer ngây thơ cộng ra 200.000 — sai gấp đôi; consumer idempotent dùng redis SETNX khử trùng theo id, chỉ xử lý 1000 lượt duy nhất, ra đúng 100.000. At-least-once + idempotency an toàn như exactly-once mà rẻ hơn.
22/09/2026
· 7 phút đọc
5
Outbox pattern: vì sao ghi database rồi publish message là cái bẫy mất dữ liệu
Service ghi đơn hàng vào database rồi publish message sang Kafka — hai bước riêng. Nếu crash giữa hai bước, database có đơn mà message biến mất. Bài này chạy thật: dual-write để lệch 40/100 đơn; outbox pattern ghi business row và outbox row trong cùng một transaction PostgreSQL, relay đọc và publish — khớp 100%, và relay chạy lại vẫn an toàn. Chìa khoá là tính nguyên tử của database.
22/09/2026
· 7 phút đọc
6
Dead Letter Queue: một message độc không được phép giữ con tin cả hàng đợi
Một message hỏng không xử lý nổi mà consumer cứ retry mãi sẽ chặn đứng mọi message sau nó. Bài này chạy thật trên Kafka: 10 message với 2 cái độc ở vị trí 3 và 7 — consumer không có DLQ kẹt tại message độc đầu tiên, chỉ xử lý được 2/10; consumer có DLQ tách 2 message độc sang topic riêng sau vài lần retry, xử lý trọn 8 message tốt và hàng đợi không kẹt.
22/09/2026
· 8 phút đọc
7
Consumer lag: tín hiệu cảnh báo số một của hệ message queue, đo thật qua offset
Hệ message queue có khoẻ không? Đừng nhìn CPU hay throughput — nhìn consumer lag. Bài này đo thật trên Kafka: backlog 200.000 message, consumer chậm, producer thêm 100.000 — lag vọt từ 150.000 lên 250.000 dù consumer vẫn chạy, rồi về 0 khi bắt kịp. LAG = LOG-END-OFFSET trừ CURRENT-OFFSET, và nó là chỉ báo sớm nhất rằng hệ sắp không theo kịp.
22/09/2026
· 7 phút đọc
8
Thứ tự message trong Kafka: vì sao scale ra nhiều partition làm lộn thứ tự, và key cứu thế nào
Kafka đảm bảo thứ tự — nhưng chỉ trong một partition, không phải toàn topic. Bài này chạy thật: một partition giữ đúng thứ tự ev-1..10; ba partition không key làm thứ tự toàn topic lộn tung (ev-3, ev-8, ev-7...); nhưng dùng key theo user thì mọi event của cùng một user vào cùng partition và giữ nguyên thứ tự. Đây là đánh đổi cốt lõi giữa song song và thứ tự.
22/09/2026
· 7 phút đọc
9
Event sourcing: lưu chuỗi sự kiện thay vì trạng thái, và dựng lại mọi thứ bằng replay
Thay vì lưu 'số dư = 200' rồi ghi đè mỗi lần đổi, event sourcing lưu từng sự kiện (nạp 100, rút 30, nạp 50...) và tính trạng thái bằng cách replay chúng. Bài này chạy thật trên Kafka: replay toàn bộ event log dựng lại balance 200, time-travel về quá khứ cho balance 100 tại thời điểm cũ, thêm event rồi replay vẫn đúng 700, và toàn bộ 7 sự kiện còn nguyên làm audit. Đổi lại là chi phí replay.
22/09/2026
· 8 phút đọc
10
Saga pattern: giao dịch phân tán khi không có transaction chung, demo rollback bằng bù trừ
Đặt một đơn hàng cần trừ kho, trừ tiền, tạo đơn — ở ba service khác nhau, không có transaction chung để 'commit cả ba hoặc không gì'. Saga giải quyết bằng chuỗi bước cục bộ, mỗi bước có bước bù trừ. Bài này chạy thật trên PostgreSQL: saga thành công trừ kho/tiền và tạo đơn khớp; saga lỗi ở bước tiền tự hoàn kho đã trừ và không tạo đơn, đưa hệ về đúng trạng thái ban đầu.
22/09/2026
· 8 phút đọc
11
Schema evolution: tiến hoá message mà không làm vỡ consumer đang chạy
Producer và consumer deploy không đồng bộ — producer gửi message phiên bản mới trong khi consumer cũ còn chạy, và ngược lại. Bài này chạy thật bằng Go: thêm field optional thì consumer mới đọc message cũ vẫn ổn (currency mặc định VND), consumer cũ đọc message mới thì bỏ qua field lạ; nhưng đổi kiểu field cho lỗi parse to, còn đổi tên field gây mất dữ liệu âm thầm (amount=0 mà không báo lỗi). Quy tắc: chỉ thêm field optional.
22/09/2026
· 8 phút đọc
12
Tổng kết Message Queue & Event-Driven: khung quyết định và checklist từ 12 bài đo thật
Mười một phần qua ta dựng từng mảnh của hệ message queue bằng demo đo thật. Bài cuối ghép chúng lại: một pipeline hoàn chỉnh produce 500 order qua Kafka, giao trùng 1000 lượt theo at-least-once, rồi consumer idempotent xử lý đúng 500 — tất cả patterns làm việc cùng nhau. Kèm bảng tổng hợp mọi con số đo thật xuyên 12 bài và một khung quyết định chọn dùng pattern nào khi nào.
22/09/2026
· 6 phút đọc