Thử lại an toàn
Gửi lại một yêu cầu tạo hay khởi chạy với cùng Idempotency-Key mà không làm việc hai lần — thao tác nào nhận khóa, thế nào là cùng một yêu cầu, lần phát lại trả về gì, và nên làm gì với bốn mã lỗi.
Vì sao cần
Một yêu cầu có thể hết thời gian chờ, hoặc mất kết nối, sau khi server đã làm xong việc. Gửi lại mù quáng thì bạn có hai hồ sơ, hay hai lượt chạy. Hãy gửi kèm header Idempotency-Key: gửi lại với cùng khóa sẽ nhận về kết quả gốc, và không có gì được làm lần hai.
Gửi một khóa
export IDEMPOTENCY_KEY=$(uuidgen)
curl -sS -X POST "$ORIGIN/api/v1/workflows/$WORKFLOW_ID/instances" \
-H "Authorization: Bearer $AGENTFLOW_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{"name": "Đề xuất mua 10 laptop", "input_payload": {"so_luong": 10}}'- Khóa gồm 1–255 ký tự ASCII in được. Nên dùng một UUID: sinh một lần cho mỗi việc bạn muốn làm, rồi dùng lại đúng khóa đó ở mọi lần gửi lại.
- Khóa thuộc về một danh tính trong một tổ chức — một chủ thể dịch vụ, hoặc một người. Hai danh tính tình cờ gửi cùng một khóa là hai yêu cầu không liên quan gì nhau.
- Khóa có hiệu lực 24 giờ theo mặc định; bản triển khai có thể đặt khác. Hết thời hạn, cùng khóa được coi là một yêu cầu mới.
Thao tác nào nhận khóa
| Thao tác | Phương thức và đường dẫn |
|---|---|
| Tạo hồ sơ | POST /entities/{entity_id}/records |
| Sửa một vài trường của hồ sơ | PATCH /entities/{entity_id}/records/{record_id} |
| Khởi chạy một lượt chạy | POST /workflows/{workflow_id}/instances |
| Khởi chạy lượt chạy hàng loạt | POST /entities/{entity_id}/records/batch-runs |
| Tải tệp lên | POST /attachments |
| Gửi bước của một công việc | POST /entities/{entity_id}/records/{record_id}/step/submit |
- Trong tài liệu tham chiếu, các thao tác này mang
x-idempotent: true. Hãy dựa vào dấu hiệu đó thay vì chép lại bảng này. - Gửi một bước là
x-person-only: chủ thể dịch vụ gọi sẽ nhận403 USER_IDENTITY_REQUIRED. - Các thao tác công việc (nhận, trả lại, giao lại, giao việc) không nhận khóa. Một công việc được nhận và trả lại như mọi bản ghi công việc, và gửi lại một lượt nhận bạn đã thắng sẽ trả
409 WORK_ALREADY_CLAIMED: sau mỗi lần quá thời gian chờ, hãy đọc lại bản ghi trước. - Khóa gửi tới một thao tác khác bị bỏ qua, như mọi header mà thao tác đó không đọc. Một lớp thử lại dùng chung, gắn khóa vào mọi
POST, vẫn hoạt động bình thường.
Thế nào là cùng một yêu cầu
Server so dấu vân tay (fingerprint) của yêu cầu gửi lại với dấu vân tay của yêu cầu gốc. Dấu vân tay gồm:
- phương thức, đường dẫn và giá trị các tham số đường dẫn;
- chuỗi truy vấn — tên các tham số theo thứ tự nào cũng được, nhưng các giá trị lặp của cùng một tham số giữ nguyên thứ tự:
?a=1&a=2khác?a=2&a=1; - thân yêu cầu — với JSON, thứ tự khóa và khoảng trắng không quan trọng; thứ tự phần tử trong mảng và cách viết số thì có:
1khác1.0; - với tải tệp lên, nội dung tệp, tên tệp và kiểu nội dung, thay cho thân yêu cầu.
Cùng khóa với một yêu cầu khác bị từ chối 422 IDEMPOTENCY_KEY_MISMATCH, và yêu cầu đó không được thực hiện. Muốn làm một việc khác, hãy dùng khóa mới.
Lần phát lại trả về gì
Khi cùng khóa và cùng yêu cầu tới trong thời hạn, sau khi yêu cầu gốc đã thành công:
HTTP/1.1 201 Created
Content-Type: application/json
Idempotent-Replayed: true
{ "id": "c04b…", "status": "running", … }- Trạng thái gốc — ở đây
201. - Tài nguyên như hiện tại, không phải bản sao của phản hồi cũ: nếu hồ sơ đã được sửa kể từ đó, bạn thấy bản đã sửa.
Idempotent-Replayed: true. Phản hồi của yêu cầu gốc không có header này.- Không có gì được tạo, khởi chạy hay thay đổi lần hai.
Lần phát lại trả về kết quả gốc, không đánh giá lại hành động:
- Quyền thực hiện không bị kiểm tra lại. Một vai trò bị gỡ sau yêu cầu gốc không biến lần phát lại thành
403. Nhưng tài nguyên vẫn được đọc lại theo quyền đọc hiện tại của bạn. - Khởi chạy có chờ (
wait_ms, xem ví dụ): lần phát lại trả202khi lượt chạy còn đang chạy,200khi không, và không chờ lại. - Sửa hồ sơ:
expected_updated_atkhông bị kiểm tra lại, nên lần phát lại không trả409 STALE_VERSIONdù hồ sơ đã đổi. - Gửi một bước: trả
200với bảng của bước như bạn đọc được lúc này, và gọi công việc bằng mã bản ghi của nó. Một lượt gửi bị từ chối — biểu mẫu không hợp lệ, hoặc việc ghi vào bản ghi mà công việc nói tới bị từ chối — không làm mất khóa, nên sau khi bạn sửa nguyên nhân, cùng khóa đó sẽ thực hiện lượt gửi đúng một lần.
Tài nguyên không còn
Nếu tài nguyên đã bị xóa, hoặc bạn không còn được thấy nó, lần phát lại trả 410 IDEMPOTENT_RESOURCE_GONE. extensions.idempotency nêu loại và id của tài nguyên:
{
"type": "IDEMPOTENT_RESOURCE_GONE",
"title": "Gone",
"status": 410,
"detail": "…",
"request_id": "…",
"instance": "/api/v1/entities/…/records",
"extensions": { "idempotency": { "resource_kind": "record", "resource_id": "7d2e…" } }
}resource_kindhiện là một trongrecord,instance,batch_run,attachment,task. Danh sách này có thể được bổ sung về sau.- Với một lượt gửi,
resource_kindlàtaskvàresource_idlà mã bản ghi Workflow task của công việc;410xảy ra khi bản ghi của công việc không còn tồn tại hoặc bạn không còn mở được bước của nó.
Chỉ kết quả thành công được ghi nhận
Khóa chỉ được ghi nhận khi yêu cầu thành công. Một yêu cầu bị từ chối — ví dụ 422 vì dữ liệu không hợp lệ — không để lại gì: sửa yêu cầu rồi gửi lại với cùng khóa.
Hai yêu cầu cùng lúc
Khi yêu cầu gốc vẫn đang chạy, một lần gửi lại với cùng khóa nhận 409 IDEMPOTENCY_KEY_IN_PROGRESS kèm Retry-After: 1. Chờ chừng ấy giây rồi gửi lại với cùng khóa: nếu yêu cầu gốc đã thành công, bạn nhận lần phát lại; nếu nó thất bại, lần gửi của bạn được xử lý như một yêu cầu mới.
Hết thời gian chờ có thể che một thành công
Một yêu cầu có thể hết thời gian chờ, hoặc mất kết nối, sau khi việc đã được ghi. Đừng coi đó là thất bại. Gửi lại với cùng khóa là cách biết kết quả thật:
- Phản hồi có
Idempotent-Replayed: true: việc đã được làm từ trước, và đây là kết quả của nó. - Phản hồi thành công không có header đó: yêu cầu trước không thành công hoặc chưa tới server, và bây giờ việc đã được làm đúng một lần.
Bốn mã lỗi
| Trạng thái | Mã (type) | Khi nào | Nên làm gì |
|---|---|---|---|
400 | IDEMPOTENCY_KEY_INVALID | Khóa rỗng, dài hơn 255 ký tự, có ký tự ngoài ASCII in được, hoặc header được gửi hai lần. field_errors có một mục với path là Idempotency-Key. | Sửa khóa. Gửi lại y nguyên không giúp gì. |
409 | IDEMPOTENCY_KEY_IN_PROGRESS | Yêu cầu gốc với khóa này vẫn đang chạy. | Đáng thử lại: chờ theo Retry-After (1 giây), rồi gửi lại với cùng khóa. |
422 | IDEMPOTENCY_KEY_MISMATCH | Khóa đã được dùng cho một yêu cầu khác. | Dùng khóa mới cho yêu cầu mới. Gửi lại y nguyên không giúp gì. |
410 | IDEMPOTENT_RESOURCE_GONE | Tài nguyên mà khóa đã tạo bị xóa, hoặc bạn không còn thấy nó. | Đừng thử lại: kết quả sẽ y như vậy. |
Toàn bộ danh mục nằm ở Mã lỗi; cách đọc một lỗi nằm ở Lỗi.
Gọi từ trình duyệt
Một ứng dụng chạy trong trình duyệt, trên một nguồn gốc (origin) mà bản triển khai cho phép, gửi được header Idempotency-Key và đọc được header Idempotent-Replayed của phản hồi.