Avoiding Double Payments in a Distributed Payments System
Bài tóm tắt Airbnb 2019 3. Quy mô lớn Độ khó: Nâng cao
Airbnb · 2019 · 3. Quy mô lớn · Độ khó: Nâng cao — bài gốc
Bối cảnh
- Airbnb chuyển sang SOA; một API thanh toán gọi xuống nhiều service, mỗi service đổi trạng thái và có tác dụng phụ: thực chất là một transaction phân tán, trong khi request mạng chắc chắn có lúc timeout.
- Hệ thanh toán dùng cả ba kiểu sửa nhất quán cuối: read repair, write repair, async repair (scan, cron, thông báo bất đồng bộ). Bài này nói về write repair: mỗi lần client gửi lại cùng request là một lần sửa trạng thái hỏng, nên client phải thông minh hơn và API phải idempotent (chuyển tiền nhiều nhất một lần).
- Yêu cầu: giải pháp chung cho nhiều service; latency cực thấp nên không làm service idempotency riêng (nó sẽ gặp đúng vấn đề nó định giải); che phức tạp khỏi developer sản phẩm.
Thay đổi chính
- Orpheus: thư viện idempotency (Java), không phải service. Gồm idempotency key, bảng thông tin idempotency luôn đọc/ghi trên master đã shard, gộp transaction bằng Java lambda, phân loại lỗi retryable/non-retryable.
- Mỗi request chia ba pha: Pre-RPC (ghi chi tiết request vào DB), RPC (gọi service ngoài, processor, ngân hàng), Post-RPC (ghi kết quả và có retry được không). Hai luật: không gọi mạng trong pha Pre/Post, không đụng DB trong pha RPC. Mỗi pha Pre/Post là một transaction duy nhất (gồm cả commit của thư viện lẫn của ứng dụng), nên hỏng giữa chừng vẫn khôi phục được. Theo Airbnb, gọi mạng trong transaction từng làm cạn pool kết nối.
- Lỗi 5XX (mạng, hạ tầng) mặc định retryable; lỗi validate 4XX non-retryable. Gắn nhầm thì hoặc request kẹt "thất bại" mãi, hoặc trả tiền hai lần. Payload không được đổi giữa các lần retry.
- Client: tạo key mới cho request mới, dùng lại khi retry; lưu key vào DB trước khi gọi; xoá key khi nhận kết quả; retry với exponential backoff + jitter. Key mức request (UUID) hoặc mức thực thể (tất định, như
payment-1234-refund) tuỳ nghiệp vụ. - Lease có hạn: request lấy row lock trên key; chỉ retry được sau khi lease hết hạn (đặt dài hơn timeout RPC); có cửa sổ retry tối đa để chặn retry bất thường.
- Ghi lại response khi đã tới trạng thái cuối để retry trả nhanh; bảng này phình theo thông lượng, xoá sớm quá thì mất tác dụng idempotent.
- Không đọc replica: replica lag vài giây có thể khiến retry không thấy kết quả đã ghi ở master và trả tiền lần nữa. Scale master bằng shard theo idempotency key (cardinality cao, phân bố đều).
- Kết quả theo Airbnb: nhất quán thanh toán đạt "five nines" trong khi khối lượng thanh toán hằng năm tăng gấp đôi.
Vì sao
- Không dùng two-phase commit qua nhiều service; idempotency + retry cho nhất quán cuối mà client tự kích hoạt được khi cần.
Bài học cho SWE
- Tách hẳn việc gọi mạng khỏi transaction DB; ranh giới pha rõ ràng làm trạng thái hỏng có thể khôi phục.
- Phân loại lỗi retry được hay không là quyết định về đúng đắn, không chỉ về độ bền.
- Dữ liệu dùng để quyết định "đã làm chưa" phải đọc ở nơi nhất quán mạnh; replica lag biến thành tiền mất.
- Cái giá: client phức tạp hơn và API phải tách thành ba khúc, không phải lúc nào cũng tự nhiên.
Liên quan
- Giao dịch phân tán và nhất quán xuyên service · Failover, replica và dự phòng dữ liệu
- Báo cáo tài chính phía offline: Airbnb 2017
#idempotency#Orpheus#idempotency key#write repair#Pre-RPC/RPC/Post-RPC#lease#retryable#replica lag#sharded master#eventual consistency
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (1)