ASMAdaptiveConcurrency能夠根據採樣的請求資料動態調整允許的並發的數量,當並發數量超過服務可承受範圍時,會拒絕請求,達到保護服務的目的。本文介紹如何使用ASMAdaptiveConcurrency實現自適應並發控制。
前提條件
已建立ASM執行個體,且執行個體為1.12.4.19及以上版本。具體操作,請參見建立ASM執行個體。
已添加叢集到ASM執行個體。具體操作,請參見添加叢集到ASM執行個體。
已通過kubectl工具串連叢集。具體操作,請參見通過kubectl工具串連叢集。
背景資訊
通常情況下,希望服務在超出負載能力時能拒絕超額的請求,從而防止產生其他連鎖反應。您可以使用Service Mesh的DestinationRule配置基礎的熔斷能力,但這要求必須給出一個觸發熔斷的閾值,例如具體的pending requests數量,使得Service Mesh資料平面在網路訪問超出熔斷配置時能夠拒絕請求。在實際應用情境下,準確地估算服務的負載能力是較為困難的。
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,查看並發控制器的運行狀態。具體操作,請參見ARMS相關文檔。
展開查看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的自適應並發控製成功。