Tích hợp API bên thứ ba an toàn để ứng dụng bạn vẫn chạy khi nhà cung cấp gặp sự cố. Tìm hiểu về timeout, retry, circuit breaker và kiểm tra nhanh.

Một API bên thứ ba có thể lỗi theo những cách không giống như một sự cố "sập" rõ ràng. Vấn đề phổ biến nhất là chậm: các yêu cầu treo, phản hồi đến muộn, và ứng dụng của bạn tiếp tục chờ. Nếu những cuộc gọi đó nằm trên đường dẫn quan trọng, một trục trặc nhỏ bên ngoài kiểm soát sẽ tích tụ bên trong hệ thống của bạn.
Đó là cách một chậm trễ cục bộ thành một sự cố toàn bộ. Luồng xử lý hoặc worker bị khóa chờ, hàng đợi tăng, giao dịch cơ sở dữ liệu mở lâu hơn, và các yêu cầu mới bắt đầu hết thời gian. Chẳng mấy chốc, ngay cả những trang không dùng API ngoài cũng cảm thấy hỏng vì hệ thống bị quá tải bởi công việc đang chờ.
Tác động rất cụ thể. Một nhà cung cấp xác thực không ổn định chặn đăng ký và đăng nhập. Một lần timeout với cổng thanh toán đóng băng checkout, khiến người dùng không chắc họ có bị trừ tiền hay không. Trễ tin nhắn làm dừng reset mật khẩu và xác nhận đơn, kích hoạt làn retry thứ hai và nhiều phiếu hỗ trợ.
Mục tiêu đơn giản: cô lập lỗi ngoài để các luồng cốt lõi vẫn chạy. Điều đó có thể có nghĩa là cho phép người dùng đặt hàng trước rồi xác nhận thanh toán sau, hoặc cho phép đăng ký ngay cả khi email chào mừng thất bại.
Một chỉ số thành công thực tế: khi một nhà cung cấp chậm hoặc sập, ứng dụng của bạn vẫn phản hồi nhanh và rõ ràng, và phạm vi ảnh hưởng giữ ở mức nhỏ. Ví dụ: hầu hết yêu cầu cốt lõi vẫn hoàn thành trong ngân sách độ trễ bình thường, lỗi chỉ ảnh hưởng đến những tính năng thực sự phụ thuộc vào API, người dùng thấy trạng thái rõ ràng (đang xếp hàng, đang chờ, thử lại sau), và khôi phục xảy ra tự động khi nhà cung cấp trở lại.
Hầu hết lỗi đều có thể dự đoán, dù thời điểm thì không. Ghi tên chúng trước và bạn có thể quyết định cái gì đáng thử lại, cái gì cần dừng, và nên hiển thị gì cho người dùng.
Các loại phổ biến:
Không phải tất cả lỗi đều giống nhau. Lỗi tạm thời thường đáng để retry vì lần gọi sau có thể thành công (sự cố mạng ngắn, timeout, 502/503, một số 429 sau khi chờ). Lỗi vĩnh viễn thường không tự sửa (chứng thực sai, endpoint sai, yêu cầu bị định dạng sai, từ chối quyền truy cập).
Đối xử mọi lỗi như nhau sẽ biến một sự cố nhỏ thành thời gian chết. Retry lỗi vĩnh viễn lãng phí thời gian, đẩy nhanh việc chạm giới hạn tốc độ, và tạo backlog làm chậm mọi thứ khác. Không bao giờ retry lỗi tạm thời buộc người dùng phải lặp lại hành động và bỏ mất công việc có thể hoàn thành vài giây sau.
Hãy chú ý hơn đến những luồng mà một khoảng dừng là một đứt gãy: checkout, đăng nhập, reset mật khẩu và thông báo (email/SMS/push). Tăng 2 giây với một API marketing thì phiền toái. Tăng 2 giây với ủy quyền thanh toán chặn doanh thu.
Một bài kiểm tra hữu ích: “Cuộc gọi này có bắt buộc phải hoàn tất nhiệm vụ chính của người dùng ngay bây giờ không?” Nếu có, bạn cần timeout chặt, retry thận trọng và một lộ trình thất bại rõ ràng. Nếu không, chuyển nó vào hàng đợi và giữ ứng dụng phản hồi.
Timeout là thời gian tối đa bạn sẵn sàng chờ trước khi dừng và chuyển hướng. Không có giới hạn rõ ràng, một nhà cung cấp chậm có thể tích tụ các yêu cầu chờ và chặn công việc quan trọng.
Tốt khi tách hai dạng chờ:
Chọn số không phải để hoàn hảo mà để phù hợp với sự kiên nhẫn của con người và luồng công việc của bạn.
Cách thực tế chọn timeout là làm ngược lại từ trải nghiệm:
Cái giá phải trả là thực: quá dài thì chiếm thread, worker và kết nối DB; quá ngắn thì tạo lỗi giả và kích hoạt retry không cần thiết.
Retry hữu ích khi lỗi có khả năng tạm thời: mạng chập chờn, DNS hiccup, hoặc 500/502/503 một lần. Trong các trường hợp đó, thử lại có thể thành công và người dùng không để ý.
Rủi ro là bão retry. Khi nhiều client lỗi cùng lúc và đều retry đồng thời, họ có thể quá tải nhà cung cấp (và cả worker của bạn). Backoff và jitter ngăn điều đó.
Một ngân sách retry giúp bạn kỷ luật. Giữ số lần cố gắng thấp và giới hạn tổng thời gian để các luồng cốt lõi không bị đứng chờ người khác.
Đừng retry các lỗi client có thể dự đoán như 400/422 (validation), 401/403 (xác thực), hoặc 404. Chúng thường sẽ thất bại lần nữa và chỉ làm tăng tải.
Một biện pháp nữa: chỉ retry các thao tác ghi (POST/PUT) khi bạn có idempotency, nếu không bạn có rủi ro trừ tiền đôi hoặc tạo bản ghi trùng.
Idempotency nghĩa là bạn có thể thực hiện cùng một yêu cầu hai lần mà kết quả cuối cùng vẫn như nhau. Điều này quan trọng vì retry là bình thường: mạng rớt, server khởi động lại, client timeout. Không có idempotency, một retry "hữu ích" sẽ tạo bản ghi trùng hoặc rắc rối về tiền.
Hãy tưởng tượng checkout: API thanh toán chậm, app của bạn timeout, và bạn retry. Nếu cuộc gọi đầu thực sự đã thành công, retry có thể gây trừ tiền lần hai. Nguy cơ tương tự xuất hiện với tạo đơn hàng, bắt đầu gói đăng ký, gửi email/SMS, hoàn tiền, hoặc tạo ticket hỗ trợ.
Cách khắc phục là gắn một khóa idempotency (hoặc request ID) vào mọi cuộc gọi "thực hiện hành động". Khóa nên là duy nhất cho hành động của người dùng, không phải cho lần thử. Nhà cung cấp (hoặc dịch vụ của bạn) dùng khóa đó để phát hiện trùng và trả về cùng kết quả thay vì thực hiện hành động lần nữa.
Hãy coi khóa idempotency như một phần của mô hình dữ liệu, không phải một header mà bạn hy vọng ai cũng nhớ gửi.
Tạo một khóa khi người dùng bắt đầu hành động (ví dụ khi họ nhấn Pay), rồi lưu nó với bản ghi cục bộ.
Mỗi lần thử:
Nếu bạn là "nhà cung cấp" cho các cuộc gọi nội bộ, hãy bắt buộc hành vi tương tự ở phía server.
Circuit breaker là công tắc an toàn. Khi dịch vụ ngoài bắt đầu lỗi, bạn ngưng gọi trong một khoảng ngắn thay vì thêm các yêu cầu có khả năng timeout.
Circuit breaker thường có ba trạng thái:
Khi breaker mở, ứng dụng của bạn nên làm điều gì đó có thể dự đoán. Nếu API xác thực địa chỉ sập trong lúc đăng ký, chấp nhận địa chỉ và gắn nhãn để xem lại sau. Nếu kiểm tra rủi ro thanh toán sập, xếp hàng đơn cho rà soát thủ công hoặc tạm thời vô hiệu hóa tùy chọn đó và giải thích.
Chọn ngưỡng phù hợp với tác động người dùng:
Giữ cooldown ngắn (vài giây đến một phút) và giới hạn probe half-open. Mục tiêu là bảo vệ luồng cốt lõi trước, rồi phục hồi nhanh.
Khi API ngoài chậm hoặc sập, mục tiêu của bạn là giữ người dùng tiến bước. Điều đó nghĩa là có Kế hoạch B trung thực về những gì xảy ra.
Fallback là những gì app bạn làm khi API không phản hồi kịp. Tùy chọn gồm dùng dữ liệu cache, chuyển sang chế độ suy giảm (ẩn widget không cần thiết, vô hiệu hóa hành động tùy chọn), yêu cầu người dùng nhập thủ công (nhập địa chỉ), hoặc hiển thị thông báo rõ ràng với bước tiếp theo.
Thành thật: đừng nói điều gì đã hoàn tất nếu thực tế chưa xong.
Nếu công việc không cần hoàn tất trong yêu cầu người dùng, đẩy nó vào hàng đợi và phản hồi nhanh. Các ứng viên thường thấy: gửi email, đồng bộ tới CRM, tạo báo cáo, gửi events phân tích.
Fail fast cho các hành động cốt lõi. Nếu API không cần để hoàn tất checkout (hoặc tạo tài khoản), đừng chặn yêu cầu. Chấp nhận đơn, xếp công việc ngoài để gọi API, và đối soát sau. Nếu API là bắt buộc (ví dụ ủy quyền thanh toán), fail nhanh với thông báo rõ ràng và đừng để người dùng chờ.
Những gì người dùng thấy nên khớp với việc diễn ra phía sau: trạng thái rõ ràng (hoàn tất, đang chờ, thất bại), một lời hứa bạn có thể giữ (biên nhận ngay, xác nhận sau), cách để thử lại, và bản ghi hiển thị trong UI (nhật ký hoạt động, badge đang chờ).
Rate limit là cách nhà cung cấp nói, “Bạn có thể gọi chúng tôi, nhưng đừng quá thường.” Bạn sẽ chạm giới hạn sớm hơn bạn nghĩ: bùng nổ traffic, job nền đồng loạt chạy, hoặc lỗi khiến vòng lặp gọi.
Bắt đầu bằng việc kiểm soát số yêu cầu bạn tạo. Gộp lô khi có thể, cache phản hồi trong 30–60 giây nếu an toàn, và throttle ở phía client để app không bùng nổ nhanh hơn nhà cung cấp cho phép.
Khi nhận 429 Too Many Requests, coi đó là tín hiệu giảm tốc.
Retry-After khi có.Ngoài ra hạn chế concurrency. Một workflow (ví dụ đồng bộ danh bạ) không nên chiếm hết slot worker và làm nghèo các luồng quan trọng như đăng nhập hay checkout. Pool riêng hoặc giới hạn theo tính năng giúp ích.
Mỗi cuộc gọi đến bên thứ ba cần một kế hoạch xử lý lỗi. Bạn không cần hoàn hảo. Bạn cần hành vi có thể dự đoán khi nhà cung cấp có một ngày xấu.
Quyết định chuyện gì xảy ra nếu cuộc gọi thất bại ngay bây giờ. Một tính toán thuế lúc checkout có thể là bắt buộc. Đồng bộ liên hệ marketing thường có thể chờ. Quyết định này dẫn lối cho các bước tiếp theo.
Chọn timeout theo loại cuộc gọi và giữ nhất quán. Rồi đặt ngân sách retry để không tiếp tục búa API chậm.
Nếu một yêu cầu có thể tạo thứ gì đó hoặc trừ tiền, thêm khóa idempotency và lưu bản ghi yêu cầu. Nếu yêu cầu thanh toán timeout, retry không nên trừ tiền lần hai. Việc theo dõi cũng giúp hỗ trợ trả lời, “Nó có đi qua không?”
Khi lỗi tăng vọt, dừng gọi nhà cung cấp trong khoảng ngắn. Với các cuộc gọi bắt buộc, hiển thị đường dẫn “Thử lại” rõ ràng. Với những cuộc gọi có thể chờ, xếp vào hàng đợi để xử lý sau.
Theo dõi latency, tỉ lệ lỗi, và sự kiện breaker open/close. Báo động khi có thay đổi kéo dài, không phải từng trục trặc đơn lẻ.
Hầu hết sự cố API không bắt đầu lớn. Chúng trở nên lớn vì app của bạn phản ứng theo cách tồi tệ: chờ quá lâu, retry quá mức, và chiếm hết worker giữ mọi thứ khác chạy.
Những mẫu sau gây cascade:
Các sửa nhỏ ngăn chặn outage lớn: chỉ retry lỗi có khả năng tạm thời (timeout, một số 429, một số 5xx) và giới hạn số lần với backoff và jitter; giữ timeout ngắn và có chủ ý; yêu cầu idempotency cho mọi thao tác tạo hoặc trừ tiền; và thiết kế cho suy giảm phần tính năng.
Trước khi đẩy tích hợp lên production, rà soát nhanh với tư duy thất bại. Nếu bạn không trả lời được “có” cho một mục, coi đó là blocker triển khai cho các luồng cốt lõi như đăng ký, thanh toán, hoặc gửi tin.
Nếu một nhà cung cấp thanh toán bắt đầu timeout, hành vi đúng là “checkout vẫn tải, người dùng nhận thông báo rõ ràng, và bạn không treo mãi,” chứ không phải “mọi thứ treo cho đến khi timeout.”
Hãy tưởng tượng một checkout gọi ba dịch vụ: API thanh toán để trừ thẻ, API thuế để tính thuế, và API email để gửi hóa đơn.
Cuộc gọi thanh toán là duy nhất cần đồng bộ. Vấn đề với thuế hoặc email không nên làm tắc việc mua.
Giả sử API thuế đôi khi mất 8–15 giây. Nếu checkout chờ, người dùng bỏ giỏ và app chiếm worker.
Luồng an toàn hơn:
Kết quả: ít giỏ bị bỏ và ít đơn bị treo khi nhà cung cấp thuế chậm.
Email biên nhận quan trọng, nhưng không bao giờ được chặn việc ghi nhận thanh toán. Nếu API email lỗi, circuit breaker nên mở sau vài lần thất bại nhanh và ngưng gọi trong cửa sổ cooldown ngắn.
Thay vì gửi email trực tuyến, xếp job "gửi biên nhận" với khóa idempotency (ví dụ order_id + email_type). Nếu nhà cung cấp sập, hàng đợi sẽ retry nền và khách hàng vẫn thấy mua thành công.
Kết quả: ít ticket hỗ trợ do thiếu xác nhận và không mất doanh thu vì checkout thất bại vì lý do không phải thanh toán.
Chọn một luồng làm tham chiếu — cái gây tổn thất nhiều nhất khi hỏng (checkout, đăng ký, phát hành hóa đơn) — và biến nó thành tích hợp mẫu. Rồi sao chép các mặc định đó khắp nơi.
Thứ tự rollout đơn giản:
Ghi lại các mặc định của bạn và giữ chúng đơn giản: một connect timeout, một request timeout, số retry tối đa, phạm vi backoff, cooldown breaker, và quy tắc về những gì được retry.
Chạy drill thất bại trước khi mở rộng sang luồng tiếp theo. Ép timeout (hoặc chặn nhà cung cấp trong môi trường test), rồi xác nhận người dùng thấy thông báo hữu dụng, fallback hoạt động, và các retry trong hàng đợi không tích tụ mãi.
Nếu bạn xây sản phẩm nhanh, đáng để biến các mặc định độ tin cậy này thành mẫu tái sử dụng. Với các đội dùng Koder.ai (koder.ai), điều đó thường nghĩa là định nghĩa timeout, retry, idempotency và quy tắc breaker một lần, rồi áp mẫu đó cho các dịch vụ mới khi bạn tạo và lặp.