SRE 彈性能力:使用 Envoy 對應用進行速率限制

做者:dm03514 前端

譯者:楊傳勝 git

原文:medium.com/dm03514-tec… 
github

速率限制是緩解級聯故障和防止耗盡共享資源的一種簡單有效的方法。Envoy 是一個功能豐富的代理,能夠爲任何服務輕鬆添加速率限制的功能。本文將介紹在不更改應用程序自己配置的前提下如何配置 Envoy 來強制對應用進行速率限制。web

問題

你是否遇到過資源被大量的請求淹沒或耗盡的狀況?你的客戶端是否具備回退重試或速率限制的邏輯?在微服務架構中,不對其使用量進行限制的資源很容易被客戶端發出的大量請求所淹沒。固然可能存在必定數量的客戶端,這些客戶端自己就已經實現了各類重試/回退和速率限制的策略。不對訪問量進行限制的客戶端會耗盡服務端的資源,從而使其餘客戶端沒法訪問服務,甚至有些客戶端會一直髮起請求,直到使服務徹底不可用。docker

API 的使用進行約束的經常使用方法是啓用速率限制。與基於 IP 的速率限制或者 web 框架提供的應用級別速率限制不一樣,Envoy 容許在 HTTP 層實現快速,高性能和可靠的全局速率限制。後端

上圖中左側的 Service Client 表明使用率特別高的客戶端。在運行期間,它可使負載均衡後端的全部服務實例流量飽和,並使其餘更高優先級的客戶端丟棄其請求。api

Envoy 可以對網絡級別的任何服務進行速率限制,而無需對應用程序進行任何修改。此外,因爲 Envoy 工做在 7 層,也就是應用程序級別,因此它能夠檢查 HTTP 速率信息並對其進行速率限制。bash

在本教程中,vegata 負載測試工具用於模擬上述示例中的批處理做業。下圖顯示了請求速率大約爲 500次/秒 的穩定狀態。服務器

譯者注:首先克隆 grokking-go 項目,而後進入 bolt-on-out-of-process-rate-limits 目錄微信

$ make load-test​echo "GET http://localhost:8080/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report複製代碼

在模擬後臺做業期間,對 API 資源 /slow 的訪問速率達到了每秒 3500 個請求,影響到了其餘端點和客戶端。

爲了解決這個問題,下面的解決方案將使用 Envoy 強制限制請求速率爲 500個請求/秒。但首先...

Envoy 是什麼?

Envoy 是一個輕量級代理服務器,可以處理任何 TCP/IP/HTTP/GRPC/HTTP2 等協議的鏈接。它具備高度可配置性,並支持許多不一樣的插件。它還使可觀察性成爲一等公民。

在 Envoy 橫空出世以前,應用程序級別的重試、延遲注入、速率限制和熔斷都要經過應用程序自己的代碼邏輯來實現。Envoy 將這些功能從應用程序中剝離出來,並讓運維管理人員可以配置和啓用這些功能,無需對應用程序自己做任何修改。

Envoy 的 官方文檔Matt Klein 的文章提供了一個比我更好的對 Envoy 的介紹:

Envoy 是一款由 Lyft 開源的,使用 C++ 編寫的高性能分佈式代理,專爲單體服務和應用而設計。它也被做爲大型微服務框架 Istio service mesh 的通訊總線和通用數據平面。經過借鑑 NGINX、HAProxy、硬件負載均衡器和雲負載均衡器等解決方案,Envoy 做爲一個獨立的進程與應用程序一塊兒運行,並經過與平臺無關的方式提供一些高級特性,從而造成一個對應用透明的通訊網格。當基礎設施中的全部服務流量經過 Envoy 網格流動時,經過一致的可觀察性,調整總體性能和添加更多底層特性,一旦發生網絡和應用程序故障,可以很容易定位出問題的根源。

解決方案

全部代碼和示例均可以在 GitHub 上找到。

下面給出具體的解決方案:

  • 將 Envoy 配置爲 API 負載均衡器的前端代理;仍然容許全部流量經過。

  • 配置並運行全局速率限制服務。

  • 配置 Envoy 使用全局速率限制服務。

