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