Payment Transaction Management in Microservices
Bài tóm tắt Mercari 2019 3. Quy mô lớn Độ khó: Trung bình
Mercari · 2019 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Bối cảnh
- PaymentService của Merpay (mảng thanh toán của Mercari) nằm giữa kiến trúc microservices: dùng các phương thức thanh toán ở service phía dưới (kể cả service bên ngoài như thẻ tín dụng) để cung cấp luồng thanh toán cho service phía trên (Mercari, NFC, QR).
- Ví dụ: trả 1.000 yên = 600 yên điểm (service nội bộ A) + 400 yên thẻ (service ngoài), cộng 1.000 yên cho merchant (service nội bộ B), rồi gửi thông báo. Mỗi bước có thể timeout, và khi đó không biết bước đó đã chạy hay chưa. Ví dụ timeout ở bước thẻ: điểm đã bị trừ, nhưng không biết thẻ đã bị trừ chưa. Commit DB cục bộ thất bại ở cuối thì mất luôn ID giao dịch.
- XA/2PC lo ngại về hiệu năng và khả dụng, và không áp dụng được cho service bên ngoài. Họ theo hướng BASE (nhất quán cuối cùng, ưu tiên khả dụng), như eBay (2008) và Alipay với TCC.
Cách làm của PaymentService (ở tầng ứng dụng, không dùng distributed transaction của DB)
- Chia giao dịch thành các pha, ghi lại pha hiện tại. ID giao dịch nội bộ được chốt thành một pha riêng trước mọi thao tác. Độ mịn của pha chọn theo mức dễ retry và rollback; thao tác gọi service khác thường là một pha riêng, kèm journal log.
- Coordinator là một state machine: mỗi pha có trạng thái, xử lý và chuyển trạng thái (Created → Paid / Failed → Refunded). Lỗi ở pha nào thì retry hoặc rollback theo chuyển trạng thái đã định nghĩa.
- Lợi: bắt người viết nghĩ về phân pha từ đầu; dễ có cơ chế tự phục hồi chung.
- Hại (theo tác giả): code không đọc từ trên xuống, onboarding lâu, khó debug, nên cần test kết quả cuối và log tốt. Chia pha quá mịn thì ảnh hưởng hiệu năng.
- Idempotency bắt buộc: mọi API phía dưới phải nhận Idempotency Key và cho cùng kết quả dù bị gọi nhiều lần. Họ không chọn cách "hỏi kết quả trước khi gọi lại", vì tốn một request và dễ quên. PaymentService truyền ID giao dịch đã chốt làm key.
- Compensating transaction (bù trừ) do chính state machine quản lý. Muốn rollback sạch thì tách thành giữ chỗ → thực hiện (kiểu TCC), để rollback không để lại lịch sử "bẩn".
- Đối soát (reconciliation): batch đồng bộ gọi API tra cứu của service phía dưới bằng idempotency key; worker bất đồng bộ so dữ liệu cục bộ với event kết quả. Đối soát cả với kế toán và hệ số dư; service phía trên cũng đối soát với PaymentService.
- Fault injection testing: ngẫu nhiên tiêm timeout mạng, lỗi DB, kết nối chập chờn tới service ngoài. Mọi luồng thanh toán bắt buộc qua test này trước release, vì không có danh sách đầy đủ các ca lỗi do bên ngoài gây ra.
Bài học cho SWE
- Trong microservices, câu hỏi khó nhất là "timeout rồi, việc đó đã xảy ra chưa?". Câu trả lời là idempotency key, cộng chốt ID trước, cộng đối soát sau (Giao dịch phân tán và nhất quán xuyên service).
- Saga hay TCC chạy bằng state machine là cách phổ biến thay 2PC; đổi lại code khó đọc và cần đầu tư test, log (Monolith và microservices — đi và quay lại).
- Nhất quán cuối cùng phải được kiểm chứng liên tục bằng đối soát, không chỉ tin vào thiết kế.
Liên quan
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (4)