使用Envoy 作Sidecar Proxy的微服務模式-5.rate limiter

本博客是深入研究Envoy Proxy和Istio.io 以及它如何實現更優雅的方式來連接和管理微服務系列文章的一部分。

這是接下來幾個部分的想法(將在發佈時更新鏈接):

  • 斷路器(第一部分)
  • 重試/超時(第二部分)
  • 分佈式跟蹤(第三部分)
  • Prometheus的指標收集(第四部分)
  • rate limiter(第五部分)

第五部分 - rate limiter

Envoy ratelimit filters

Envoy通過兩個過濾器與Ratelimit服務集成:

  • Network Level Filter: envoy爲安裝過濾器的偵聽器上的每個新連接調用Ratelimit服務。這樣,您可以對通過偵聽器的每秒連接進行速率限制。
  • HTTP Level Filter:Envoy爲安裝過濾器的偵聽器上的每個新請求調用Ratelimit服務,路由表指定應調用Ratelimit服務。許多工作都在擴展HTTP過濾器的功能。

envoy 配置 啓用 http rate limiter

http rate limiter 當請求的路由或虛擬主機具有與過濾器階段設置匹配的一個或多個速率限制配置時,HTTP速率限制過濾器將調用速率限制服務。該路由可以選擇包括虛擬主機速率限制配置。多個配置可以應用於請求。每個配置都會導致將描述符發送到速率限制服務。

如果調用速率限制服務,並且任何描述符的響應超出限制,則返回429響應。速率限制過濾器還設置x-envoy-ratelimited標頭。

果在呼叫速率限制服務中出現錯誤或速率限制服務返回錯誤並且failure_mode_deny設置爲true,則返回500響應。

全部的配置如下:

  envoy.yaml: |-
    static_resources:
      listeners:
      - address:
          socket_address:
            address: 0.0.0.0
            port_value: 8000
        filter_chains:
        - filters:
          - name: envoy.http_connection_manager
            config:
              codec_type: auto
              stat_prefix: ingress_http
              access_log:
              - name: envoy.file_access_log
                config:
                  path: "/dev/stdout"
                  format: "[ACCESS_LOG][%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n"              
              route_config:
                name: local_route
                virtual_hosts:
                - name: gateway
                  domains:
                  - "*"
                  routes:
                  - match:
                      prefix: "/cost"
                    route:
                      cluster: cost
                  rate_limits: # enable rate limit checks for the greeter service
                    actions:
                    - destination_cluster: {}
              http_filters:
              - name: envoy.rate_limit # enable the Rate Limit filter
                config:
                  domain: envoy            
              - name: envoy.router
                config: {}
      clusters:
      - name: cost
        connect_timeout: 0.25s
        type: strict_dns
        lb_policy: round_robin
        hosts:
        - socket_address:
            address: cost.sgt
            port_value: 80
      - name: rate_limit_cluster
        type: strict_dns
        connect_timeout: 0.25s
        lb_policy: round_robin
        http2_protocol_options: {}
        hosts:
        - socket_address:
            address: limiter.sgt
            port_value: 80
    rate_limit_service:
        grpc_service:
            envoy_grpc:
                cluster_name: rate_limit_cluster
            timeout: 0.25s
    admin:
      access_log_path: "/dev/null"
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 9000
          

通過配置文件可以看出,本demo設置的是一個全局的http filter rate limiter。

儘管分佈式熔斷通常在控制分佈式系統中的吞吐量方面非常有效,但有時它不是非常有效並且需要全局速率限制。最常見的情況是當大量主機轉發到少量主機並且平均請求延遲較低時(例如,對數據庫服務器的連接/請求)。如果目標主機已備份,則下游主機將淹沒上游羣集。在這種情況下,在每個下游主機上配置足夠嚴格的斷路限制是非常困難的,這樣系統在典型的請求模式期間將正常運行,但在系統開始出現故障時仍能防止級聯故障。全局速率限制是這種情況的一個很好的解決方案。

編寫rate limiter 服務

Envoy直接通過gRPC與速率限制服務集成。Envoy要求速率限制服務支持rls.proto中指定的gRPC IDL。有關API如何工作的更多信息,請參閱IDL文檔。

本身envoy 只是提供了限流的接口,沒有具體的實現,所以必須自己實現一個限流器。下面只是簡單實現一下,給大家一個思路。

具體的代碼如下:

package main

import (
    "log"
    "net"
    "time"

    rls "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2"
    "github.com/juju/ratelimit"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

// server is used to implement rls.RateLimitService
type server struct {
    bucket *ratelimit.Bucket
}

func (s *server) ShouldRateLimit(ctx context.Context,
    request *rls.RateLimitRequest) (*rls.RateLimitResponse, error) {
    // logic to rate limit every second request
    var overallCode rls.RateLimitResponse_Code
    if s.bucket.TakeAvailable(1) == 0 {
        overallCode = rls.RateLimitResponse_OVER_LIMIT
    } else {
        overallCode = rls.RateLimitResponse_OK
    }

    response := &rls.RateLimitResponse{OverallCode: overallCode}
    return response, nil
}

func main() {
    // create a TCP listener on port 8089
    lis, err := net.Listen("tcp", ":8089")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    log.Printf("listening on %s", lis.Addr())

    // create a gRPC server and register the RateLimitService server
    s := grpc.NewServer()

    rls.RegisterRateLimitServiceServer(s, &server{
        bucket: ratelimit.NewBucket(100*time.Microsecond, 100),
    })
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

具體項目,查閱github

PS:

  • 使用了令牌桶算法來限流。令牌桶算法(Token Bucket)和 Leaky Bucket 效果一樣但方向相反的算法,更加容易理解.隨着時間流逝,系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶裏加入Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了.新請求來臨時,會各自拿走一個Token,如果沒有Token可拿了就阻塞或者拒絕服務.

圖片描述

  • 該實現存在單點風險。
  • Dockerfile均在代碼倉庫中,大家可以構建鏡像自己測試。

結論

本文簡單講了envoy的 rate limit功能,提供了全侷限流的配置文件,並簡單實現了一個基於令牌桶的限流器。希望能幫助你理解Envoy的限速過濾器如何跟gRPC協議協同工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章