全部產品
Search
文件中心

Container Service for Kubernetes:多NUMA機型的容器記憶體就近訪問加速

更新時間:Jun 19, 2024

ack-koordinator以資料安全的方式將綁核應用遠端NUMA上的記憶體遷移至本地,提高本地訪存命中率,為記憶體密集型的工作負載提供更好的訪存效能。本文介紹如何使用記憶體就近訪問加速功能,並驗證其對密集型應用效能的提升。

索引

前提條件

  • 已建立ACK Pro叢集,且叢集版本為1.18及以上版本。具體操作,請參見建立Kubernetes託管版叢集

  • 已通過kubectl串連Kubernetes叢集。具體操作,請參見擷取叢集KubeConfig並通過kubectl工具串連叢集

  • 已安裝ack-koordinator組件(原ack-slo-manager),且組件版本為v1.2.0-ack1.2及以上版本。關於安裝ack-koordinator組件的具體操作,請參見ack-koordinator(ack-slo-manager)

    說明

    ack-koordinator適配了原resource-controller組件的所有功能。如果您正在使用resource-controller,請您先卸載resource-controller,再安裝ack-koordinator。關於卸載組件的具體操作,請參見卸載resource-controller

  • 已確保多NUMA機型為神龍裸金屬ecs.ebmc、ecs.ebmg、ecs.ebmgn、ecs.ebmr、ecs.ebmhfc、ecs.scc等五、 六、七、八代機型。

    說明

    記憶體就近訪問加速功能尤其對ecs.ebmc8i.48xlarge、ecs.c8i.32xlarge、ecs.g8i.48xlarge的八代機型提供更好的支援。關於ECS執行個體規格類型系列,請參見ECS執行個體規格

  • 已確保應用運行在多NUMA機型上,且已通過CPU拓撲感知調度等功能實現綁核。更多資訊,請參見CPU拓撲感知調度

費用說明

ack-koordinator組件本身的安裝和使用是免費的,不過需要注意的是,在以下情境中可能產生額外的費用:

  • ack-koordinator是非託管組件,安裝後將佔用Worker節點資源。您可以在安裝組件時配置各模組的資源申請量。

  • ack-koordinator預設會將資源畫像、精細化調度等功能的監控指標以Prometheus的格式對外透出。若您配置組件時開啟了ACK-Koordinator開啟Prometheus監控指標選項並使用了阿里雲Prometheus服務,這些指標將被視為自訂指標併產生相應費用。具體費用取決於您的叢集規模和應用數量等因素。建議您在啟用此功能前,仔細閱讀阿里雲Prometheus計費說明,瞭解自訂指標的免費額度和收費策略。您可以通過賬單和用量查詢,監控和管理您的資源使用方式。

記憶體就近訪問加速功能的優勢

多個非統一記憶體存取NUMA(Non-uniform memory access)架構下,當記憶體與CPU不在同一個NUMA時, 進程在跨NUMA讀取遠端記憶體時需要經過QPI匯流排,相對於記憶體與CPU在相同NUMA的本地記憶體訪問情境,跨NUMA情境訪存耗時更多。遠端記憶體的分布會降低應用的本地訪存命中率,影響記憶體密集型容器應用訪存效能。

以記憶體密集型應用Redis為例,Redis對所有CPU親和時,進程在運行過程中可能發生NUMA的切換。此時,以多NUMA機型ecs.ebmc8i.48xlarge為例,部分記憶體分布在遠端NUMA上,記憶體分布如下方左圖所示。redis-server的記憶體全本地分布相對於記憶體隨機分布和全遠端分布的情境,在壓測中,QPS效能分別提升10.52%和26.12%,如下方右圖所示。

重要

本文中提供的測試資料僅為理論值(參考值),實際資料以您的作業環境為準。

QPS

為提高訪存效能,ack-koordinator提供容器記憶體就近訪問加速功能。對基於CPU拓撲感知調度等功能實現綁核的應用,在確保資料安全的前提下儘可能地將遠端記憶體遷移至所在NUMA,且遷移過程中無需中斷業務。

使用情境

