背景
限流是服务治理中保护服务的重要手段之一,也是最直接有效的手段,它可以保护服务不被瞬间的大流量冲垮,类似电路中的“保险丝”。在服务上线前,我们都会对服务进行基准测试,来了解可通过的最大“电流”。
上面所说的这类限流通常放置的入站一侧,对服务起到保护的作用。同样,出站一侧也有限流,这种限流防止程序错误导致瞬间发送大量请求导致其他服务故障,避免了错误的“蔓延”。
这篇文章中“入站限流”是主角,来为大家介绍服务网格如何在 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