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組件本身的安裝和使用是免費的,不過需要注意的是,在以下情境中可能產生額外的費用:
記憶體就近訪問加速功能的優勢
多個非統一記憶體存取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%,如下方右圖所示。
本文中提供的測試資料僅為理論值(參考值),實際資料以您的作業環境為準。
為提高訪存效能,ack-koordinator提供容器記憶體就近訪問加速功能。對基於CPU拓撲感知調度等功能實現綁核的應用,在確保資料安全的前提下儘可能地將遠端記憶體遷移至所在NUMA,且遷移過程中無需中斷業務。
使用情境
記憶體就近訪問加速功能的使用情境如下。
工作負載為記憶體密集型,例如大型記憶體資料庫Redis。
運行在配置英特爾®DSA(Data Streaming Accelerator)資料流加速器硬體的機器上,DSA可以提升記憶體就近訪問加速功能的速度,並降低CPU消耗。
使用記憶體就近訪問加速功能
步驟一:通過Policy開啟記憶體就近訪問加速功能
您可以通過以下兩種方式開啟記憶體就近訪問加速功能。
通過配置
metadata.annotations/koordinator.sh/memoryLocality
,為單個Pod開啟記憶體就近訪問加速功能。具體操作,請參見下文為單個Pod開啟記憶體就近訪問加速。通過配置
configmap/ack-slo-config
的指定QoS的memoryLocality
,為指定QoS下所有Pod開啟記憶體就近訪問加速功能。具體操作,請參見下文為指定QoS下所有Pod開啟記憶體就近訪問加速。
為單個Pod開啟記憶體就近訪問加速功能
使用以下命令,為已綁核的Redis Pod開啟記憶體就近訪問加速功能。
kubectl annotate pod <pod-name> koordinator.sh/memoryLocality='{"policy": "bestEffort"}' --overwrite
policy
參數取值說明:
bestEffort
:立即執行單次記憶體就近訪問加速。none
:關閉記憶體就近訪問加速功能。
為指定QoS下所有Pod開啟記憶體就近訪問加速功能
使用以下內容,建立ml-config.yaml檔案。
apiVersion: v1 data: resource-qos-config: |- { "clusterStrategy": { "lsClass": { "memoryLocality": { "policy": "bestEffort" } } } } kind: ConfigMap metadata: name: ack-slo-config namespace: kube-system
執行以下命令,檢查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
預期輸出1
completed
表明,記憶體就近訪問加速成功。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
預期輸出2
completed
表明,部分記憶體就近訪問加速失敗,例如遷移過程中,進程終止。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和記憶體規格。
測試流程測試步驟
使用以下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
類比記憶體增壓。
往Redis中寫入業務資料。
參考以下Shell指令碼,通過管道方式向redis-server批量寫入資料。在以下指令碼中,
<max-out-cir>
和<max-out-cir>
用來控制寫入的資料量。本文測試案例在4 Core 32 GB的Redis應用中寫入資料,本次參數設定為max-our-cir=300
、max-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。使用以下stress命令進行增壓。
其中
<workers-num>
用於分配記憶體的進程數,<malloc-size-per-workers>
為每個進程分配的記憶體大小。說明由於Redis處於綁核狀態,在本地記憶體未達到100%時,記憶體會優先寫入到本地。請根據節點當前負載情況設定
workers-num
及malloc-size-per-workers
參數。各NUMA記憶體負載情況可通過numactl -H
命令查詢。numactl --membind=<numa-id> stress -m <workers-num> --vm-bytes <malloc-size-per-workers>
在記憶體就近訪問加速前後進行redis-benchmark壓測並對比。
記憶體就近訪問加速前壓測
資料寫入完成後,在壓測機上使用redis-benchmark對測試機redis-server進行適中壓力及高壓力壓測。
執行以下適中壓力壓測命令,並發數為500。
redis-benchmark -t GET -c 500 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
執行以下高壓力壓測命令,並發數為10000。
redis-benchmark -t GET -c 10000 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
為Redis開啟記憶體加速訪問功能
執行以下命令,為Redis進行記憶體就近訪問加速。
kubectl annotate pod redis koordinator.sh/memoryLocality='{"policy": "BestEffort"}' --overwrite
執行以下命令,查看記憶體就近訪問加速結果。
若遠端記憶體量較大,記憶體就近訪問加速需要一定時間。
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
記憶體就近訪問加速後壓測
重複執行中壓力和高壓力壓測命令,進行記憶體就近訪問加速後壓測。
結果分析
本文測試分別採用ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge機型的測試機,對記憶體就近訪問加速前後進行多次壓測取平均值,資料對比如下。
採用不同的工具進行測試,測試結果會存在差異。本文中的測試資料僅為ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge機型的測試機獲得的測試結果。
結果說明:在一定記憶體壓力下,應用的資料寫入到遠端記憶體,本地記憶體佔比影響了訪存效能。在綁核基礎上進行記憶體就近訪問加速後,在兩種壓測情境下的Redis時延和吞吐指標都得到一定的改善。