[ADP-362] 並發控制
reviewing phase 1
增加參考資料
概述
本規範涵蓋了兩種主要的並發控制機制: 樂觀鎖定和悲觀鎖定。
指導原則
- 如有必要,必須(MUST)確保並發操作期間的數據一致性和完整性。
- 應該(SHOULD)根據應用程序的需求和特性使用適當的並發控制機制。
- 必須(MUST)優雅地處理衝突並適當地通知客戶端。
樂觀鎖定
概念: 樂觀鎖定假設衝突很少發生,只在提交事務時檢查衝突。它使用 ETag 來檢測衝突。
實施指南:
使用 ETag 進行資源版本控制:
- 必須在資源表示中包含 ETag 標頭作為版本標識符。
- 對於需要精確控制的資源,應該使用強 ETag。
資源表示示例:
httpGET /users/123 HTTP/1.1 Host: example.com HTTP/1.1 200 OK Content-Type: application/json ETag: "abc123" { "id": 123, "email": "user@example.com", "phone": "+1234567890" }
在更新請求中包含 ETag:
- 必須要求客戶端在更新請求中使用
If-Match
標頭包含 ETag。 - 如果缺少
If-Match
標頭,應該拒絕更新。
包含 ETag 的更新請求示例:
httpPATCH /users/123 HTTP/1.1 Host: example.com Content-Type: application/json-patch+json If-Match: "abc123" [ { "op": "replace", "path": "/email", "value": "newemail@example.com" } ]
- 必須要求客戶端在更新請求中使用
衝突檢測:
- 在應用更新之前,必須比較提供的 ETag 與資源的當前 ETag。
- 如果 ETag 不匹配,表示資源已被另一個客戶端修改,必須拒絕更新。
錯誤回應:
- 當檢測到衝突時,必須返回
412 Precondition Failed
狀態碼。 - 應該使用 HTTP Problem Details 格式進行錯誤回應。
衝突回應示例:
httpHTTP/1.1 412 Precondition Failed Content-Type: application/problem+json { "type": "https://example.com/problems/concurrent-update", "title": "前提條件失敗", "status": 412, "detail": "該資源已被另一個客戶端修改。", "instance": "/users/123", "currentETag": "def456" }
- 當檢測到衝突時,必須返回
更新 ETag:
- 每次成功更新資源時,必須生成新的 ETag。
- 應該在回應標頭中包含更新後的 ETag。
成功更新回應示例:
httpHTTP/1.1 200 OK Content-Type: application/json ETag: "def456" { "id": 123, "email": "newemail@example.com", "phone": "+1234567890" }
悲觀鎖定
概念: 悲觀鎖定假設衝突經常發生,並在操作期間鎖定資源以防止其他客戶端修改它。
實施指南:
鎖定獲取:
- 在進行任何修改之前必須鎖定資源。
- 應該使用鎖定機制;我們在此引入類似 WebDAV 的機制。
- 當鎖定請求成功時,應該在回應標頭中返回
Lock-Token
。 - 應該在回應標頭中指定
Timeout
。
鎖定期間的操作:
- 應該在回應標頭中指定
Lock-Token
。
- 應該在回應標頭中指定
鎖定釋放:
- 操作完成後必須釋放鎖定。
- 應該確保即使操作失敗也釋放鎖定,通常使用 try-finally 塊或服務器超時機制。
- 應該在請求標頭中指定
Lock-Token
處理鎖定失敗:
- 如果資源已被另一個客戶端鎖定,必須返回
423 Locked
狀態碼。 - 應該使用 HTTP Problem Details 格式進行錯誤回應。
鎖定失敗回應示例:
httpHTTP/1.1 423 Locked Content-Type: application/problem+json { "type": "https://example.com/problems/locked", "title": "已鎖定", "status": 423, "detail": "該資源當前被另一個操作鎖定。", "instance": "/users/123" }
- 如果資源已被另一個客戶端鎖定,必須返回
長時間運行的操作:
- 應該最小化鎖定持續時間,以避免其他客戶端長時間等待。
- 可以使用鎖定超時機制來釋放持有時間過長的鎖定。
最佳實踐
- 清晰的文件:
- 應該清楚地記錄所使用的並發控制機制,包括如何在請求中包含 ETag 以及如何處理衝突。
- 客戶端處理:
- 應該建議客戶端如何處理
412 Precondition Failed
和423 Locked
回應,包括重試策略。
- 應該建議客戶端如何處理
- 一致性:
- 必須確保在資源的所有實例中一致地更新和檢查 ETag。
- 冪等操作:
- 應該設計更新操作為冪等的,以便使用相同的 ETag 重試操作不會導致不一致的狀態。
樂觀鎖定的示例工作流程
客戶端獲取資源:
- 客戶端檢索資源並獲取當前的 ETag。
httpGET /users/123 HTTP/1.1 Host: example.com
回應:
httpHTTP/1.1 200 OK Content-Type: application/json ETag: "abc123" { "id": 123, "email": "user@example.com", "phone": "+1234567890" }
客戶端發送更新請求:
- 客戶端使用
If-Match
標頭在更新請求中包含 ETag。
httpPATCH /users/123 HTTP/1.1 Host: example.com Content-Type: application/json-patch+json If-Match: "abc123" [ { "op": "replace", "path": "/email", "value": "newemail@example.com" } ]
- 客戶端使用
服務器檢查 ETag:
- 服務器檢查提供的 ETag 是否與資源的當前 ETag 匹配。
- 如果 ETag 匹配,服務器應用更新並生成新的 ETag。
- 如果 ETag 不匹配,服務器返回
412 Precondition Failed
回應。
成功更新回應:
- 如果更新成功,服務器回應更新後的資源和新的 ETag。
httpHTTP/1.1 200 OK Content-Type: application/json ETag: "def456" { "id": 123, "email": "newemail@example.com", "phone": "+1234567890" }
衝突回應:
- 如果檢測到衝突,服務器回應
412 Precondition Failed
狀態碼和資源的當前 ETag。
httpHTTP/1.1 412 Precondition Failed Content-Type: application/problem+json { "type": "https://example.com/problems/concurrent-update", "title": "前提條件失敗", "status": 412, "detail": "該資源已被另一個客戶端修改。", "instance": "/users/123", "currentETag": "def456" }
- 如果檢測到衝突,服務器回應
悲觀鎖定的示例工作流程
客戶端請求鎖定:
- 客戶端在執行更新之前請求對資源進行鎖定。
httpPOST /users/123/lock HTTP/1.1 Host: example.com Timeout: Second-60
回應:
httpHTTP/1.1 200 OK Content-Type: application/json Lock-Token: abc123 { "lockId": "abc123", "resource": "/users/123", "locked": true }
客戶端發送更新請求:
- 客戶端發送更新請求,包括鎖定標識符。
httpPATCH /users/123 HTTP/1.1 Host: example.com Content-Type: application/json-patch+json