ASMAdaptiveConcurrency能够根据采样的请求数据动态调整允许的并发的数量,当并发数量超过服务可承受范围时,会拒绝请求,达到保护服务的目的。本文介绍如何使用ASMAdaptiveConcurrency实现自适应并发控制。
前提条件
已创建ASM实例,且实例为1.12.4.19及以上版本。具体操作,请参见创建ASM实例。
已添加集群到ASM实例。具体操作,请参见添加集群到ASM实例。
背景信息
通常情况下,希望服务在超出负载能力时能拒绝超额的请求,从而防止产生其他连锁反应。您可以使用服务网格的DestinationRule配置基础的熔断能力,但这要求必须给出一个触发熔断的阈值,例如具体的pending requests数量,使得服务网格数据平面在网络访问超出熔断配置时能够拒绝请求。在实际应用场景下,准确地估算服务的负载能力是较为困难的。
ASMAdaptiveConcurrency采用自适应并发控制算法通过定期对比采样Latency和测定的理想Latency之间的差异及一系列计算对并发限制进行动态调整,从而尽可能使得并发限制数量在服务可承受的范围附近,同时拒绝超出该限制的请求(拒绝请求时会返回HTTP 503
及错误信息reached concurrency limit
)。
由于周期性的MinRTT统计期间会将连接数限制在较小值(min_concurrency),建议您在启用了AdaptiveConcurrency服务后,同时使用DestinationRule为服务启用重试功能,使得在minRTT计算期间被拒绝的请求能够通过Sidecar的重试功能尽可能地成功返回结果。
步骤一:部署示例应用
部署testserver和gotest应用,设置testserver应用的负载能力为并发处理500个请求,超过并发数量的请求会被排队处理。每个请求的处理时间是1000ms。设置gotest应用的一个副本一次能发起200个请求。
部署testserver应用。
使用以下内容,创建testserver.yaml文件。
展开查看详细内容
apiVersion: apps/v1 kind: Deployment metadata: labels: app: testserver name: testserver namespace: default spec: replicas: 1 selector: matchLabels: app: testserver template: metadata: creationTimestamp: null labels: app: testserver spec: containers: - args: - -m - "500" - -t - "1000" command: - /usr/local/bin/limited-concurrency-http-server image: registry.cn-hangzhou.aliyuncs.com/acs/asm-limited-concurrency-http-server:v0.1.1-gee0b08f-aliyun imagePullPolicy: IfNotPresent name: testserver ports: - containerPort: 8080 protocol: TCP
您可以使用
-m
指定应用能够承受的并发数量,-t
指定请求处理时间。执行以下命令,部署testserver应用。
kubectl apply -f testserver.yaml
部署testserver的Service。
使用以下内容,创建testservice.yaml文件。
展开查看详细内容
apiVersion: v1 kind: Service metadata: labels: app: testserver name: testserver namespace: default spec: internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - name: http port: 8080 protocol: TCP targetPort: 8080 - name: metrics port: 15020 protocol: TCP targetPort: 15020 selector: app: testserver type: ClusterIP
执行以下命令,部署testserver的Service。
kubectl apply -f testservice.yaml
部署gotest应用。
使用以下内容,创建gotest.yaml文件。
展开查看详细内容
apiVersion: apps/v1 kind: Deployment metadata: labels: app: gotest name: gotest namespace: default spec: replicas: 0 selector: matchLabels: app: gotest template: metadata: creationTimestamp: null labels: app: gotest spec: containers: - args: - -c - "200" - -n - "10000" - -u - testserver:8080 command: - /root/go-stress-testing-linux image: xocoder/go-stress-testing-linux:v0.1 imagePullPolicy: Always name: gotest resources: limits: cpu: 500m
执行以下命令,部署gotest应用。
kubectl apply -f gotest.yaml
步骤二:创建ASMAdaptiveConcurrency
使用以下内容,创建adaptiveconcurrency.yaml。
展开查看详细内容
apiVersion: istio.alibabacloud.com/v1beta1 kind: ASMAdaptiveConcurrency metadata: name: sample-adaptive-concurrency namespace: default spec: workload_selector: labels: app: testserver sample_aggregate_percentile: value: 60 concurrency_limit_params: max_concurrency_limit: 500 concurrency_update_interval: 15s min_rtt_calc_params: interval: 60s request_count: 100 jitter: value: 15 min_concurrency: 50 buffer: value: 25
参数
类型
描述
是否必填
workload_selector
WorkloadSelector
工作负载选择器,声明如何匹配生效的工作负载。
是
labels
map
列出需要匹配的工作负载(Pod)的Labels,用于匹配工作负载。
是
sample_aggregate_percentile
Percent
采样百分位,将采样请求的该百分位值作为SampleRTT参与计算。
是
value
int
百分比数,有效值为0~100。
是
concurrency_limit_params
ConcurrencyLimitParams
并发限制相关配置。
是
max_concurrency_limit
int
最大并发限制数。默认值1000。
否
concurrency_update_interval
duration
最大并发计算间隔,例如60s。
是
min_rtt_calc_params
MinRTTCalcParams
计算MinRTT的相关配置。
是
interval
duration
理想round-trip time计算间隔,例如120s。
否
request_count
int
统计多少请求的数据用作计算理想round-trip time。默认值为50。
否
jitter
Percent
计算理想round-trip time时,增加随机延迟的百分比,例如,interval为120s时,jitter为50,则间隔时间为ramdom(120, 120 + (120 * 50%))。默认值为15。
否
min_concurrency
int
计算理想round-trip time时的并发数量,同时也是控制器初始的并发数值。该值的取值应当远低于服务的瓶颈,从而使得服务在该并发下可以以最理想的延迟返回。默认值为3。
否
buffer
Percent
合理延迟波动范围(百分比),例如延迟在100ms上下时,10%浮动属于合理范围,则这个值设置为10。默认值为25。
否
执行以下命令,创建ASMAdaptiveConcurrency。
kubectl apply -f adaptiveconcurrency.yaml
步骤三:启用可观测监控Prometheus版
为了对并发控制器的运行状态有直观的理解,以及便于对参数调优,您可以将自适应并发控制的相关Metrics输出到可观测监控Prometheus版,使用可观测监控Prometheus版查看并发控制器的运行状态。
启用可观测监控Prometheus版。具体操作,请参见阿里云Prometheus监控。
在集群中配置ServiceMonitor,使可观测监控Prometheus版可以获取到testserver数据。
使用以下内容,创建servicemonitor.yaml。
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: testserver-envoy-metrics namespace: default spec: endpoints: - interval: 5s path: /stats/prometheus port: metrics namespaceSelector: any: true selector: matchLabels: app: testserver
在集群对应的KubeConfig环境下,执行以下命令,创建ServiceMonitor。
kubectl apply -f servicemonitor.yaml
步骤四:验证ASMAdaptiveConcurrency的自适应并发控制是否成功
设置gotest的副本数为5。
一个gotest应用副本将发起200个请求,5个副本则将发起1000个请求。
登录容器服务管理控制台。
在控制台左侧导航栏,单击集群。
在集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情。
在集群管理页左侧导航栏,选择工作负载 > 无状态。
在无状态页面设置命名空间为default,在gotest应用右侧操作列下选择更多 > 查看Yaml。
在编辑 YAML对话框中,设置
replicas
为5
,单击更新。
使用如下JSON定义,为Grafana导入Dashboard,查看并发控制器的运行状态。具体操作,请参见导入仪表盘。
展开查看JSON定义
{ "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "description": "monitoring ASM Adaptive Concurrency", "editable": true, "gnetId": 6693, "graphTooltip": 0, "id": 3239002, "iteration": 1651922323976, "links": [], "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }, "hiddenSeries": false, "id": 22, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_rq_blocked{service=\"$service\", pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "RqBlocked", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 }, "hiddenSeries": false, "id": 24, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_burst_queue_size{service=\"$service\", pod=\"$pod\"}", "format": "time_series", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "HeadRoom", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 }, "hiddenSeries": false, "id": 26, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_concurrency_limit{service=\"$service\",pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "ConcurrencyLimit", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 }, "hiddenSeries": false, "id": 28, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_gradient{service=\"$service\",pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Gradient", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }, "hiddenSeries": false, "id": 32, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_min_rtt_msecs{service=\"$service\",pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "MinRTT(msec)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$cluster", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }, "hiddenSeries": false, "id": 34, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_sample_rtt_msecs{service=\"$service\",pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "SampleRTT(msec)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "test-adaptive-concurrency_1217520382582089", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 24 }, "hiddenSeries": false, "id": 30, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.0-pre", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_min_rtt_calculation_active{service=\"$service\",pod=\"$pod\"}", "interval": "", "legendFormat": "{{service}}-{{pod}}", "queryType": "randomWalk", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "MinRTTCalc", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "refresh": "5s", "schemaVersion": 26, "style": "dark", "tags": [], "templating": { "list": [ { "current": { "selected": true, "text": "edas120_1217520382582089", "value": "edas120_1217520382582089" }, "error": null, "hide": 0, "includeAll": false, "label": null, "multi": false, "name": "cluster", "options": [], "query": "prometheus", "queryValue": "", "refresh": 1, "regex": "", "skipUrlSync": false, "type": "datasource" }, { "allValue": null, "current": { "isNone": true, "selected": false, "text": "None", "value": "" }, "datasource": "$cluster", "definition": "label_values(envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_burst_queue_size,service)", "error": null, "hide": 0, "includeAll": false, "label": null, "multi": false, "name": "service", "options": [], "query": "label_values(envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_burst_queue_size,service)", "refresh": 2, "regex": "", "skipUrlSync": false, "sort": 1, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allValue": null, "current": { "selected": false, "text": "All", "value": "$__all" }, "datasource": "$cluster", "definition": "label_values(envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_concurrency_limit, pod)", "error": null, "hide": 0, "includeAll": true, "label": null, "multi": true, "name": "pod", "options": [], "query": "label_values(envoy_http_inbound_0_0_0_0_8080_adaptive_concurrency_gradient_controller_concurrency_limit, pod)", "refresh": 2, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false } ] }, "time": { "from": "now-15m", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "", "title": "ASM Adaptive Concurrency", "uid": "000000084", "version": 3 }
在Dashboard中选择集群,设置service为testserver,pod为ALL。
gotest应用向testserver应用发起了1000个请求,但是testserver应用始终限制收到的并发数在500以内,表明使用ASMAdaptiveConcurrency的自适应并发控制成功。