雲原生應用需要處理 雲中很容易出現瞬時故障。原因在以下文檔 暫時性故障處理[1] 中有具體說明。
任何環境、任何平台或操作系統以及任何類型的應用程序都會發生暫時性故障。 在本地基礎結構上運行的解決方案中,應用程序及其組件的性能與可用性通常是通過昂貴但通常很少使用的硬件冗余來維持的,並且組件與資源的位置互相靠近。 盡管此方法可以大大減少故障,但可能仍會導致暫時性故障,甚至是不可預見的事件(例如外部電源或網絡問題或其他災難性的狀況)造成的中斷。
雲托管(包括私有雲系統)可以通過跨許多商品計算節點使用共享資源、冗余、自動故障轉移和動態資源分配,提供更高的整體可用性。 但是,這些環境的性質意味着更可能發生暫時性故障。 原因包括:
-
雲環境中的許多資源是共享的,為了保護這些資源,會限制對這些資源的訪問。 某些服務在負載上升到特定級別時,或到達吞吐量比率的上限時,會拒絕連接以便處理現有的請求,並為所有用戶維持服務性能。 限制有助於為共享資源的鄰居與其他租戶維持服務質量。
-
雲環境是使用大量商用硬件單元構建而成的。 雲環境將負載動態分散到多個計算單元和基礎結構組件上以提供性能,並通過自動回收或更換故障單元來提供可靠性。 這種動態性意味着可能偶爾會發生暫時性故障和暫時連接失敗。
-
在應用程序與資源及其使用的服務之間,通常有多個硬件組件,包括網絡基礎結構,例如路由器和負載均衡器。 這個附加的基礎結構偶爾會導致額外的連接延遲與暫時性連接故障。
-
客戶端與服務器之間的網絡狀況會不時改變,尤其是通過 Internet 通信時。 即使在本地位置,高流量負載也可能減慢通信速度,並會導致間歇性的連接故障。
在許多情況下,恢復和切換是在雲內部完成的。如果調用者等待一段時間,然后重試,那么它很有可能會成功。因此,建議[2]在應用程序中加入重試等提高彈性的機制。
Dapr 的誕生是為了減輕開發人員開發雲原生應用程序的負擔。應用程序開發人員很自然地會想,“我想知道 Dapr 是否會處理與彈性相關的問題。”
即將發布的Dapr 1.7 版本 有一個組織良好的 [提案] 跨所有構建塊的彈性策略[3],這篇文章目的是總結 前面介紹的問題,以及為做Dapr 技術選型的同仁提供參考,此外,我在寫作的另一個目的也是希望這將是你對 Dapr 的彈性產生興趣的機會。
Dapr 當前版本是1.6,Dapr 運行時在邊車(sidecar)調用端的實現是是上面這個提案要清理的主題,調用者的可恢復性需要利用Kubernetes的各種恢復功能等基礎設施來保證。
調用模式分為服務到服務調用和組件調用。
如果服務調用失敗,每次調用重試[4]的回退間隔是 1 秒,最多重試三次。 通過 gRPC 連接目標 sidecar 的超時時間為5秒。
- 實現invokeWithRetry :https://github.com/dapr/dapr/blob/release-1.6/pkg/messaging/direct_messaging.go#L136
- 定義是一個常數: https://github.com/dapr/dapr/blob/release-1.6/pkg/retry/retry.go#L20
組件調用取決於每個實現
- 每個組件的規格(按構建塊): https://v1-6.docs.dapr.io/reference/components-reference/
- 有些可以指定重試的參數
- 按構建塊(components-contrib)為每個組件提供包的源代碼:https://github.com/dapr/components-contrib
- 在每個構建塊目錄下,每個組件都有一個 Go 包
- 許多組件的包都單獨實現了重試功能。
- dapr/kit 中有一個 retry 包,但是使用是可選的:https://github.com/dapr/kit/blob/main/retry/retry.go
至此,大家看到了問題吧,上面這個提案就是要解決下面這幾個問題。
- 服務和組件之間的規范和實現不一致
- 有些項目在規范中沒有規定,需要確認執行。
- 無法指定某些參數,例如重試次數和間隔。
- 實現了許多重試,但我還想要其他模式,例如斷路器。
上述提案[3]討論了解決問題的方向以及如何進行。
雖然提高彈性的機制是非常好的,但它也包括風險,比如通過重試重復寫入是典型的,假如你的接口不是冪等的。提案里面考慮到兩個階段來實施,或許第一階段在1.7版本發布后會有很多反饋。預計到這一點,階段 1 將重點放在基本功能上。
- 階段 1:可分配給每個構建塊或組件的通用彈性策略
- 將彈性策略定義為 Kubernetes 自定義資源
- 來自有關超時、重試和斷路器的策略
- 階段 2:允許覆蓋的特定於 API 的策略
階段1
在第 1 階段,建議使用以下自定義資源:請參閱 https://github.com/dapr/dapr/issues/3586
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: daprResiliency
# Like in the Subscriptions CRD, scopes lists the Dapr App IDs that this
# configuration applies to.
scopes:
- app1
- app2
spec:
#------------------------------------------------------------------------------
# PHASE 1: Basic policy definition and applying policies to building blocks
#------------------------------------------------------------------------------
policies:
# Timeouts are simple named durations.
timeouts:
general: 5s
important: 60s
largeResponse: 10s
# Retries are named templates for and are instantiated for life of the operation.
retries:
general: {} # Sane defaults
pubsubRetry:
policy: constant
duration: 5s
maxRetries: 10
retryForever:
policy: exponential
maxInterval: 15s
maxRetries: 0 # Retry indefinitely
important:
policy: constant
duration: 5s
maxRetries: 30
someOperation:
policy: exponential
maxInterval: 15s
largeResponse:
policy: constant
duration: 5s
maxRetries: 3
# Circuit breakers are automatically instantiated per component, service endpoint, and application route.
# using these settings as a template. See logic below under `buildingBlocks`.
# Circuit breakers maintain counters that can live as long as the Dapr sidecar.
circuitBreakers:
general: {} # Sane defaults
pubsubCB:
maxRequests: 1
interval: 8s
timeout: 45s
trip: consecutiveFailures > 8
# This section specifies default policies for:
# * service invocation
# * requests to components
# * events sent to routes
buildingBlocks:
services:
appB:
timeout: general
retry: general
# Circuit breakers for services are scoped per endpoint (e.g. hostname + port).
# When a breaker is tripped, that route is removed from load balancing for the configured `timeout` duration.
circuitBreaker: general
actors:
myActorType:
timeout: general
retry: general
# Circuit breakers for actors are scoped by type, id, or both.
# When a breaker is tripped, that type or id is removed from the placement table for the configured `timeout` duration.
circuitBreaker: general
circuitBreakerScope: both
circuitBreakerCacheSize: 5000
components:
# For state stores, policies apply to saving and retrieving state.
# Watching, which is not implemented yet, is out of scope.
statestore1:
timeout: general
retry: general
# Circuit breakers for components are scoped per component configuration/instance (e.g. redis1).
# When this breaker is tripped, all interaction to that component is prevented for the configured `timeout` duration.
circuitBreaker: general
# For Pub/Sub, policies apply only to publishing.
# Subscribing/consuming is handled by routes.
pubsub1:
retry: pubsubRetry
circuitBreaker: pubsubCB
pubsub2:
retry: pubsubRetry
circuitBreaker: pubsubCB
# Routes represent the application's URI/paths that receive incoming events from both
# PubSub and Input binding components. The route values correspond to the value configured
# in the Subscription configuration or programmatic call. For input bindings, this is
# configured in the component metadata via #3566.
routes:
'dsstatus.v3':
timeout: general
retry: general
circuitBreaker: general
#------------------------------------------------------------------------------
# PHASE 2: Overriding policies for specific Dapr API operations
#------------------------------------------------------------------------------
apis:
invoke:
- match: appId == "appB"
# Nested rules: Prevent duplicative checks in rules.
# Its likely that controler-gen does not support this
# but apiextensionsv1.JSON can be used as workaround.
rules:
- match:
request.method == "GET" &&
request.metadata.count > 1000
timeout: largeResponse
retry: largeResponse
- match:
request.path == "/someOperation"
retry: someOperation
publish:
- match: |
event.type == "important.event.v1"
timeout: important
retry: important
# subscribe is when Dapr attempts to deliver a pubsub event to an application route.
subscribe:
- match: |
event.type == "important.event.v1"
timeout: important
retry: retryForever
Default resiliency.yml that could be installed by dapr init. Might include commented out examples for Redis.
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: daprResiliency
scopes:
spec:
policies:
# Timeouts are simple named durations.
timeouts:
general: 5s
important: 60s
largeResponse: 10s
# Retries are named templates for and are instantiated for life of the operation.
retries:
general: {} # Sane defaults
# Circuit breakers are automatically instantiated per component, service endpoint, and application route.
# using these settings as a template. See logic below under `buildingBlocks`.
# Circuit breakers maintain counters that can live as long as the Dapr sidecar.
circuitBreakers:
general: {} # Sane defaults
# This section specifies default policies for:
# * service invocation
# * requests to components
# * events sent to routes
buildingBlocks:
services:
actors:
components:
# Routes represent the application's URI/paths that receive incoming events from both
# PubSub and Input binding components. The route values correspond to the value configured
# in the Subscription declarative or programmatic configurations.
routes:
apis:
invoke:
publish:
subscribe:
定義超時、重試和斷路器策略,並將它們分配給構成構建塊的服務和組件。您可以根據您的目的創建策略,例如確定超時之前的時間以及固定/成倍增加的重試間隔。
目前,階段 1 的目標是在計划於2022/3發布的 Dapr v1.7 中發布。當然,您必須支持每個組件包才能使用此自定義資源,但這是重要的第一步。第二階段根據第 1 階段的反饋,此時計划使用特定於 API 的策略定義的覆蓋。
基於此,如果你現在使用 Dapr,我認為你應該注意以下幾點。
- 在選擇時檢查每個組件包具有什么樣的彈性改進功能。
- 考慮調用服務和應用程序中要實現的功能
- 即使我們期待未來的擴展,也需要最少的錯誤處理
- 參考 SDK 以及如何使用 它
- 最好從 SDK 的角度檢查已知的問題、問題和情況。
- 示例:從狀態 apis --Go SDK 中刪除重試選項
從Dapr 1.0 發布以來的每個版本都在不斷改進Dapr 的各項功能,Dapr 大約每兩個月進行一次次要版本升級,這個月將發布1.7 版本,具體參見 https://github.com/dapr/dapr/issues/4170。
[2]可靠性模式: https://docs.microsoft.com/zh-cn/azure/architecture/framework/resiliency/reliability-patterns
[3][提案] 跨所有構建塊的彈性策略: https://github.com/dapr/docs/issues/2059

