Dapr 彈性的策略

張善友發表於2022-03-12

雲原生應用需要處理 雲中很容易出現瞬時故障。原因在以下文件 暫時性故障處理[1] 中有具體說明。

任何環境、任何平臺或作業系統以及任何型別的應用程式都會發生暫時性故障。 在本地基礎結構上執行的解決方案中,應用程式及其元件的效能與可用性通常是通過昂貴但通常很少使用的硬體冗餘來維持的,並且元件與資源的位置互相靠近。 儘管此方法可以大大減少故障,但可能仍會導致暫時性故障,甚至是不可預見的事件(例如外部電源或網路問題或其他災難性的狀況)造成的中斷。

雲託管(包括私有云系統)可以通過跨許多商品計算節點使用共享資源、冗餘、自動故障轉移和動態資源分配,提供更高的整體可用性。 但是,這些環境的性質意味著更可能發生暫時性故障。 原因包括:

  • 雲環境中的許多資源是共享的,為了保護這些資源,會限制對這些資源的訪問。 某些服務在負載上升到特定級別時,或到達吞吐量比率的上限時,會拒絕連線以便處理現有的請求,併為所有使用者維持服務效能。 限制有助於為共享資源的鄰居與其他租戶維持服務質量。

  • 雲環境是使用大量商用硬體單元構建而成的。 雲環境將負載動態分散到多個計算單元和基礎結構元件上以提供效能,並通過自動回收或更換故障單元來提供可靠性。 這種動態性意味著可能偶爾會發生暫時性故障和暫時連線失敗。

  • 在應用程式與資源及其使用的服務之間,通常有多個硬體元件,包括網路基礎結構,例如路由器和負載均衡器。 這個附加的基礎結構偶爾會導致額外的連線延遲與暫時性連線故障。

  • 客戶端與伺服器之間的網路狀況會不時改變,尤其是通過 Internet 通訊時。 即使在本地位置,高流量負載也可能減慢通訊速度,並會導致間歇性的連線故障。


在許多情況下,恢復和切換是在雲內部完成的。如果呼叫者等待一段時間,然後重試,那麼它很有可能會成功。因此,建議[2]在應用程式中加入重試等提高彈性的機制。

Dapr 的誕生是為了減輕開發人員開發雲原生應用程式的負擔。應用程式開發人員很自然地會想,“我想知道 Dapr 是否會處理與彈性相關的問題。”

即將釋出的Dapr 1.7 版本 有一個組織良好的 [提案] 跨所有構建塊的彈性策略[3],這篇文章目的是總結 前面介紹介紹的問題,以及為做Dapr 技術選型的同仁提供參考,此外,我在寫作的另一個目的也是希望這將是你對 Dapr 的彈性產生興趣的機會。

Dapr 當前版本是1.6,Dapr 執行時在邊車(sidecar)呼叫端的實現是是上面這個提案要清理的主題,呼叫者的可恢復性需要利用Kubernetes的各種恢復功能等基礎設施來保證。

dapr-overview

呼叫模式分為服務到服務呼叫和元件呼叫。

如果服務呼叫失敗,每次呼叫重試[4]的回退間隔是 1 秒,最多重試三次。 通過 gRPC 連線目標 sidecar 的超時時間為5秒。

元件呼叫取決於每個實現

至此,大家看到了問題吧,上面這個提案就是要解決下面這幾個問題。

  • 服務和元件之間的規範和實現不一致
  • 有些專案在規範中沒有規定,需要確認執行。
  • 無法指定某些引數,例如重試次數和間隔。
  • 實現了許多重試,但我還想要其他模式,例如斷路器。

上述提案[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,我認為你應該注意以下幾點。

  • 在選擇時檢查每個元件包具有什麼樣的彈性改進功能。
  • 考慮呼叫服務和應用程式中要實現的功能

從Dapr 1.0 釋出以來的每個版本都在不斷改進Dapr 的各項功能,Dapr 大約每兩個月進行一次次要版本升級,這個月將釋出1.7 版本,具體參見 https://github.com/dapr/dapr/issues/4170


[1]暫時性故障處理: https://docs.microsoft.com/zh-cn/azure/architecture/best-practices/transient-faults#why-do-transient-faults-occur-in-the-cloud

[2]可靠性模式: https://docs.microsoft.com/zh-cn/azure/architecture/framework/resiliency/reliability-patterns

[3][提案] 跨所有構建塊的彈性策略: https://github.com/dapr/docs/issues/2059

[4] 服務呼叫重試: https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/#retries

相關文章