服务网格:限流保护 (上)

背景

限流是服务治理中保护服务的重要手段之一,也是最直接有效的手段,它可以保护服务不被瞬间的大流量冲垮,类似电路中的“保险丝”。在服务上线前,我们都会对服务进行基准测试,来了解可通过的最大“电流”。

上面所说的这类限流通常放置的入站一侧,对服务起到保护的作用。同样,出站一侧也有限流,这种限流防止程序错误导致瞬间发送大量请求导致其他服务故障,避免了错误的“蔓延”。

这篇文章中“入站限流”是主角,来为大家介绍服务网格如何在 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: foo
  rateLimit:
    local:
      tcp:
        connections: 1
        unit: minute
---
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
  name: http-rate-limit
spec:
  host: bar
  rateLimit:
    local:
      http:
        requests: 3
        unit: 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.local
  rateLimit:
    local:
      tcp:
        connections: 1
        unit: 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.local
  rateLimit:
    local:
      tcp:
        connections: 1
        unit: minute
        burst: 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.local
  rateLimit:
    local:
      http:
        requests: 3
        unit: 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.local
  rateLimit:
    local:
      http:
        requests: 3
        unit: minute
        responseStatusCode: 509
        responseHeadersToAdd:
          - name: hello
            value: 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.local
  rateLimit:
    local:
      http:
        requests: 3
        unit: minute
        burst: 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] UpstreamTrafficSettinghttps://github.com/flomesh-io/osm-edge/blob/release-v1.2/pkg/apis/policy/v1alpha1/upstreamtrafficsetting.go#L11

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值