咱們須要一種方法來限制同一時間發出的請求數量,以便將 API 負載均衡器與請求達到高峯的客戶端隔離,並確保其餘客戶端在執行這些批處理做業(經過 vegeta 來模擬)期間能夠繼續訪問 API。爲了達到這個目的,咱們將 Envoy 代理和批處理客戶端 vegeta 部署在同一臺機器上。

經過將 Envoy 做爲 Sidecar 與批處理客戶端一塊兒運行,在請求達到負載均衡以前就能夠對請求進行速率限制。使用 Envoy 是一個很明智的選擇,由於它具備高度可配置性,高性能,而且能夠很好地處理 HTTP 請求之間的平衡。

將 Envoy 配置爲 API 負載均衡器的前端代理

第一步是將 Envoy 配置爲處於批處理做業客戶端和 API 負載均衡器之間,客戶端向 API 發起的全部請求都會首先通過 Envoy 的處理。首先須要讓 Envoy 知道如何鏈接 API,而後再更新批處理做業的配置,使該客戶端向 Envoy 發出請起,而不是直接向 API 發出請求。配置完以後的最終狀態以下圖所示:

此步驟僅經過 Envoy 來對 API 流量進行路由,還沒有對應用進行速率限制。爲了達到限速的目的,咱們還須要作一些額外的配置:

cluster

Cluster 表示 Envoy 鏈接到的一組邏輯上類似的上游主機(在本示例中表示 API 負載均衡器)。Cluster 的配置很是簡單:

clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080複製代碼

在本示例中,咱們運行了一個監聽在 localhost:8080 上的 fakapi 來模擬上圖中的負載均衡器。經過 Envoy 向 API 發出的任何請求都會被髮送到 localhost:8080

virtual_host

virtual_host 部分的配置用來確保全部請求都會路由到上面定義的 API 集羣。

- name: api  domains:  - "*"  routes:  - match:      prefix: "/"    route:      cluster: api複製代碼

其他的配置文件用來肯定 Envoy 自己監聽在哪一個地址以及 Envoy 與其餘服務之間的鏈接規則。

static_resources:  listeners:  - name: listener_0    address:      socket_address: { address: 0.0.0.0, port_value: 10000}    filter_chains:    - filters:      - name: envoy.http_connection_manager        config:          stat_prefix: ingress_http          codec_type: AUTO          route_config:            name: remote_api            virtual_hosts:            - name: api              domains:              - "*"              routes:              - match:                  prefix: "/"                route:                  cluster: api​          http_filters:          - name: envoy.router​  clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080​admin:  access_log_path: "/dev/null"  address:    socket_address:      address: 0.0.0.0      port_value: 9901複製代碼

更新負載測試工具的參數,直接訪問本地的 Envoy 代理,經過儀表板能夠觀察到 Envoy 正在接收流量。下圖的 Envoy 儀表板來自 Grafana 官方儀表板倉庫Lyft 也提供了一份 Envoy 儀表板)。

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=500​echo "GET http://localhost:10000/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report複製代碼

上圖顯示 Envoy 如今正在接收客戶端發送給 API 的全部請求,並將它們發送到上游的負載均衡器!

配置並運行全局速率限制服務

此步驟將配置運行 Lyft 開源的全局 速率限制 服務。運行該服務很是簡單,只須要克隆它的代碼倉庫,修改一部分配置文件,而後經過 docker-compose 啓動就好了。

首先克隆 Ratelimit 代碼倉庫並修改配置文件,更新 domain 字段以及 descriptor 字段的 keyvalue

$ cat examples/ratelimit/config/config.yaml複製代碼
---domain: apisdescriptors:  - key: generic_key    value: default    rate_limit:      unit: second      requests_per_unit: 500複製代碼

接下來使用docker-compose 的配置文件(docker-compose.yml)來啓動全局速率限制服務(詳細步驟請參考 README):

$ docker-compose down && docker-compose up複製代碼

配置 Envoy 使用全局速率限制服務

