在这篇文章我们将介绍如何在 Flomesh 服务网格中使用熔断功能。
背景
在微服务架构设计中,熔断是服务可用性保障除了限流外的另一种手段。熔断是一种自我保护机制,对链路中的问题进行隔离防止服务雪崩。熔断的开启通常有一定的触发条件,通常情况下都是关闭的,只有达到条件时才会打开。这点类似电路中的保险丝,当电压过高时就会熔断,防止电路中的电器损毁。
降级与熔断的结果是一样的,都会使应用不对外提供服务或者进行简单的处理。不同的地方则是降级是人为发起、主动的行为,因为某种原因主动开启。比如电商活动促销时,会将某些非核心服务降级,释放资源给核心的服务。
Flomesh 服务网格方案
在服务网格中,我们将接收、处理请求和返回响应的服务称之为上游(Upstream)。
在 Flomesh 服务网格 中提供了 UpstreamTrafficSetting API
对发往上游的流量进行控制,在介绍 服务网格的限流功能 时就增用过这个 API,这次我们来介绍它的另一部分功能:熔断。Flomesh 服务网格的熔断可以由错误请求或慢调用请求触发:
spec.host
字段用于配置上游服务的主机名,这里配置上游主机fortio.server.svc.cluster.local
,熔断的设置真对发往该主机的请求生效。connectionSettings.http.circuitBreaking
:是 7 层的熔断策略statTimeWindow
:熔断统计的时间窗口,1m
。minRequestAmount
:触发熔断的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断。结合统计的时间窗口指熔断配置在 1 分钟内请求数超过 200 才会生效,才有可能触发熔断。低于 200,即使异常比率超出阈值也不会触发熔断。errorAmountThreshold
:触发熔断的错误请求数阈值为100
errorRatioThreshold
:触发熔断的错误请求比例阈值为50%
slowTimeThreshold
:慢调用耗时阈值100ms
slowAmountThreshold
:慢调用请求数阈值100
slowRatioThreshold
:慢调用请求比例阈值10%
degradedTimeWindow
:熔断的持续时间为1m
degradedStatusCode
:熔断开启时的响应状态码为503
degradedResponseContent
:熔断开启式响应内容为Service Unavailable!
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
name: http-circuit-breaking
spec:
host: fortio.server.svc.cluster.local
connectionSettings:
http:
circuitBreaking:
statTimeWindow: 1m
minRequestAmount: 200
errorAmountThreshold: 100
errorRatioThreshold: 0.50
slowTimeThreshold: 100ms
slowAmountThreshold: 100
slowRatioThreshold: 0.10
degradedTimeWindow: 1m
degradedStatusCode: 503
degradedResponseContent: 'Service Unavailable!'
演示
下载 CLI
system=$(uname -s | tr [:upper:] [:lower:])
arch=$(dpkg --print-architecture)
release=v1.3.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"
部署示例应用
使用 fortio
作为客户端和服务端,部署服务端。
kubectl create namespace server
osm namespace add server
kubectl apply -n server -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: fortio
labels:
app: fortio
service: fortio
spec:
ports:
- port: 8080
name: http-8080
- port: 8078
name: tcp-8078
- port: 8079
name: grpc-8079
selector:
app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio
spec:
replicas: 1
selector:
matchLabels:
app: fortio
template:
metadata:
labels:
app: fortio
spec:
containers:
- name: fortio
image: fortio/fortio:latest_release
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
- containerPort: 8078
name: tcp
- containerPort: 8079
name: grpc
EOF
部署客户端。
kubectl create namespace client
osm namespace add client
kubectl apply -n client -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: fortio-client
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio-client
spec:
replicas: 1
selector:
matchLabels:
app: fortio-client
template:
metadata:
labels:
app: fortio-client
spec:
serviceAccountName: fortio-client
containers:
- name: fortio-client
image: fortio/fortio:latest_release
imagePullPolicy: Always
EOF
测试
我们使用 10 个连接、200 并发来发送 1000 个请求,设置服务端 20%
的情况下返回状态码 511
。
fortio_client=`kubectl get pod -n client -l app=fortio-client -o jsonpath='{.items[0].metadata.name}'`
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 99.99 http://fortio.server.svc.cluster.local:8080/echo?status=511:20
结果类似下面这种,511
的响应在 20%
上下浮动。
Sockets used: 205 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 205
Code 200 : 804 (80.4 %)
Code 511 : 196 (19.6 %)
All done 1000 calls (plus 0 warmup) 2.845 ms avg, 199.8 qps
接下来我们测试不同熔断策略下系统的表现。
策略:错误请求数触发熔断
设置错误请求数触发阈值 errorAmountThreshold=100
,错误请求数达到 100
的时候触发熔断,返回 503 Service Unavailable!
,熔断时长 10s
。
kubectl apply -f - <<EOF
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
name: http-circuit-breaking
namespace: server
spec:
host: fortio.server.svc.cluster.local
connectionSettings:
http:
circuitBreaking:
statTimeWindow: 1m
minRequestAmount: 200
errorAmountThreshold: 100
degradedTimeWindow: 10s
degradedStatusCode: 503
degradedResponseContent: 'Service Unavailable!'
EOF
设置服务端 20%
的响应返回错误码 511
。当错误请求数达到 100
时,成功的请求应该 400
左右。
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 99.99 http://fortio.server.svc.cluster.local:8080/echo\?status\=511:20
从结果来看符合预期,熔断后的请求返回了 503
错误码。
Sockets used: 570 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 570
Code 200 : 430 (43.0 %)
Code 503 : 470 (47.0 %)
Code 511 : 100 (10.0 %)
All done 1000 calls (plus 0 warmup) 3.376 ms avg, 199.8 qps
检查 sidecar 日志,在第 530 个请求的时候错误数达到了 100,触发了熔断。
2023-02-08 01:08:01.456 [INF] [circuit_breaker] total/slowAmount/errorAmount (open) server/fortio|8080 530 0 100
策略:错误率触发熔断
这里我们将错误数触发改为错误率触发:errorRatioThreshold=0.10
,当错误率达到 10%
触发熔断,熔断 10s
并返回 503 Service Unavailable!
。注意,这里的最小请求数仍为 200
。
kubectl apply -f - <<EOF
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
name: http-circuit-breaking
namespace: server
spec:
host: fortio.server.svc.cluster.local
connectionSettings:
http:
circuitBreaking:
statTimeWindow: 1m
minRequestAmount: 200
errorRatioThreshold: 0.10
degradedTimeWindow: 10s
degradedStatusCode: 503
degradedResponseContent: 'Service Unavailable!'
EOF
设置服务端 20%
的响应返回错误码 511
。
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 99.99 http://fortio.server.svc.cluster.local:8080/echo\?status\=511:20
从输出的结果可以看出 200 个请求之后触发了熔断,熔断的请求数为 800。
Sockets used: 836 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 836
Code 200 : 164 (16.4 %)
Code 503 : 800 (80.0 %)
Code 511 : 36 (3.6 %)
All done 1000 calls (plus 0 warmup) 3.605 ms avg, 199.8 qps
查看 sidecar 的日志,在满足触发熔断的最小请求数 200 后,检查错误率也达到了阈值,触发了熔断。
2023-02-08 01:19:25.874 [INF] [circuit_breaker] total/slowAmount/errorAmount (close) server/fortio|8080 200 0 36
策略:慢调用请求数触发熔断
测试慢调用,为 20% 的请求加上 200ms 的延迟。
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 50,78,79,80,81,82,90,95 http://fortio.server.svc.cluster.local:8080/echo\?delay\=200ms:20
得类似下面的效果,接近 80% 的请求耗时小于 200ms。
# target 50% 0.000999031
# target 78% 0.0095
# target 79% 0.200175
# target 80% 0.200467
# target 81% 0.200759
# target 82% 0.20105
# target 90% 0.203385
# target 95% 0.204844
285103 max 0.000285103 sum 0.000285103
Sockets used: 10 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 10
Code 200 : 1000 (100.0 %)
All done 1000 calls (plus 0 warmup) 44.405 ms avg, 149.6 qps
设置策略:
- 设置慢调用请求耗时阈值
200ms
- 设置慢调用请求数为
100
kubectl apply -f - <<EOF
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
name: http-circuit-breaking
namespace: server
spec:
host: fortio.server.svc.cluster.local
connectionSettings:
http:
circuitBreaking:
statTimeWindow: 1m
minRequestAmount: 200
slowTimeThreshold: 200ms
slowAmountThreshold: 100
degradedTimeWindow: 10s
degradedStatusCode: 503
degradedResponseContent: 'Service Unavailable!'
EOF
为 20% 的请求注入 200ms 的延迟。
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 50,78,79,80,81,82,90,95 http://fortio.server.svc.cluster.local:8080/echo\?delay\=200ms:20
成功请求数为 504,其中的 20% 耗时 200ms,慢调用的请求数量达到触发熔断的阈值。
# target 50% 0.00246111
# target 78% 0.00393846
# target 79% 0.00398974
# target 80% 0.00409756
# target 81% 0.00421951
# target 82% 0.00434146
# target 90% 0.202764
# target 95% 0.220036
Sockets used: 496 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 496
Code 200 : 504 (50.4 %)
Code 503 : 496 (49.6 %)
All done 1000 calls (plus 0 warmup) 24.086 ms avg, 199.8 qps
查看 sidecar 的日志,在第 504 个请求时慢调用的数量达到了 100,触发熔断。
2023-02-08 07:27:01.106 [INF] [circuit_breaker] total/slowAmount/errorAmount (open) server/fortio|8080 504 100 0
策略:慢调用比率触发熔断
将慢调用比率设置为 0.10
,即统计窗口内期望 10% 的请求耗时超过 200ms 时触发熔断。
kubectl apply -f - <<EOF
apiVersion: policy.openservicemesh.io/v1alpha1
kind: UpstreamTrafficSetting
metadata:
name: http-circuit-breaking
namespace: server
spec:
host: fortio.server.svc.cluster.local
connectionSettings:
http:
circuitBreaking:
statTimeWindow: 1m
minRequestAmount: 200
slowTimeThreshold: 200ms
slowRatioThreshold: 0.1
degradedTimeWindow: 10s
degradedStatusCode: 503
degradedResponseContent: 'Service Unavailable!'
EOF
为 20% 的请求注入 200ms 的延迟。
kubectl exec "$fortio_client" -n client -c fortio-client -- fortio load -quiet -c 10 -n 1000 -qps 200 -p 50,78,79,80,81,82,90,95 http://fortio.server.svc.cluster.local:8080/echo\?delay\=200ms:20
查看输出的结果,成功的请求数 202,满足配置生效的最小请求数,其中 20% 为慢调用,达到触发熔断的阈值。
# target 50% 0.00305539
# target 78% 0.00387172
# target 79% 0.00390087
# target 80% 0.00393003
# target 81% 0.00395918
# target 82% 0.00398834
# target 90% 0.00458915
# target 95% 0.00497674
Sockets used: 798 (for perfect keepalive, would be 10)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.43.43.151:8080: 798
Code 200 : 202 (20.2 %)
Code 503 : 798 (79.8 %)
All done 1000 calls (plus 0 warmup) 10.133 ms avg, 199.8 qps
查看 sidecar 的日志,第 202 个请求的时候,慢调用的请求数为 28,触发了熔断。
2023-02-08 07:38:25.284 [INF] [circuit_breaker] total/slowAmount/errorAmount (open) server/fortio|8080 202 28 0