使用指数加权移动平均(EWMA)实现基于工作负载延迟的负载均衡

ASM在1.21版本提供了Peak EWMA(Exponentially Weighted Moving Average)负载均衡器,该负载均衡器通过计算静态权重、延迟、错误率等的移动平均值得出节点的分值进行负载均衡选择。当后端服务需要处理突然增加的流量时,ASM可以通过Peak EWMA算法关注实例的最高负载和实时响应时间,灵活地对流量进行分配,从而更好地处理流量峰值。本文介绍如何配置和使用指数加权移动平均(EWMA)实现基于工作负载延迟的负载均衡。

背景信息

服务网格ASM提供了多种常见的负载均衡算法,包括轮询、最少请求数、随机等。这些算法可以满足大多数业务场景的需求,并保证一定的性能表现。但是这些算法只能根据静态规则来进行后端选择,不会考虑后端实例的实时状态和性能。

例如,某网格使用默认的负载均衡算法,其后端服务的一个Pod由于所在的宿主机资源被其他应用占用而响应迟缓,导致请求延时增加甚至超时,而其他几个Pod则相对空闲。在这种情况下,如果负载均衡器能智能地避开这个性能异常的Pod,将流量更多地路由至其他相对空闲的Pod,这样可以显著地降低应用整体的错误率和响应延迟。

前提条件

使用方法

  1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

  2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择流量管理中心 > 目标规则,然后单击使用YAML创建

  3. 填入以下示例内容,单击创建。此示例YAML为命名空间default下的服务simple-server指定了PEAK_EWMA负载均衡算法。

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-server
      namespace: default
    spec:
      host: simple-server.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: PEAK_EWMA # 指定使用ASM PEAK_EWMA负载均衡器

使用示例

示例介绍

本示例使用simple-server应用作为服务端,sleep应用作为发起测试流量的客户端,基于simple-server应用的服务simple-server.default.svc.cluster.local服务作为服务端。该服务下包含两个不同配置的deployment:

  • simple-server-normal:该Deployment配置返回延迟范围50~100ms。

  • simple-server-hight-latency:该Deployment配置返回延迟范围为500~2000ms,使用该deployment来模拟一个服务下部分工作负载延迟增高的情况。

image

步骤一:为ASM实例启用监控指标

为了更加直观地体现Peak EWMA负载均衡器的效果,本示例通过启用ASM监控指标来观察启用Peak EWMA负载均衡前后服务整体响应时间变化。关于启用监控指标以及采集监控指标到ARMS,请参见将监控指标采集到可观测监控Prometheus版

步骤二:环境部署

  1. 在ACK集群对应的KubeConfig环境下,使用以下YAML创建sleep.yaml。

    展开查看YAML内容

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: sleep
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: sleep
      labels:
        app: sleep
        service: sleep
    spec:
      ports:
      - port: 80
        name: http
      selector:
        app: sleep
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
      template:
        metadata:
          labels:
            app: sleep
        spec:
          terminationGracePeriodSeconds: 0
          serviceAccountName: sleep
          containers:
          - name: sleep
            image: curlimages/curl
            command: ["/bin/sleep", "infinity"]
            imagePullPolicy: IfNotPresent
            volumeMounts:
            - mountPath: /etc/sleep/tls
              name: secret-volume
          volumes:
          - name: secret-volume
            secret:
              secretName: sleep-secret
              optional: true
    ---

    执行以下命令,部署sleep应用。

    kubectl apply -f sleep.yaml
  2. 使用以下内容创建simple.yaml。

    展开查看YAML内容

    apiVersion: v1
    kind: Service
    metadata:
      name: simple-server
      labels:
        app: simple-server
        service: simple-server
    spec:
      ports:
      - port: 8080
        name: http
      selector:
        app: simple-server
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-server
      name: simple-server-normal
      namespace: default
    spec:
      progressDeadlineSeconds: 600
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          app: simple-server
      strategy:
        rollingUpdate:
          maxSurge: 25%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          creationTimestamp: null
          labels:
            app: simple-server
        spec:
          containers:
          - args:
            - --delayMin
            - "50"
            - --delayMax
            - "100"
            image: registry-cn-hangzhou.ack.aliyuncs.com/test-public/simple-server:v1.0.0.0-g88293ca-aliyun
            imagePullPolicy: IfNotPresent
            name: simple-server
            ports:
            - containerPort: 80
              protocol: TCP
            resources:
              limits:
                cpu: 500m
            terminationMessagePath: /dev/termination-log
            terminationMessagePolicy: File
          dnsPolicy: ClusterFirst
          restartPolicy: Always
          schedulerName: default-scheduler
          securityContext: {}
          terminationGracePeriodSeconds: 30
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-server
      name: simple-server-high-latency
      namespace: default
    spec:
      progressDeadlineSeconds: 600
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          app: simple-server
      strategy:
        rollingUpdate:
          maxSurge: 25%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          creationTimestamp: null
          labels:
            app: simple-server
        spec:
          containers:
          - args:
            - --delayMin
            - "500"
            - --delayMax
            - "2000"
            image: registry-cn-hangzhou.ack.aliyuncs.com/test-public/simple-server:v1.0.0.0-g88293ca-aliyun
            imagePullPolicy: IfNotPresent
            name: simple-server
            ports:
            - containerPort: 80
              protocol: TCP
            resources:
              limits:
                cpu: 500m
            terminationMessagePath: /dev/termination-log
            terminationMessagePolicy: File
          dnsPolicy: ClusterFirst
          restartPolicy: Always
          schedulerName: default-scheduler
          securityContext: {}
          terminationGracePeriodSeconds: 30
    ---

    执行以下命令部署simple-server-normal和simple-server-high-latency应用。

    kubectl apply -f simple.yaml

