背景
限流是服務治理中保護服務的重要手段之一,也是最直接有效的手段,它可以保護服務不被瞬間的大流量沖垮,類似電路中的“保險絲”。在服務上線前,我們都會對服務進行基準測試,來了解可通過的最大“電流”。
上面所說的這類限流通常放置的入站一側,對服務起到保護的作用。同樣,出站一側也有限流,這種限流防止程序錯誤導致瞬間發送大量請求導致其他服務故障,避免了錯誤的“蔓延”。
這篇文章中“入站限流”是主角,來為大家介紹服務網格如何在 4 層和 7 層網絡提供限流保護的功能。
實現
在 osm-edge 中提供了 CRD?UpstreamTrafficSetting
[1],提供到上游流量的設置,限流就是其中之一。服務網格可以作用在 4 層和 7 層網絡上,限流在 4 層和 7 層網絡上的體現有所不同。
4 層網絡上的限流是限制連接創建的速度,也就是單位時間窗口內創建連接的數量;而 7 層網絡上則是消息的發送速度,即單位時間窗口內發送消息的數量。
在下面的例子中,分別定義了 4 層和 7 層網絡上的限流,前者 **限制每分鐘創建的連接數為?1
**,后者 **限制每分鐘發送的消息數為?3
**。
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?tcp-rate-limit
spec:host:?foorateLimit:local:tcp:connections:?1unit:?minute
---
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?http-rate-limit
spec:host:?barrateLimit:local:http:requests:?3unit:?minute
---
細心的讀者可能發現了配置中的?local
?字眼。是的,這里的實現都是本地限速。與本地限速相對的是全局限速,前者的統計維度是當前的服務實例,后者則可以有更大的維度,比如集群中的所有實例,甚至跨多個集群的所有實例。全局限速需要一個中心化的計數組件,而在實現上需要在性能和準確性上做取舍:每個請求都檢查中心化的組件,還是定期從中心化組件申請“配額”,每個請求只進行本地統計(可以理解為本地限速的變種)。在即將發布的版本中,osm-edge 將提供全局限速的支持。
接下來就為大家演示限流功能的使用。
演示
環境準備
export?INSTALL_K3S_VERSION=v1.23.8+k3s2
curl?-sfL?https://get.k3s.io?|?sh?-s?-?--disable?traefik?--write-kubeconfig-mode?644?--write-kubeconfig?~/.kube/config
下載 osm-edge CLI
system=$(uname?-s?|?tr?[:upper:]?[:lower:])
arch=$(dpkg?--print-architecture)
release=v1.2.0
curl?-L?https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz?|?tar?-vxzf?-
./${system}-${arch}/osm?version
cp?./${system}-${arch}/osm?/usr/local/bin/
安裝 osm-edge
export?osm_namespace=osm-system?
export?osm_mesh_name=osm?osm?install?\--mesh-name?"$osm_mesh_name"?\--osm-namespace?"$osm_namespace"?\--set=osm.image.pullPolicy=Always
創建命名空間并加入服務網格
kubectl?create?namespace?samples
osm?namespace?add?samples
部署示例應用
kubectl?apply?-n?samples?-f?https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/fortio/fortio.yaml
kubectl?apply?-n?samples?-f?https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/fortio/fortio-client.yaml
確保 pod 啟動并正常運行
kubectl?wait?--for=condition=ready?pod?--all?-n?samples
pod/fortio-client-b9b7bbfb8-2hmj2?condition?met
pod/fortio-c4bd7857f-zww46?condition?met
fortio
?啟動后會監聽幾個端口,后面我們會用到 TCP 端口?8078
?和 HTTP 端口?8080
。
TCP 限流
在開啟限流之前,我們先驗證下訪問。執行下面的命令,fortio-client
?會通過?3
?個并發(-c 3
)向?fort
?服務發送?10
?個 TCP 請求(-n 10
)。
fortio_client="$(kubectl?get?pod?-n?samples?-l?app=fortio-client?-o?jsonpath='{.items[0].metadata.name}')"kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-qps?-1?-c?3?-n?10?tcp://fortio.samples.svc.cluster.local:8078
從結果?tcp OK : 10 (100.0 %)
?可以看出所有的請求發送成功。
Fortio?1.38.4?running?at?1?queries?per?second,?2->2?procs,?for?10?calls:?tcp://fortio.samples.svc.cluster.local:8078
08:16:50?I?tcprunner.go:239>?Starting?tcp?test?for?tcp://fortio.samples.svc.cluster.local:8078?with?3?threads?at?1.0?qps
Starting?at?1?qps?with?3?thread(s)?[gomax?2]?:?exactly?10,?3?calls?each?(total?9?+?1)
08:16:59?I?periodic.go:809>?T002?ended?after?9.000531122s?:?3?calls.?qps=0.3333136633089462408:16:59?I?periodic.go:809>?T001?ended?after?9.000573284s?:?3?calls.?qps=0.3333121019449943
08:17:02?I?periodic.go:809>?T000?ended?after?12.001176928s?:?4?calls.?qps=0.33330064409496224
Ended?after?12.001215699s?:?10?calls.?qps=0.83325
Sleep?times?:?count?7?avg?4.2815741?+/-?0.2468?min?3.991364314?max?4.499322287?sum?29.9710185
Aggregated?Function?Time?:?count?10?avg?0.0029061275?+/-?0.003739?min?0.000404332?max?0.008700971?sum?0.029061275
#?range,?mid?point,?percentile,?count
>=?0.000404332?<=?0.001?,?0.000702166?,?70.00,?7
>?0.008?<=?0.00870097?,?0.00835049?,?100.00,?3
#?target?50%?0.000801444
#?target?75%?0.00811683
#?target?90%?0.00846731
#?target?99%?0.00867761
#?target?99.9%?0.00869863
Error?cases?:?no?data
Sockets?used:?3?(for?perfect?no?error?run,?would?be?3)
Total?Bytes?sent:?240,?received:?240
tcp?OK?:?10?(100.0?%)
All?done?10?calls?(plus?0?warmup)?2.906?ms?avg,?0.8?qps
現在我們試著添加策略,將連接限流到?1/min
?,意味著每分鐘只能創建一個連接。
kubectl?apply?-n?samples?-f?-?<<EOF
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?tcp-rate-limit
spec:host:?fortio.samples.svc.cluster.localrateLimit:local:tcp:connections:?1unit:?minute
EOF
再次使用前面的命令發送請求。從結果來看,只有 3 次請求成功,因為客戶端設置了并發數為?3
。
kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-qps?-1?-c?3?-n?10?tcp://fortio.samples.svc.cluster.local:8078...
tcp?OK?:?3?(30.0?%)
tcp?short?read?:?7?(70.0?%)
...
接下來修改策略,加上一個波動?burst: 10
?允許短時間的波動峰值?10
。
kubectl?apply?-n?samples?-f?-?<<EOF
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?tcp-rate-limit
spec:host:?fortio.samples.svc.cluster.localrateLimit:local:tcp:connections:?1unit:?minuteburst:?10
EOF
應用新的策略之后,再次發送請求可以發現所有請求發送成功。
kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-qps?-1?-c?3?-n?10?tcp://fortio.samples.svc.cluster.local:8078...
tcp?OK?:?10?(100.0?%)
...
測試完成后,記得刪除已經應用的限流策略。
kubectl?delete?upstreamtrafficsettings?-n?samples?tcp-rate-limit
HTTP 限流
在開始之前,我們先驗證不限流的情況。這次改為?3
?個并發(-c 3
)發送 10 個 HTTP 請求(-n 10
)到?fortio
?的?8080
?端口。可以看到在不限流的情況下,所有請求發送成功。
kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-c?3?-n?10?http://fortio.samples.svc.cluster.local:8080...
IP?addresses?distribution:
10.43.52.220:8080:?3
Code?200?:?10?(100.0?%)
...
應用下面的限流策略,將流量限制到?3/min
,及每分鐘?3
?次。
kubectl?apply?-n?samples?-f?-?<<EOF
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?http-rate-limit
spec:host:?fortio.samples.svc.cluster.localrateLimit:local:http:requests:?3unit:?minute
EOF
還是同樣的方式發送 10 個請求,從結果來看 3 個請求發送成功(Code 200
),7 個請求被限流(Code 429
),符合預期。
kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-c?3?-n?10?http://fortio.samples.svc.cluster.local:8080...
IP?addresses?distribution:
10.43.52.220:8080:?7
Code?200?:?3?(30.0?%)
Code?429?:?7?(70.0?%)
...
響應狀態碼?429
?說明請求被限流,該狀態碼支持定制,比如我們在當前策略的基礎上將狀態碼修改為?529
,并在響應頭部添加?hello: flomesh
。
kubectl?apply?-f?-?<<EOF
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?http-rate-limit
spec:host:?fortio.samples.svc.cluster.localrateLimit:local:http:requests:?3unit:?minuteresponseStatusCode:?509responseHeadersToAdd:-?name:?hellovalue:?flomesh
EOF
假如再發送請求會發現被限流的請求收到?509
?狀態碼。
IP?addresses?distribution:
10.43.52.220:8080:?7
Code?200?:?3?(30.0?%)
Code?509?:?7?(70.0?%)
與 TCP 限流一樣,HTTP 的限流也支持波動峰值的設置,同樣將波動峰值設置為?10
。
kubectl?apply?-n?samples?-f?-?<<EOF
apiVersion:?policy.openservicemesh.io/v1alpha1
kind:?UpstreamTrafficSetting
metadata:name:?http-rate-limit
spec:host:?fortio.samples.svc.cluster.localrateLimit:local:http:requests:?3unit:?minuteburst:?10
EOF
再次請求,會發現所有的 10 個請求都發送成功。
kubectl?exec?"$fortio_client"?-n?samples?-c?fortio-client?--?fortio?load?-c?3?-n?10?http://fortio.samples.svc.cluster.local:8080...
IP?addresses?distribution:
10.43.52.220:8080:?3
Code?200?:?10?(100.0?%)
...
總結
本篇介紹了服務網格中限流功能的使用,并通過實際的演示來測試限流的效果。由于篇幅的原因,在上面的例子中不管是 4 層還是 7 層的,限流的作用都是在主機(host
)的粒度,即以?host: fortio.samples.svc.cluster.local
?為統計的粒度。這種粒度在 4 層網絡上能夠滿足需求,但是在 7 層網絡實際環境中會有更多的需求。比如有些?path
?只會讀寫緩存,而有些會讀寫數據庫或者調用其他的服務(RPC),不同的?path
?的性能會存在差異。因此,需要在更細的粒度上進行限流控制,這將在下一篇中為大家介紹。
引用鏈接
[1]
?UpstreamTrafficSetting
:?https://github.com/flomesh-io/osm-edge/blob/release-v1.2/pkg/apis/policy/v1alpha1/upstreamtrafficsetting.go#L11