記憶體就近訪問加速功能的使用情境如下。

  • 工作負載為記憶體密集型,例如大型記憶體資料庫Redis。

  • 運行在配置英特爾®DSA(Data Streaming Accelerator)資料流加速器硬體的機器上,DSA可以提升記憶體就近訪問加速功能的速度,並降低CPU消耗。

使用記憶體就近訪問加速功能

步驟一:通過Policy開啟記憶體就近訪問加速功能

您可以通過以下兩種方式開啟記憶體就近訪問加速功能。

為單個Pod開啟記憶體就近訪問加速功能

使用以下命令,為已綁核的Redis Pod開啟記憶體就近訪問加速功能。

kubectl annotate pod <pod-name> koordinator.sh/memoryLocality='{"policy": "bestEffort"}' --overwrite

policy參數取值說明:

  • bestEffort:立即執行單次記憶體就近訪問加速。

  • none:關閉記憶體就近訪問加速功能。

為指定QoS下所有Pod開啟記憶體就近訪問加速功能

  1. 使用以下內容,建立ml-config.yaml檔案。

    apiVersion: v1
    data:
      resource-qos-config: |-
        {
          "clusterStrategy": {
            "lsClass": {
               "memoryLocality": {
                 "policy": "bestEffort"
               }
             }
          }
        }
    kind: ConfigMap
    metadata:
      name: ack-slo-config
      namespace: kube-system
  2. 執行以下命令,檢查kube-system命名空間下,是否存在configmap/ack-slo-config

    為避免影響原有的QoS配置,需要進行ack-slo-config的檢查操作。

    kubectl get cm ack-slo-config -n kube-system
    • configmap/ack-slo-config不存在,則使用以下命令建立。

      kubectl apply -f ml-config.yaml
    • configmap/ack-slo-config已存在,則使用以下命令,配置記憶體就近訪問加速。

      kubectl patch cm -n kube-system ack-slo-config --patch "$(cat ml-config.yaml)"

步驟二:通過Event查看記憶體就近訪問加速結果

使用以下命令,查看記憶體就近訪問加速的結果。

kubectl describe pod <pod-name>
  • 預期輸出1:

      Normal  MemoryLocalityCompleted  6s    koordlet  Container <container-name-1> completed: migrated memory from remote numa: 1 to local numa: 0
    Container <container-name-2> completed: migrated memory from remote numa: 0 to local numa: 1
    Total: 2 container(s) completed, 0 failed, 0 skipped; cur local memory ratio 100, rest remote memory pages 0

    預期輸出1completed表明,記憶體就近訪問加速成功。Event記錄各個容器記憶體的遷移方向及本地化後Pod的總記憶體頁分布比例。

  • 預期輸出2:

      Normal  MemoryLocalityCompleted  6s    koordlet  Container <container-name-1> completed: migrated memory from remote numa: 1 to local numa: 0
    Container <container-name-2> completed: failed to migrate the following processes: failed pid: 111111, error: No such process,from remote numa: 0 to local numa: 1
    Total: 2 container(s) completed, 0 failed, 0 skipped; cur local memory ratio 100, rest remote memory pages 0

    預期輸出2completed表明,部分記憶體就近訪問加速失敗,例如遷移過程中,進程終止。Event記錄失敗進程號。

  • 預期輸出3:

      Normal  MemoryLocalitySkipped  1s    koordlet  no target numa

    預期輸出3表明,沒有遠端NUMA時(例如機型為非NUMA架構或進程分布在所有NUMA時),Event告知記憶體就近訪問加速已跳過。

  • 預期輸出4:

    其他非預期錯誤,Pod報出MemoryLocalityFailed的Event,並提示Message錯誤資訊。

(可選)步驟三:開啟多次記憶體就近訪問加速

說明
  • 若已進行過單次記憶體就近訪問加速,開啟多次記憶體訪問加速前,請確認前一次加速已完成。

  • 多次記憶體就近訪問加速時,只有當記憶體就近訪問加速的結果發生改變,或本地化後記憶體頁分布比例發生超出10%的改變時,加速的結果才會記錄在Event中。

