•10 min read
Thiết Kế Idempotency Layer Cho Cổng Thanh Toán & Dịch Vụ Giao Dịch Tài Chính
Giải quyết triệt để rủi ro mạng chập chờn, client tự động retry và lỗi trừ tiền trùng lặp (Double Charging) bằng cơ chế Idempotency Key, Request Hashing và khóa phân tán nguyên tử.
TGH
Tăng Gia Hoài
Systems Craftsman
#Backend#Thanh Toán#Idempotency#Kiến Trúc
Mục lục
5 mụcThảm Họa Trừ Tiền Kép (Double Spending & Double Charging)
Trong kiến trúc phân tán, việc kết nối mạng bị rớt giữa chừng là điều tất yếu. Hãy hình dung một kịch bản giao dịch thực tế:
- Ứng dụng di động gửi yêu cầu thanh toán $100 qua HTTP POST.
- Máy chủ backend nhận request, giao tiếp với ngân hàng, trừ tiền thành công.
- Khi backend đang chuẩn bị gửi HTTP 200 OK phản hồi về máy khách, mạng 4G của người dùng bị đứt kết nối.
- Ứng dụng client nhận lỗi Network Timeout. Thư viện HTTP client tự động kích hoạt cơ chế retry, gửi lại chính xác request thanh toán đó lần thứ hai.
- Nếu backend không có cơ chế nhận diện trùng lặp, tài khoản khách hàng sẽ bị trừ tiền hai lần.
Nguy hiểm (Caution)
Một API thanh toán không có tính Idempotent (Tính lũy đẳng) là một lỗi thiết kế nghiêm trọng, có thể gây thiệt hại tài chính và pháp lý trực tiếp cho doanh nghiệp.
Chuẩn Hóa Idempotency Key & Request Hashing
Khái niệm Idempotency (Lũy đẳng) phát biểu rằng: Thực hiện một thao tác một lần hay nhiều lần liên tiếp phải mang lại cùng một kết quả trạng thái duy nhất: $$f(f(x)) = f(x)$$
Quy Trình Xử Lý Chuẩn Quốc Tế (Stripe / IETF Standard)
code(17 dòng)Client Request [Idempotency-Key: UUIDv4, Payload: {...}] | v +----------------------+ | Idempotency Filter | +----------------------+ | +--------------+--------------+ | Đã có Key trong Redis/DB? | +-----------------------------+ / \ [CÓ] [CHƯA] | | Kiểm tra Hash payload: Giành Lock nguyên tử (SET NX) - Khác Hash? -> Lỗi 422 Thực thi nghiệp vụ thanh toán - Đang chạy? -> Lỗi 409 Lưu Response Cache vào DB - Đã xong? -> Replay 200 Trả kết quả cho Client
- Client sinh UUIDv4: Gửi kèm trong header:
Idempotency-Key: e8b9f3c1-45a2-4a7b-9c88-123456789abc. - Request Hashing (SHA-256): Backend băm toàn bộ Body + Request URI + User ID thành một mã băm
payload_hash. Nếu client gửi lại cùng Idempotency Key nhưng cố tình thay đổi số tiền từ $100 thành $500, hệ thống phát hiện hành vi giả mạo và từ chối với mã lỗi422 Unprocessable Entity.
Triển Khai Idempotency Middleware Trong Go
go(49 dòng)package middleware import ( "crypto/sha256" "encoding/hex" "net/http" "time" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" ) func IdempotencyMiddleware(rdb *redis.Client) gin.HandlerFunc { return func(c *gin.Context) { key := c.GetHeader("Idempotency-Key") if key == "" { c.Next() return } userID := c.GetString("user_id") lockKey := "idemp:lock:" + userID + ":" + key dataKey := "idemp:data:" + userID + ":" + key // 1. Thử giành Lock phân tán trong 30 giây để xử lý acquired, err := rdb.SetNX(c.Request.Context(), lockKey, "1", 30*time.Second).Result() if err != nil || !acquired { // Đang có một request khác cùng key đang được xử lý c.AbortWithStatusJSON(http.StatusConflict, gin.H{ "error": "Một giao dịch tương tự đang được xử lý. Vui lòng không gửi lại liên tục.", }) return } defer rdb.Del(c.Request.Context(), lockKey) // 2. Kiểm tra xem kết quả trước đó đã hoàn tất và lưu cache chưa cachedResponse, err := rdb.Get(c.Request.Context(), dataKey).Bytes() if err == nil { // Phát lại kết quả cũ một cách an toàn (Replay Cache) c.Header("X-Cache-Lookup", "HIT-IDEMPOTENT") c.Data(http.StatusOK, "application/json", cachedResponse) c.Abort() return } // 3. Cho phép xử lý tiếp và bắt response để lưu cache c.Next() } }
Các Cạm Bẫy Cần Tránh Khi Vận Hành
- Không lưu response lỗi tạm thời: Nếu request đầu tiên thất bại do database rớt mạng (500 Internal Error), không được lưu kết quả đó vào Idempotency Cache. Hãy xóa key để client có thể retry thật sự khi database hồi phục.
- Thời gian TTL hợp lý: Idempotency Key nên được lưu giữ từ 24 đến 72 giờ. Sau thời gian này, các giao dịch tài chính nếu có yêu cầu mới bắt buộc phải do người dùng chủ động khởi tạo phiên mới.
Bài viết liên quan
Kỹ thuật & Kiến trúc Hệ thống
Nhận bản tin khảo luận kỹ thuật chuyên sâu
Ghi chép thực chiến về tháo gỡ điểm nghẽn hiệu năng, scale CSDL PostgreSQL chịu tải lớn và giải thuật phân tán. Không bài viết thương mại, không thư rác.