步骤三:以默认负载均衡算法发起测试

本节是基于默认负载均衡算法LEAST_REQUEST产生基准数据。

  1. 执行以下命令发起测试,该命令会向simple-server服务发起100次/hello调用:

    kubectl exec -it deploy/sleep -c sleep --  sh -c 'for i in $(seq 1 100); do time curl simple-server:8080/hello; echo "request $i done"; done'

    预期输出如下:

    hello
     this is port: 8080real 0m 0.06s
    user    0m 0.00s
    sys     0m 0.00s
    request 1 done
    hello
     this is port: 8080real 0m 0.09s
    user    0m 0.00s
    sys     0m 0.00s
    request 2 done
    
    ......
    
    hello
     this is port: 8080real 0m 1.72s
    user    0m 0.00s
    sys     0m 0.00s
    request 100 done
  2. 命令执行完毕后,在网格管理页面,单击目标实例名称,然后在左侧导航栏,选择可观测管理中心 > 监控指标,选择网格服务级别监控选项卡,输入以下筛选条件:

    • Namespace: default

    • Service: simple-server.default.svc.cluster.local

    • Reporter: destination

    • Client Workload Namespace: default

    • Client Workload: sleep

    • Service Workload Namespace: default

    • Service Workload: simple-server-normal + simple-server-high-latency

  3. 单击Client Workloads下拉菜单,查看Incoming Request Duration By Source面板。

    image

    可以看到,sleep应用到simple-server服务的请求响应时间P50为87.5ms,P95则上升较为显著,达到了2.05s,这是因为simple-server-high-latency的延迟较高,增加了整个服务的响应时间。

    重要

    上述步骤中提到的测试结果是在受控实验环境下得出的理论数值,实际结果可能会因您的业务环境不同而有所差异。

步骤四:配置Peak EWMA负载均衡算法再次测试

创建DestinationRule为simple-server服务应用配置Peak EWMA负载均衡算法。

  1. 参考使用方法中的步骤和以下YAML内容创建目标规则。

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-server
      namespace: default
    spec:
      host: simple-server.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: PEAK_EWMA
  2. 在ACK集群对应的KubeConfig环境下,执行以下命令再次发起测试。

    kubectl exec -it deploy/sleep -c sleep --  sh -c 'for i in $(seq 1 100); do time curl simple-server:8080/hello; echo "request $i done"; done'
  3. 对照步骤三中查看监控指标的步骤及结果,可以看到P90、P95、P99响应时间明显下降。这是因为Peak EWMA负载均衡器发现了simple-server-high-latency工作负载的延迟较高,降低了它的负载均衡权重,从而将更多的请求分配至延迟更低的simple-server-normal,从服务整体的视角观察,请求的整体延迟显著降低了。

    image