執行以下命令,開啟多次記憶體就近訪問加速。

kubectl annotate pod <pod-name> koordinator.sh/memoryLocality='{"policy": "bestEffort","migrateIntervalMinutes":10}' --overwrite

migrateIntervalMinutes為兩次記憶體就近訪問加速的最小執行間隔,單位為分鐘。參數取值說明:

  • 0:立即執行單次記憶體就近訪問加速。

  • > 0:立即執行記憶體就近訪問加速。

  • none:關閉記憶體就近訪問功能。

驗證記憶體就近訪問加速功能對應用效能的提升

測試環境

  • 一台多NUMA架構的物理機或虛擬機器:用於部署記憶體密集型應用的測試機,本文測試選用ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge機型。

  • 一台能訪問測試機的壓測機:用於進行服務壓測。

  • 測試的應用:4 Core 32 GB的多線程Redis。

    說明

    若選用的測試機型規格較小,可改用單線程Redis並降低CPU和記憶體規格。

測試流程測試流程測試步驟

  1. 使用以下YAML內容,建立並部署Redis應用。

    以下代碼中redis-config欄位的內容,請根據測試機型規格修改配置。

    ---
    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: example-redis-config
    data:
      redis-config: |
        maxmemory 32G                  
        maxmemory-policy allkeys-lru
        io-threads-do-reads yes        
        io-threads 4                   
    ---
    kind: Service
    apiVersion: v1
    metadata:
      name: redis
    spec:
      type: NodePort
      ports:
        - port: 6379
          targetPort: 6379
          nodePort: 32379
      selector:
        app: redis
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: redis
      annotations:
        cpuset-scheduler: "true"        # 通過CPU拓撲感知調度實現綁核。
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6.0.5
        command: ["bash", "-c", "redis-server /redis-master/redis.conf"]
        ports:
        - containerPort: 6379
        resources:
          limits:
            cpu: "4"                  # 根據測試機型規格修改配置。
        volumeMounts:
        - mountPath: /redis-master-data
          name: data
        - mountPath: /redis-master
          name: config
      volumes:
        - name: data
          emptyDir: {}
        - name: config
          configMap:
            name: example-redis-config
            items:
            - key: redis-config
              path: redis.conf
  2. 類比記憶體增壓。

    1. 往Redis中寫入業務資料。

      參考以下Shell指令碼,通過管道方式向redis-server批量寫入資料。在以下指令碼中,<max-out-cir><max-out-cir>用來控制寫入的資料量。本文測試案例在4 Core 32 GB的Redis應用中寫入資料,本次參數設定為max-our-cir=300max-in-cir=1000000

      說明

      資料量較大時,資料寫入時間較長。為提高寫入速度,建議在測試機本地進行資料寫入。

      for((j=1;j<=<max-out-cir>;j++))
      do
              echo "set k$j-0 v$j-0" > data.txt
              for((i=1;i<=<max-in-cir>;i++))
              do
                      echo "set k$j-$i v$j-$i" >> data.txt
              done
              echo "$j"
              unix2dos data.txt  # pipe方式需要dos格式檔案。
              cat data.txt | redis-cli --pipe -h <redis-server-IP> -p <redis-server-port>
      done

      在寫入過程中,對Redis所在的NUMA部署高記憶體佔用的混部業務(例如FFmpeg)進行記憶體增壓,類比Redis在運行過程中,因記憶體壓力突發導致記憶體漂移到其他NUMA的現象。

      在測試機上,可使用numactl --membind命令,限制部署業務記憶體所在的NUMA。

    2. 使用以下stress命令進行增壓。

      其中<workers-num>用於分配記憶體的進程數,<malloc-size-per-workers>為每個進程分配的記憶體大小。

      說明

      由於Redis處於綁核狀態,在本地記憶體未達到100%時,記憶體會優先寫入到本地。請根據節點當前負載情況設定workers-nummalloc-size-per-workers參數。各NUMA記憶體負載情況可通過numactl -H命令查詢。

      numactl --membind=<numa-id> stress -m <workers-num> --vm-bytes <malloc-size-per-workers>
  3. 在記憶體就近訪問加速前後進行redis-benchmark壓測並對比。

    1. 記憶體就近訪問加速前壓測

      資料寫入完成後,在壓測機上使用redis-benchmark對測試機redis-server進行適中壓力及高壓力壓測。

      1. 執行以下適中壓力壓測命令,並發數為500。

        redis-benchmark -t GET -c 500 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
      2. 執行以下高壓力壓測命令,並發數為10000。

        redis-benchmark -t GET -c 10000 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
    2. 為Redis開啟記憶體加速訪問功能

      1. 執行以下命令,為Redis進行記憶體就近訪問加速。

        kubectl annotate pod redis koordinator.sh/memoryLocality='{"policy": "BestEffort"}' --overwrite
      2. 執行以下命令,查看記憶體就近訪問加速結果。

        若遠端記憶體量較大,記憶體就近訪問加速需要一定時間。

        kubectl describe pod redis

        記憶體就近訪問加速完成後,預期輸出:

          Normal  MemoryLocalitySuccess  0s   koordlet-resmanager  migrated memory from remote numa: 0 to local numa: 1, cur local memory ratio 98, rest remote memory pages 28586
    3. 記憶體就近訪問加速後壓測

      重複執行中壓力和高壓力壓測命令,進行記憶體就近訪問加速後壓測。

