---
id: case-distributed
title: "App gọi nhiều dịch vụ phân tán"
shortTitle: "Service phân tán"
track: cases
trackTitle: "Tình huống vận hành"
order: 9
level: core
status: published
estimatedMinutes: 45
prerequisites:
  - networking-http
  - case-alert
next: case-cache
reviewedAgainst: 2026-10-12
sources:
  - title: "AWS Builders Library — Timeouts, retries and backoff"
    url: https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/
  - title: "Azure Architecture Center — Circuit Breaker"
    url: https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
---

# App gọi nhiều dịch vụ phân tán

Một request checkout đi qua API, inventory, payment rồi notification. Kể cả khi cả bốn service đều đạt 99,9%, request đi qua cả bốn vẫn không đạt 99,9%, vì availability của cả chuỗi là tích availability của từng chặng. Mỗi network hop còn thêm timeout, retry và một kiểu lỗi mới.

## Để làm gì

Bạn sẽ phân loại dependency nào bắt buộc, dependency nào tùy chọn, đặt timeout riêng cho từng cái, giới hạn số lần retry, và trả về response có chủ đích khi một service chết.

## Gồm những gì

- Timeout, retry với exponential backoff và jitter.
- Circuit breaker, bulkhead, fallback.
- `try/catch` chỉ là chỗ xử lý lỗi, không phải là một chiến lược resilience.

Bài không nói về service mesh. App phải xử lý đúng semantics trước, vì mesh không biết một call payment có được phép retry hay không.

## Dùng khi nào

API tổng hợp dữ liệu từ nhiều service, hoặc một bước checkout gọi inventory và payment rồi gửi email.

Đừng bọc mọi call bằng `try { } catch { return null }`. Làm vậy thì user nhận 200 với dữ liệu sai, alert không thấy lỗi nào, và đội vận hành mất dấu dependency đang chết.

## Kiến thức cốt lõi

Việc đầu tiên là phân loại dependency, vì cách xử lý timeout, retry và fallback đều phụ thuộc vào câu hỏi "nếu nó chết thì sao":

| Dependency | Nếu chết | Cách xử lý |
| --- | --- | --- |
| Payment trong checkout | Không được bỏ qua | Fail request rõ ràng; không retry mù POST |
| Inventory trước đặt hàng | Không được đoán | Fail hoặc giữ đơn ở trạng thái pending |
| Recommendation | Tùy chọn | Bỏ section, trả phần còn lại |
| Email xác nhận | Chạy sau | Ghi event vào queue, không giữ response user |

Timeout của call con phải ngắn hơn deadline của request cha. Nếu request cha chỉ còn 2 giây mà call con đặt timeout 5 giây, response chắc chắn sẽ trễ. Connect timeout và response timeout là hai thứ khác nhau, và các bước DNS, TCP, TLS cũng ăn vào deadline.

Retry chỉ hợp với lỗi tạm thời và operation idempotent. GET thường retry được. POST payment thì có thể trừ tiền hai lần nếu provider không hỗ trợ idempotency key. Retry ở nhiều tầng sẽ nhân lên: client retry 3 lần, API retry 3 lần, SDK retry 3 lần là tối đa 27 call. Hãy chọn đúng một tầng chịu trách nhiệm retry.

Exponential backoff giãn dần khoảng cách giữa các lần thử. Jitter thêm yếu tố ngẫu nhiên để hàng nghìn instance không cùng retry một lúc. AWS Builders Library khuyên dùng backoff kèm jitter, vì retry đồng loạt sẽ biến một dependency đang yếu thành một dependency chết hẳn.

Circuit breaker tạm dừng gọi dependency khi tỉ lệ lỗi vượt ngưỡng. Ở trạng thái `open`, app trả fallback ngay, cho dependency thời gian hồi phục. Sau một khoảng, breaker chuyển sang `half-open` và cho vài request đi thử. Circuit breaker không thay được timeout, vì những request đi trước lúc breaker mở vẫn cần timeout để không bị treo.

Bulkhead tách riêng connection pool hoặc giới hạn concurrency cho từng dependency. Nhờ vậy, recommendation chậm không chiếm hết worker mà payment cần.

## Lệnh và code mẫu

Node.js, với dependency tùy chọn:

```js
async function fetchRecommendation(userId, signal) {
  try {
    const res = await fetch(`http://recommendation/users/${userId}`, { signal });
    if (!res.ok) throw new Error(`recommendation ${res.status}`);
    return await res.json();
  } catch (error) {
    if (error.name !== "AbortError") console.error("recommendation_failed", { error });
    return [];
  }
}

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 300);
const recommendations = await fetchRecommendation(userId, controller.signal);
clearTimeout(timer);
```

Với payment thì không được nuốt lỗi:

```js
const res = await fetch("http://payment/charges", {
  method: "POST",
  headers: { "Idempotency-Key": orderId, "Content-Type": "application/json" },
  body: JSON.stringify({ orderId, amount }),
  signal: AbortSignal.timeout(1500)
});
if (!res.ok) throw new Error(`payment_failed ${res.status}`);
```

`Idempotency-Key` chỉ có tác dụng khi payment service lưu key lại và trả về kết quả cũ cho request trùng. Riêng việc gửi header không tạo ra idempotency.

## Thực hành

1. Vẽ đường đi checkout → inventory → payment → email.
2. Trên mỗi mũi tên, ghi deadline, có retry được không, và fallback là gì.
3. Cho recommendation trả 500. Checkout vẫn trả 200, nhưng metric `recommendation_failed` phải tăng.
4. Cho payment timeout. Checkout phải fail hoặc chuyển sang pending, không được trả "đã thanh toán".

## Kết quả mong đợi

Mỗi dependency có timeout và semantics riêng. Một service tùy chọn chết không kéo sập cả trang. Một dependency bắt buộc chết không bị che thành một response 200 giả.

## Checklist

- [ ] Deadline của call con nhỏ hơn deadline của request cha.
- [ ] Chỉ một tầng retry, có backoff và jitter.
- [ ] POST có idempotency key, và server thật sự xử lý key đó.
- [ ] Dependency tùy chọn có fallback và metric riêng.
- [ ] Pool hoặc concurrency của dependency chậm không chiếm hết tài nguyên của app.

## Lỗi thường gặp

**Retry ngay lập tức mọi lỗi 500.** Năm replica cùng retry ba lần sẽ tạo tải gấp ba đúng lúc service đang yếu. Hãy dùng backoff, jitter và giới hạn retry budget.

**Catch lỗi payment rồi trả mảng rỗng.** User nhận thông báo thành công giả. Chỉ dependency tùy chọn mới được fallback im lặng; payment phải có trạng thái rõ ràng.

**Dùng chung một circuit breaker cho mọi tenant.** Một tenant lỗi sẽ làm breaker mở cho tất cả. Hãy đặt key của breaker theo endpoint hoặc dependency, nhưng đừng tạo breaker riêng cho từng request vì số lượng sẽ không có giới hạn.

## Nguồn

1. [Timeouts, retries and backoff with jitter](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/) — retry storm, timeout và jitter.
2. [Circuit Breaker pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) — closed, open, half-open và fallback.

## Tiếp theo

[Cache và Redis gặp sự cố](./10-cache.md)