最後一步是配置 Envoy 使用全局速率限制服務,以強制執行速率限制並下降對 API 的請求速率。配置生效後,Envoy 將會檢查每一個傳入鏈接的速率限制,並根據上面的配置過濾掉一部分請求(限制最多 500 個請求/秒)。

開啓了速率限制的 Envoy 配置文件以下所示:

static_resources:  listeners:  - name: listener_0    address:      socket_address: { address: 0.0.0.0, port_value: 10000}    filter_chains:    - filters:      - name: envoy.http_connection_manager        config:          use_remote_address: true          stat_prefix: ingress_http          codec_type: AUTO          route_config:            name: remote_api            virtual_hosts:            - name: api              domains:              - "*"              routes:              - match:                  prefix: "/"                route:                  cluster: api              rate_limits:                - stage: 0                  actions:                    - {generic_key: {descriptor_value: "default"}}​          http_filters:          - name: envoy.rate_limit            config:              domain: apis              stage: 0​          - name: envoy.router​  clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080​  - name: rate_limit_cluster    type: strict_dns    connect_timeout: 0.25s    lb_policy: round_robin    http2_protocol_options: {}    hosts:    - socket_address:        address: localhost        port_value: 8081​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: 9901複製代碼

而後,咱們以 1000個請求/秒 的速率(速率限制的2倍)運行負載測試工具:

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000​echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta report複製代碼

能夠查看一下 ratelimiter 服務的日誌,日誌中顯示了它接收的請求和它進行速率限制檢查的過程:

msg="cache key: apis_generic_key_default_1540829538 current: 35"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 34"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 33"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 31"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 32"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 42"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="starting get limit lookup"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 46"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"複製代碼

若是速率限制功能沒法生效,能夠參考該 issue 中的討論。

運行一段時間後,中止負載測試打印出測試報告,能夠看到其中 1/2 的請求被 Envoy 限制了,被限制的請求的狀態碼爲 429 !!!

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000​echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta reportRequests      [total, rate]            128093, 1000.02Duration      [total, attack, wait]    2m8.102168403s, 2m8.090470728s, 11.697675msLatencies     [mean, 50, 95, 99, max]  10.294365ms, 11.553135ms, 33.428287ms, 52.678127ms, 177.709494msBytes In      [total, mean]            1207354, 9.43Bytes Out     [total, mean]            0, 0.00Success       [ratio]                  52.69%Status Codes  [code:count]             200:67494  429:60599Error Set:429 Too Many Requests複製代碼

經過 Envoy 暴露的速率限制指標(envoy_cluster_ratelimit_over_limit)或(4xx 響應)的速率來繪製儀表板,能夠看到相應的可視化圖表:

經過可視化 API 服務實際看到的請求數量,能夠證實請求速率在 500個請求/秒 上下波動,這正是咱們所指望的!

再查看一下 Envoy 傳出的 API 鏈接,能夠看到傳出請求速率也在 500個請求/秒 上下波動!

實驗成功!

總結

但願經過本文的講解能讓你明白配置 Envoy 以減輕貪婪客戶端對 API 資源的消耗是多麼簡單。我發現這種模式很是有用,由於彈性能力是爲應用開發更多功能的基礎。在 Envoy 橫空出世以前,應用程序級別的重試、延遲注入、速率限制和熔斷都要經過應用程序自己的代碼邏輯來實現。Envoy 將這些功能從應用程序中剝離出來,並讓運維管理人員可以配置和啓用這些功能,無需對應用程序自己做任何修改。Envoy 徹底顛覆了咱們對服務彈性能力的認知,但願你讀這篇文章時能和我寫這篇文章時同樣興奮!

參考資料

ServiceMesher社區信息

微信羣:聯繫我入羣

社區官網:www.servicemesher.com

Slack:servicemesher.slack.com 須要邀請才能加入

Twitter: twitter.com/servicemesh…

GitHub:github.com/

更多Service Mesh諮詢請掃碼關注微信公衆號ServiceMesher。

相關文章
相關標籤/搜索