結果分析

本文測試分別採用ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge機型的測試機,對記憶體就近訪問加速前後進行多次壓測取平均值,資料對比如下。

重要

採用不同的工具進行測試,測試結果會存在差異。本文中的測試資料僅為ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge機型的測試機獲得的測試結果。

展開查看ecs.ebmc8i.48xlarge機型測試結果分析

  • 記憶體分布對比

    記憶體就近訪問加速前,Redis記憶體分布如下方左圖所示,本地記憶體頁佔比為43%。記憶體就近訪問加速後,Redis記憶體分布如下方右圖所示,本地記憶體頁佔比為98%。加速1

  • 壓測結果對比

    • 500並發情境

      測試情境

      P99 ms

      P99.99 ms

      QPS

      記憶體就近訪問加速前

      3

      9.6

      122139.91

      記憶體就近訪問加速後

      3

      8.2

      129367.11

    • 10000並發情境

      測試情境

      P99 ms

      P99.99 ms

      QPS

      記憶體就近訪問加速前

      115

      152.6

      119895.56

      記憶體就近訪問加速後

      101

      145.2

      125401.44

    以上壓測結果表明:

    • 適中並發(500並發):P99無變化(時延小,變動不明顯),P99.99提升14.58%,QPS提升5.917%。

    • 高並發(10000並發):P99提升12.17%,P99.99提升4.85%,QPS提升4.59%。

展開查看ecs.ebmg7a.64xlarge機型測試結果分析

  • 記憶體分布對比

    記憶體就近訪問加速前,Redis記憶體分布如下方左圖所示,本地記憶體頁佔比為47%。記憶體就近訪問加速後,Redis記憶體分布如下方右圖所示,本地記憶體頁佔比為88%。加速2對比

  • 壓測結果對比

    • 500並發情境

      測試情境

      P99 ms

      P99.99 ms

      QPS

      記憶體就近訪問加速前

      2.4

      4.4

      135180.99

      記憶體就近訪問加速後

      2.2

      4.4

      136296.37

    • 10000並發情境

      測試情境

      P99 ms

      P99.99 ms

      QPS

      記憶體就近訪問加速前

      58.2

      80.4

      95757.10

      記憶體就近訪問加速後

      56.6

      76.8

      97015.50

    以上壓測結果表明:

    • 適中並發(500並發):P99提升8.33%,P99.99無變化(時延小,變動不明顯),QPS提升0.83%。

    • 高並發(10000並發):P99提升2.7%,P99.99提升4.4%,QPS提升1.3%。

結果說明:在一定記憶體壓力下,應用的資料寫入到遠端記憶體,本地記憶體佔比影響了訪存效能。在綁核基礎上進行記憶體就近訪問加速後,在兩種壓測情境下的Redis時延和吞吐指標都得到一定的改善。