Mô tả ngắn
Tags: #system design, #payment, #stripe, #microserviceBạn đã bao giờ gặp trường hợp bị trừ tiền hai lần cho cùng một giao dịch chưa? Đó chính là vấn đề lỗi thanh toán kép, một sự cố không ai mong muốn. Có nhiều nguyên nhân dẫn đến tình trạng này:
Để giải quyết vấn đề nan giải này, Stripe đã phát triển một công cụ hữu ích gọi là API idempotent. Hãy tưởng tượng API này như một "người gác cổng" thông minh, đảm bảo mỗi yêu cầu thanh toán chỉ được xử lý đúng một lần, dù bạn có gửi đi bao nhiêu lần đi nữa.
Mỗi khi bạn gửi yêu cầu thanh toán, Stripe sẽ tạo ra một chuỗi duy nhất(UUID) để sử dụng làm khóa idempotency và gửi kèm theo HTTP header của mỗi yêu cầu. Khóa này giống như "dấu vân tay" của yêu cầu, không có bất kỳ yêu cầu nào khác có cùng một khóa. Mỗi khi payload của yêu cầu thay đổi, họ tạo ra một UUID mới.
Stripe sử dụng một cơ sở dữ liệu hoạt động trên RAM (in-memory database) ở phía máy chủ để quản lý các khóa idempotency này.
Khi nhận được một yêu cầu, Stripe sẽ truy vấn cơ sở dữ liệu này để kiểm tra xem khóa idempotency đã tồn tại hay chưa. Nếu chưa, yêu cầu sẽ được xử lý và khóa idempotency cùng kết quả xử lý (dưới dạng phản hồi đã được cache) sẽ được lưu vào cơ sở dữ liệu.
Nếu khóa đã tồn tại, Stripe sẽ biết đây là yêu cầu lặp lại và trả về kết quả đã được lưu trước đó, mà không cần xử lý lại yêu cầu.
Cơ chế này giúp đảm bảo tính chính xác và toàn vẹn của dữ liệu, ngay cả khi có lỗi xảy ra. Trong trường hợp xảy ra lỗi máy chủ, Stripe sẽ rollback giao dịch sử dụng cơ sở dữ liệu ACID để đảm bảo tính nhất quán của dữ liệu.
Để tối ưu hóa hiệu suất và giảm thiểu chi phí lưu trữ, Stripe sẽ xóa các khóa idempotency khỏi cơ sở dữ liệu sau 24 giờ. Khoảng thời gian này đủ dài để bạn có thể thử lại yêu cầu nếu gặp lỗi, đồng thời cho phép tái sử dụng khóa idempotency sau đó.
Nếu yêu cầu thanh toán của bạn không thành công ngay lần đầu, có thể do lỗi tạm thời từ phía máy chủ Stripe hoặc do kết nối mạng không ổn định. Trong trường hợp này, bạn hoàn toàn có thể thử lại yêu cầu một cách an toàn mà không lo lắng về việc bị trừ tiền nhiều lần.
Stripe sử dụng một thuật toán đơn giản nhưng hiệu quả, gọi là exponential backoff with jitter.
Thuật toán này sẽ tăng dần thời gian chờ giữa các lần thử lại theo cấp số nhân (ví dụ: 1 giây, 2 giây, 4 giây, 8 giây...) để giảm tải cho máy chủ và tránh tình trạng nhiều yêu cầu dồn dập cùng lúc (thundering herd).
Đồng thời, thuật toán còn thêm một chút ngẫu nhiên (jitter) vào thời gian chờ để tránh các yêu cầu từ nhiều khách hàng cùng thử lại đồng thời.
Stripe không chỉ đơn thuần là lưu trữ kết quả của yêu cầu đầu tiên. Họ còn lưu trữ toàn bộ thông tin liên quan đến yêu cầu đó, bao gồm cả mã trạng thái (status code) và nội dung phản hồi (response body).
Điều này đảm bảo rằng khi bạn gửi lại yêu cầu với cùng khóa idempotency, Stripe sẽ trả về chính xác kết quả của lần xử lý đầu tiên, dù kết quả đó là thành công hay thất bại, thậm chí là cả các lỗi hệ thống (như lỗi 500 Internal Server Error).
Tuy nhiên, Stripe chỉ lưu trữ kết quả sau khi điểm cuối API (endpoint) đã bắt đầu xử lý yêu cầu. Nếu yêu cầu không hợp lệ (ví dụ: sai định dạng, thiếu thông tin) hoặc xung đột với một yêu cầu khác đang được xử lý đồng thời, kết quả sẽ không được lưu. Điều này giúp bạn có thể tự tin thử lại yêu cầu mà không lo lắng về việc kết quả sai được lưu lại và trả về trong các lần thử lại sau.
API idempotent của Stripe là một giải pháp đơn giản nhưng hiệu quả để ngăn chặn lỗi thanh toán kép. Bằng cách sử dụng các khóa idempotency và các kỹ thuật như backoff theo cấp số nhân và jitter, Stripe đảm bảo rằng các yêu cầu thanh toán của khách hàng được xử lý chính xác một lần ngay cả trong trường hợp có lỗi. Điều này không chỉ giúp tăng độ tin cậy của dịch vụ mà còn mang lại trải nghiệm tốt hơn cho khách hàng.
Hẹn gặp bạn tại những bài vết thú vị hơn nha!