6 Event-Driven Architecture Patterns — Part 2
Bài tóm tắt Wix 2020 3. Quy mô lớn Độ khó: Nâng cao
Wix · 2020 · 3. Quy mô lớn · Độ khó: Nâng cao — bài gốc
Phần 2/2 (phần tiếp, không có trong sheet). Phần 1: Wix 2020.
Bối cảnh
Ba mẫu tiếp theo của team data-streams (Kafka cho hơn 1.400 microservice), dựng trên thư viện consumer Greyhound do Wix tự viết và mở mã nguồn.
Thay đổi chính
- Schedule and Forget (job định kỳ phải cuối cùng chạy xong):
- ví dụ gia hạn subscription thanh toán: job scheduler gọi REST endpoint của service Payments Subscriptions. Cách "poll DB thường xuyên tìm gia hạn chưa xong" cần lock lạc quan/bi quan vì hai request có thể cùng gia hạn một user;
- thay vào đó, đẩy request lên Kafka theo key userId: consumer xử lý tuần tự theo user nên không cần đồng bộ; retry ở consumer nên lịch chạy có thể thưa hơn nhiều.
- Cần giữ thứ tự thì dùng blocking retry (ngủ theo exponential backoff); lỗi kéo dài thì lag tăng, có dashboard để gỡ chặn và bỏ qua message.
- Không cần thứ tự thì non-blocking retry: mỗi khoảng retry một retry topic, header ghi độ trễ trước lần gọi tiếp; hết lượt thì vào dead-letter queue chờ người xem. Cơ chế này lấy cảm hứng từ một bài của Uber.
- Events in Transactions (khi khó làm idempotent):
- luồng mua hàng: Payments phát Order Purchase Completed → Checkout consume và phát Order Checkout Completed → Delivery, Inventory, Invoices consume. Nếu Checkout phát trùng thì giao hàng hai lần hoặc trừ kho sai, nên mọi consumer phía sau phải tự lưu trạng thái khử trùng;
- dùng transaction của Kafka: Checkout gói việc gửi event mới cùng offset của message vừa đọc trong một producer transaction; consumer phía sau chỉ thấy message khi transaction xong. Producer đầu luồng (Payments) bật idempotent producer để broker bỏ bản trùng. Kết quả là exactly-once cho cặp consume-produce.
- Events Aggregation (biết khi nào một batch đã xong hết):
- file CSV lớn chia thành nhiều job chạy song song; đếm số job xong trong RAM thì mất khi pod restart, cập nhật đồng thời thì có race;
- Atomic KV Store của Greyhound: mỗi cập nhật "job N xong" là một message vào topic commands với key = request ID + tổng số job, nên mọi cập nhật của một request vào cùng partition và xử lý tuần tự; cặp consumer-producer áp lệnh (tăng bộ đếm) vào compacted topic store trong một Kafka transaction (không đếm thừa);
- khi bộ đếm bằng tổng thì callback báo user qua websocket. Theo tác giả, Kafka Streams (
groupBy,count,filter) hợp tự nhiên với bài toán này, nhưng Wix dùng hạ tầng producer/consumer sẵn có vì ít xâm lấn hơn.
Vì sao (theo Wix)
Event-driven bớt code mẫu (poll, lock), bền hơn (ít lỗi dây chuyền), giảm khớp nối (producer không cần biết ai đọc), scale bằng thêm partition và instance.
Bài học cho SWE
- Partition theo key thay cho lock: mọi việc của một thực thể xử lý tuần tự trên một partition (Event-driven, message queue và Kafka).
- Hai kiểu retry: blocking giữ thứ tự nhưng chặn cả partition; retry topic + DLQ không chặn nhưng mất thứ tự. Cùng lựa chọn với Monzo 2024 (Monzo 2024).
- Exactly-once của Kafka chỉ phủ cặp consume-produce trong Kafka; side effect ra ngoài (gọi API, ghi DB khác) vẫn cần idempotent. Bài không bàn ghi DB và phát event trong một giao dịch (outbox) (Giao dịch phân tán và nhất quán xuyên service).
Liên quan
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (1)