最近太忙了,筆者實在懶得畫圖,文章的圖片大多來源於互聯網,感謝創做者們(惋惜找不到源出處)。node
這篇博文與其說是介紹 OpenStack Nova 的高性能虛擬機,倒不如說是介紹 CPU 相關的硬件架構與應用程序之間的愛恨情仇更加貼切一些。python
SMP(Sysmmetric Multi-Processor,對稱多處理器),顧名思義,SMP 由多個具備對稱關係的處理器組成。所謂對稱,即處理器之間是水平的鏡像關係,無有主從之分。SMP 的出現使一臺計算機再也不由單個 CPU 組成。web
SMP 的典型特徵爲**「多個處理器共享一個集中式存儲器」**,且每一個處理器訪問存儲器的時間片相同,使得工做負載可以均勻的分配到全部可用處理器上,極大地提升了整個系統的數據處理能力。算法
雖然 SMP 具備多個處理器,但因爲只有一個共享的集中式存儲器,因此 SMP 只能運行一個操做系統和數據庫系統的副本(實例),依舊保持了單機特性。同時,SMP 也會要求多處理器保證共享存儲器的數據一致性。若是多個處理器同時請求訪問共享資源,就須要由軟件或硬件實現的加鎖機制來解決資源競態的問題。由此,SMP 又稱爲 UMA(Uniform Memory Access,一致性存儲器訪問),所謂一致性指的是:數據庫
顯然,這樣的架構設計註定無法擁有良好的處理器數量擴展性,由於共享存儲的資源競態老是存在的,處理器利用率最好的狀況只能停留在 2 到 4 顆。綜合來看,SMP 架構普遍的適用於 PC 和移動設備領域,能顯著提高並行計算性能。但 SMP 卻不適合超大規模的服務器端場景,例如:雲計算。vim
現代計算機系統中,處理器的處理速度已經超過了主存的讀寫速度,限制計算機計算性能的瓶頸轉移到了存儲器帶寬之上。SMP 因爲集中式共享存儲器的設計限制了處理器訪問存儲器的頻次,致使處理器可能會常常處於對數據訪問的飢餓狀態。緩存
**NUMA(Non-Uniform Memory Access,非一致性存儲器訪問)**的設計理念是將處理器和存儲器劃分到不一樣的節點(NUMA Node),它們都擁有幾乎相等的資源。在 NUMA 節點內部會經過本身的存儲總線訪問內部的本地內存,而全部 NUMA 節點均可以經過主板上的共享總線來訪問其餘節點的遠程內存。安全
很顯然,處理器訪問本地內存和遠程內存的時耗並不一致,NUMA 非一致性存儲器訪問由此得名。並且由於節點劃分並無實現真正意義上的存儲隔離,因此 NUMA 一樣只會保存一份操做系統和數據庫系統的副本。性能優化
NUMA「多節點」的結構設計可以在必定程度上解決 SMP 低存儲帶寬的問題。假若有一個 4 NUMA 節點的系統,每個 NUMA 節點內部具備 1GB/s 的存儲帶寬,外部共享總線也一樣具備 1GB/s 的帶寬。理想狀態下,若是全部的處理器老是訪問本地內存的話,那麼系統就擁有了 4GB/s 的存儲帶寬能力,此時的每一個節點能夠近似的看做爲一個 SMP(這種假設爲了便於理解,並不徹底正確);相反,在最不理想的狀況下,若是全部處理器老是訪問遠程內存的話,那麼系統就只能有 1GB/s 的存儲帶寬能力。bash
除此以外,使用外部共享總線時可能會觸發 NUMA 節點間的 Cache 同步異常,這會嚴重影響內存密集型工做負載的性能。當 I/O 性能相當重要時,共享總線上的 Cache 資源浪費,會讓鏈接到遠程 PCIe 總線上的設備(不一樣 NUMA 節點間通訊)做業性能急劇降低。
因爲這個特性,基於 NUMA 開發的應用程序應該儘量避免跨節點的遠程內存訪問。由於,跨節點內存訪問不只通訊速度慢,還可能須要處理不一樣節點間內存和緩存的數據一致性。多線程在不一樣節點間的切換,是須要花費大成本的。
雖然 NUMA 相比於 SMP 具備更好的處理器擴展性,但由於 NUMA 沒有實現完全的主存隔離。因此 NUMA 遠沒有達到無限擴展的水平,最多可支持幾百個 CPU。這是爲了追求更高的併發性能所做出的妥協,一個節點未必就能徹底知足多併發需求,多節點間線程切換實屬一個折中的方案。這種作法使得 NUMA 具備必定的伸縮性,更加適合應用在服務器端。
MPP(Massive Parallel Processing,大規模並行處理),既然 NUMA 擴展性的限制是沒有徹底實現資源(e.g. 存儲器、互聯模塊)的隔離性,那麼 MPP 的解決思路就是爲處理器提供完全的獨立資源。
MPP 擁有多個真正意義上的獨立的 SMP 單元,每一個 SMP 單元獨佔並只會訪問本身本地的內存、I/O 等資源,SMP 單元間經過節點互聯網絡進行鏈接(Data Redistribution,數據重分配),是一個徹底無共享(Share Nothing)的 CPU 計算平臺結構。
MPP 的典型特徵就是**「多 SMP 單元組成,單元之間徹底無共享」**。除此以外,MPP 結構還具備如下特色:
由於徹底的資源隔離特性,因此 MPP 的擴展性是最好的,理論上其擴展無限制,目前的技術可實現 512 個節點互聯,數千個 CPU,多應用於大型機。
包含關係:NUMA Node > Socket > Core > Thread
EXAMPLE:上圖爲一個 NUMA Topology,表示該服務器具備 2 個 numa node,每一個 node 含有一個 socket,每一個 socket 含有 6 個 core,每一個 core 又被超線程爲 2 個 thread,因此服務器總共的 processor = 2*1*6*2 = 24 顆。
Linux 的每一個進程或線程都會延續父進程的 NUMA 策略,優先會將其約束在一個 NUMA node 內。固然了,若是 NUMA 策略容許的話,進程也能夠調用其餘 node 上的資源。
NUMA 的 CPU 分配策略有下列兩種:
NUMA 的 Memory 分配策略有下列 4 種:
Bash 腳本:
#!/bin/bash function get_nr_processor() { grep '^processor' /proc/cpuinfo | wc -l } function get_nr_socket() { grep 'physical id' /proc/cpuinfo | awk -F: '{ print $2 | "sort -un"}' | wc -l } function get_nr_siblings() { grep 'siblings' /proc/cpuinfo | awk -F: '{ print $2 | "sort -un"}' } function get_nr_cores_of_socket() { grep 'cpu cores' /proc/cpuinfo | awk -F: '{ print $2 | "sort -un"}' } echo '===== CPU Topology Table =====' echo echo '+--------------+---------+-----------+' echo '| Processor ID | Core ID | Socket ID |' echo '+--------------+---------+-----------+' while read line; do if [ -z "$line" ]; then printf '| %-12s | %-7s | %-9s |\n' $p_id $c_id $s_id echo '+--------------+---------+-----------+' continue fi if echo "$line" | grep -q "^processor"; then p_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '` fi if echo "$line" | grep -q "^core id"; then c_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '` fi if echo "$line" | grep -q "^physical id"; then s_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '` fi done < /proc/cpuinfo echo awk -F: '{ if ($1 ~ /processor/) { gsub(/ /,"",$2); p_id=$2; } else if ($1 ~ /physical id/){ gsub(/ /,"",$2); s_id=$2; arr[s_id]=arr[s_id] " " p_id } } END{ for (i in arr) printf "Socket %s:%s\n", i, arr[i]; }' /proc/cpuinfo echo echo '===== CPU Info Summary =====' echo nr_processor=`get_nr_processor` echo "Logical processors: $nr_processor" nr_socket=`get_nr_socket` echo "Physical socket: $nr_socket" nr_siblings=`get_nr_siblings` echo "Siblings in one socket: $nr_siblings" nr_cores=`get_nr_cores_of_socket` echo "Cores in one socket: $nr_cores" let nr_cores*=nr_socket echo "Cores in total: $nr_cores" if [ "$nr_cores" = "$nr_processor" ]; then echo "Hyper-Threading: off" else echo "Hyper-Threading: on" fi echo echo '===== END ====='
OUTPUT:
===== CPU Topology Table ===== +--------------+---------+-----------+ | Processor ID | Core ID | Socket ID | +--------------+---------+-----------+ | 0 | 0 | 0 | +--------------+---------+-----------+ | 1 | 1 | 0 | +--------------+---------+-----------+ | 2 | 2 | 0 | +--------------+---------+-----------+ | 3 | 3 | 0 | +--------------+---------+-----------+ | 4 | 4 | 0 | +--------------+---------+-----------+ | 5 | 5 | 0 | +--------------+---------+-----------+ | 6 | 0 | 1 | +--------------+---------+-----------+ | 7 | 1 | 1 | +--------------+---------+-----------+ | 8 | 2 | 1 | +--------------+---------+-----------+ | 9 | 3 | 1 | +--------------+---------+-----------+ | 10 | 4 | 1 | +--------------+---------+-----------+ | 11 | 5 | 1 | +--------------+---------+-----------+ | 12 | 0 | 0 | +--------------+---------+-----------+ | 13 | 1 | 0 | +--------------+---------+-----------+ | 14 | 2 | 0 | +--------------+---------+-----------+ | 15 | 3 | 0 | +--------------+---------+-----------+ | 16 | 4 | 0 | +--------------+---------+-----------+ | 17 | 5 | 0 | +--------------+---------+-----------+ | 18 | 0 | 1 | +--------------+---------+-----------+ | 19 | 1 | 1 | +--------------+---------+-----------+ | 20 | 2 | 1 | +--------------+---------+-----------+ | 21 | 3 | 1 | +--------------+---------+-----------+ | 22 | 4 | 1 | +--------------+---------+-----------+ | 23 | 5 | 1 | +--------------+---------+-----------+ Socket 0: 0 1 2 3 4 5 12 13 14 15 16 17 Socket 1: 6 7 8 9 10 11 18 19 20 21 22 23 ===== CPU Info Summary ===== Logical processors: 24 Physical socket: 2 Siblings in one socket: 12 Cores in one socket: 6 Cores in total: 12 Hyper-Threading: on ===== END =====
NOTE 1:Processors / Cores = 每一個物理內核超線程出來的邏輯處理器數量,通常爲 2 個。
NOTE 2:上述具備相同 Socket ID 和 Core ID 的 2 個 Processors 就是由同一個 Core 超線程出來的兩個邏輯處理器。
DPDK 官方提供的 Python 腳本:
#!/usr/bin/env python # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2010-2014 Intel Corporation # Copyright(c) 2017 Cavium, Inc. All rights reserved. from __future__ import print_function import sys try: xrange # Python 2 except NameError: xrange = range # Python 3 sockets = [] cores = [] core_map = {} base_path = "/sys/devices/system/cpu" fd = open("{}/kernel_max".format(base_path)) max_cpus = int(fd.read()) fd.close() for cpu in xrange(max_cpus + 1): try: fd = open("{}/cpu{}/topology/core_id".format(base_path, cpu)) except IOError: continue except: break core = int(fd.read()) fd.close() fd = open("{}/cpu{}/topology/physical_package_id".format(base_path, cpu)) socket = int(fd.read()) fd.close() if core not in cores: cores.append(core) if socket not in sockets: sockets.append(socket) key = (socket, core) if key not in core_map: core_map[key] = [] core_map[key].append(cpu) print(format("=" * (47 + len(base_path)))) print("Core and Socket Information (as reported by '{}')".format(base_path)) print("{}\n".format("=" * (47 + len(base_path)))) print("cores = ", cores) print("sockets = ", sockets) print("") max_processor_len = len(str(len(cores) * len(sockets) * 2 - 1)) max_thread_count = len(list(core_map.values())[0]) max_core_map_len = (max_processor_len * max_thread_count) \ + len(", ") * (max_thread_count - 1) \ + len('[]') + len('Socket ') max_core_id_len = len(str(max(cores))) output = " ".ljust(max_core_id_len + len('Core ')) for s in sockets: output += " Socket %s" % str(s).ljust(max_core_map_len - len('Socket ')) print(output) output = " ".ljust(max_core_id_len + len('Core ')) for s in sockets: output += " --------".ljust(max_core_map_len) output += " " print(output) for c in cores: output = "Core %s" % str(c).ljust(max_core_id_len) for s in sockets: if (s,c) in core_map: output += " " + str(core_map[(s, c)]).ljust(max_core_map_len) else: output += " " * (max_core_map_len + 1) print(output)
OUTPUT:
[root@overcloud-compute-0 ~]# python cpu_topo.py ====================================================================== Core and Socket Information (as reported by '/sys/devices/system/cpu') ====================================================================== cores = [0, 1, 2, 3, 4, 5] sockets = [0, 1] Socket 0 Socket 1 -------- -------- Core 0 [0] [6] Core 1 [1] [7] Core 2 [2] [8] Core 3 [3] [9] Core 4 [4] [10] Core 5 [5] [11]
上述輸出的意義:
Output:
[root@overcloud-compute-0 ~]# python cpu_topo.py ====================================================================== Core and Socket Information (as reported by '/sys/devices/system/cpu') ====================================================================== cores = [0, 1, 2, 3, 4, 5] sockets = [0, 1] Socket 0 Socket 1 -------- -------- Core 0 [0, 12] [6, 18] Core 1 [1, 13] [7, 19] Core 2 [2, 14] [8, 20] Core 3 [3, 15] [9, 21] Core 4 [4, 16] [10, 22] Core 5 [5, 17] [11, 23]
上述輸出的意義:
在 Icehouse 版本以前,Nova 定義的 libvirt.xml,不會考慮 Host NUMA 的狀況。致使 Libvirt 在默認狀況下,有可能發生跨 NUMA node 獲取 CPU/Memory 資源的狀況,致使 Guest 性能降低。Openstack 在 Juno 版本中新增 NUMA 特性,用戶能夠經過將 Guest 的 vCPU/Memory 綁定到 Host NUMA Node上,以此來提高 Guest 的性能。
除了上文中提到的 NUMA 基本概念以外,Nova 還自定義一些對象概念:
NOTE 1:vCPU 和 pCPU 的定義具備必定的迷惑性,簡單來理解:虛擬機實際是宿主機的一個進程,虛擬機 CPU 實際是宿主機進程中的一個特殊的線程。引入 pCPU 和 vCPU 的概念是爲了讓上層邏輯可以屏蔽機器 NUMA 拓撲的複雜性。
NOTE 2:Thread siblings 對象的引入是爲了不管服務器是否開啓了超線程,Nova 一樣可以支持物理 CPU 綁定的功能。
根據不一樣的操做系統發行版許可證,可能會嚴格約束操做系統可以支持的最大 sockets 數量,同時也就約束了服務器上可運行虛擬機的數量。因此,此時應該更加偏向於使用 core 來做爲 vCPU,而不是 socket。
由於許可證的影響,建議用戶在上傳鏡像到 Glance 時,指明一個運行鏡像最佳的 CPU 拓撲。雲平臺管理員也能夠經過修改 CPU 拓撲的默認值來避免用戶超出許可限制。也就是說,對於一個 4 vCPU 的虛擬機,若是使用的默認值限制最大 socket 爲 2,則能夠設置其 core 爲 2(在 Socket 數量沒有超出限制的前提下,虛擬機也能達到具備 4 Core 的效果)。
NOTE:OpenStack 管理員應該聽從操做系統許可需求,限制虛擬機使用的 CPU 拓撲(e.g. max_sockets==2)。設置默認的 CPU 拓撲參數,在保證 GuestOS 鏡像可以知足許可證的同時,又沒必要讓每一個用戶都單獨去設置鏡像屬性。
宿主機 CPU 拓撲的方式對其自身性能(Performance)具備很大影響。
單 Socket 單 Core 拓撲(單核結構):一個 Socket 只集成了一個 Core。對於多線程程序,主要是經過時間片輪轉來得到 CPU 的執行權,其實是串行執行,沒有作到並行執行。
單 Socket 多 Core 拓撲(多核結構):一個 Socket 集成了多個水平對稱(鏡像)的 Core,Core 之間經過 CPU 內部數據總線通訊。對於多線程程序,能夠經過多 Core 實現真正的並行執行。不過對於併發數或線程數要大於 Core 數的程序而言,多核結構存在線程(上下文)切換問題。這會帶來必定的開銷,但好在使用的是 CPU 內部數據總線,因此開銷會比較低。除此以外,還由於多 Core 是水平鏡像的,因此每一個 Core 都有着本身的 Cache,在某些須要使用共享數據(共享數據極可能會被 Cache 住)的場景中,存在多核 Cache 數據一致性的問題,這也會帶來一些開銷。
多 Socket 單 Core 拓撲: 多 Socket 之間經過主板上的總線進行通訊,集成爲一個統一的計算平臺。每個 Socket 都擁有獨立的內部數據總線和 Cache。對於多線程程序,能夠經過多 Socket 來實現並行執行。不一樣於單 Socket 多 Core 拓撲,多 Socket 單 Core 拓撲的線程切換以及 Socket 間通訊走的都是外部總線,因此開銷會比使用 CPU 內部數據總線高得多、延時也更長。固然,在使用共享數據的場景中,也一樣存在多 Socket 間 Cache 一致性的問題。多 Socket 拓撲的性能瓶頸在於 Socket 間的 I/O 通信成本。
超線程拓撲(Hyper-Threading):將一個 Core 虛擬爲多個 Thread(邏輯處理器),實現一個 Core 也能夠並行執行多個線程。Thread 擁有本身的寄存器和中斷邏輯,不過 Thread 間會共享執行單元(ALU 邏輯運算單元)和 Cache,因此性能提高是比較有限的,但也很是極致了。
超線程結構
普通 CPU 內部結構
多 Socket 多 Core 超線程拓撲:具備多個 Socket,每一個 Socket 又包含有多個 Core,每一個 Core 有虛擬出多個 Thread。是上述拓撲類型的集大成者,擁有最好的性能和最早進的工藝,常見於企業級的服務器產品,例如:MPP、NUMA 計算平臺系統。
NOTE 1:「多 Socket 單 Core 拓撲」的多線程,Socket 間協做要經過外部總線通訊,在不一樣 Socket 上執行的線程間的共享數據可能會同時存放在不一樣的 Socket Cache 上,因此要保證不一樣 Cache 的數據一致性。具備通訊開銷大,線程切換開銷大,Cache 數據一致性難維持,多 Socket 佔位面積大,集成佈線工藝難等問題。
NOTE 2:「單 Socket 多 Core 拓撲」的多線程,每一個 Core 處理一個線程,支持併發。具備多 Core 之間通訊開銷小,Socket 佔位面積小等優點。可是,當須要運行多個 「大程序」(一個程序就能夠將內存、Cache、Core 佔滿)的話,就至關於多個大程序須要經過分時切片來使用 CPU。此時,程序間的上下文(指令、數據替換)切換消耗將會是巨大的。因此「單 Socket 多 Core 拓撲」在多任務、高併發、高消耗內存的程序運行環境中效率會變得很是低下(大程序會獨佔一個 Socket)。
綜上,對於程序規模小的應用場景,建議使用「單 Socket 多 Core 拓撲」,例如我的 PC 的 Dell T3600(單 CPU 6 核,超線程支持虛擬出 12 顆邏輯核心);對於多大規模程序的應用場景(e.g. 雲計算服務器端),建議使用「多 Socket 單 Core」甚至是「多 Socket 多 Core 超線程」的組合,爲每一個程序分配到單個 CPU,爲每一個程序的線程分配到單個 CPU 中的 Core。
CPU 架構對於併發程序設計而言,主要須要考慮兩個問題,一個是內存可見性問題,一個是 Cache 一致性問題。前者屬於併發安全問題,後者則屬於性能範疇的問題。
須要注意的是,超線程技術並不是萬能藥。從 Intel 和 VMware 對外公開的資料看,開啓超線程後,Core 的總計算能力是否提高以及提高的幅度和業務模型相關,平均提高在 20%-30% 左右。但超線程對 Core 的執行資源的爭搶,業務的執行時延也會相應增長。當超線程相互競爭時,超線程的計算能力相比不開超線程時的物理核甚至會降低 30% 左右。因此,超線程應該關閉仍是開啓,主要仍是取決於應用模型。
如今不少應用,好比 Web App,大多會採用多 Worker 設計,在超線程的幫助下,兩個被調度到同一個 Core 下不一樣 Thread 的 Worker,因爲 Threads 共享 Cache,TLB(Translation Lookaside Buffer,轉換檢測緩衝區),因此可以大幅下降 Workers 線程切換的開銷。另外,在某個 Worker 不忙的時候,超線程容許其它的 Worker 先使用物理計算資源,以此來提高 Core 的總體吞吐量。
從上圖能夠看出,應用了 HT 技術的場景,處理器執行單元閒置的狀況被有效減小了,並且 Thread 1 和 Thread 2 兩個線程是被交叉處理的。
但因爲 Threads 之間會爭搶 Core 的物理執行資源,致使單個 Thread 的執行時延也會相應增長,響應速度不如當初。對於 CPU 密集型任務而言,當存在超線程競爭時,超線程計算能力大概是物理核的 60% 左右(非官方數據)。
NOTE:
回到虛擬機應用場景,當咱們在 vSphere 的 ESXi 主機上運行兩個 1 vCPU 的虛擬機,分別綁定到一個 Core 的兩個 Thread 上,在虛擬機內部運行計算密集型的編譯任務,並確保虛擬機內部 CPU 佔用率在 50% 左右。從 ESXi 主機上看,兩個 Thread 使用率在 45% 左右,但 Core 的負載就已經達到了 80%。可見,超線程競爭問題會讓運行計算密集型應用的虛擬機性能損耗很是嚴重。
由此,須要注意,若是用戶對虛擬機的性能要求比較高,那麼不該該讓虛擬機的 vCPU 運行在 Thread 上,而應該將 vCPU 運行在 Socket 或者 Core 上。對於開啓了超線程的 Compute Node,應該提供一種機制可以將 Threads 過濾掉或抽象爲一個 「Core」,這就是引入 Siblings Thread 的意義。
即使在對虛擬機性能要求不高的場景中,除非咱們將虛擬機的 CPU 和宿主機的超線程一一綁定,不然並不建議應該使用超線程技術,pCPU 應該被映射爲一個 Socket 或 Core。換句話說,若是咱們但願開啓 Nova Compute Node 的超線程功能,那麼我會建議你使用 CPU 綁定功能來將虛擬機的 vCPU 綁定到某一個 pCPU(此時 pCPU 映射爲一個 Thread)上。
如今的服務器基本都支持 NUMA 拓撲,上文已經提到過,主要驅動 NUMA 體系結構應用的因素是 NUMA 具備的高存儲訪問帶寬、有效的 Cache 效率以及靈活 PCIe I/O 設備的佈局設計。但因爲 NUMA 跨節點遠程內存訪問不只延時高、帶寬低、消耗大,還可能須要處理數據一致性的問題。所以,虛擬機的 vCPU 和內存在 NUMA 節點上的錯誤佈局,將會導宿主機資源的嚴重浪費,這將抹掉任何內存與 CPU 決策所帶來的好處。因此,標準的策略是儘可能將一個虛擬機徹底侷限在單個 NUMA 節點內。
將虛擬機的 vCPU/Mem 徹底侷限在單個 NUMA 節點內是最佳的方案,但假如分配給虛擬機的 vCPU 數量以及內存大小超過了一個 NUMA 節點所擁有的資源呢?此時必須針對大資源需求的虛擬機設計出合適的策略,Guest NUMA Topology 的概念也是爲此而提出。
這些策略或許禁止建立超出單一 NUMA 節點拓撲的虛擬機,或許容許虛擬機跨多 NUMA 節點運行。而且在虛擬機遷移時,容許更改這些策略。也就是說,在對宿主機(Compute Node)進行維護時,接收臨時下降性能而選擇次優的 NUMA 拓撲佈局。固然了,NUMA 拓撲佈局的問題還須要考慮到虛擬機的具體使用場景,例如,NFV 虛擬機的部署就會強制的要求嚴格的 NUMA 拓撲佈局。
若是虛擬機具備多個 Guest NUMA Node,爲了讓操做系統能最大化利用其分配到的資源,宿主機的 NUMA 拓撲就必須暴露給虛擬機。讓虛擬機的 Guest NUMA Node 與宿主機的 Host NUMA Node 進行關聯映射。這樣能夠映射大塊的虛擬機內存到宿主機內存,和設置 vCPU 與 pCPU 的映射。
Guest NUMA Topology 其實是將一個大資源需求的虛擬機劃分爲多個小資源需求的虛擬機,將多個 Guest NUMA Node 分別綁定到不一樣的 Host NUMA Node。這樣作是由於虛擬機內部運行的工做負載一樣會遵照 NUMA 節點原則,最終的效果實際上就是虛擬機的工做負載依舊有效的被限制在了一個 Host NUMA Node 內。也就是說,若是虛擬機有 4 vCPU 須要跨兩個 Host NUMA Node,vCPU 0/1 綁定到 Host NUMA Node 1,而 vCPU 2/3 綁定到 Host NUMA Node 2 上。而後虛擬機內的 DB 應用分配到 vCPU 0/1,Web 應用分配到 vCPU 2/3,這樣實際就是 DB 應用和 Web 應用的線程始終被限制在了同一個 Host NUMA Node 上。可是,Guest NUMA Topology 並不強制將 vCPU 與對應的 Host NUMA Node 中特定的 pCPU 進行綁定,這能夠由操做系統調度器來隱式完成。只是若是宿主機開啓了超線程,則要求將超線程特性暴露給虛擬機,並在 NUMA Node 內綁定 vCPU 與 pCPU 的關係。不然 vCPU 會被分配給 Siblings Thread,因爲超線程競爭,性能遠不如將 vCPU 分配到 Socket 或 Core 的好。
NOTE:若是 Guest vCPU/Mem 需求超過了單個 Host NUAM Node,那麼應該將 Guest NUMA Topology 劃分爲多個 Guest NUMA Node,並分別映射到不一樣的 Host NUMA Node 上。
首先判斷該物理服務器是否支持 NUMA 功能:
$ dmesg | grep -i numa [ 0.000000] Enabling automatic NUMA balancing. Configure with numa_balancing= or the kernel.numa_balancing sysctl [ 0.674375] pci_bus 0000:00: on NUMA node 0 [ 0.678884] pci_bus 0000:40: on NUMA node 1 [ 0.682517] pci_bus 0000:3f: on NUMA node 0 [ 0.686182] pci_bus 0000:7f: on NUMA node 1
若是輸出上述內容則表示支持 NUMA,若是輸出 No NUMA configuration found 則表示不支持。
查看物理服務器的 NUMA 拓撲:
$ yum install -y numactl $ numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 2 4 6 node 0 size: 32690 MB node 0 free: 19579 MB node 1 cpus: 1 3 5 7 node 1 size: 32768 MB node 1 free: 19208 MB node distances: node 0 1 0: 10 20 1: 20 10
使用 numactl 指令能夠查看 NUMA 的節點及各節點上邏輯 CPU 和 RAM 的狀況。
查看物理服務器是否開啓了超線程:能夠直接執行上述 cpu_topo 腳本,也能夠手動執行如下指令來判斷。
# 查看物理服務器的 Socket 數量 cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l # 查看每一個 Socket 的 Core 數量 cat /proc/cpuinfo | fgrep "cores" | uniq # 查看每一個 Socket 的 Siblings 數量 grep "siblings" /proc/cpuinfo|uniq # 查看 Processor 的總數 cat /proc/cpuinfo | grep "processor" | wc -l
若是 Processors == Sockets * Cores
,則表示超線程沒有開啓。若是 Processors 是 Sockets * Cores 的倍數,則表示開啓了超線程。或者說若是每一個 Socket 的 Siblings 與 Core 的數量相同,表示沒有開啓超線程(Core 沒有 virtual processor)。
查看各邏輯 CPU 的使用狀況:
$ yum install -y sysstat $ mpstat -P ALL Linux 3.10.0-957.1.3.el7.x86_64 (localhost) 01/19/2019 _x86_64_ (8 CPU) 03:06:35 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 03:06:35 AM all 4.35 0.00 1.73 0.32 0.00 0.02 0.00 0.77 0.00 92.80 03:06:35 AM 0 3.78 0.00 1.52 0.44 0.00 0.05 0.00 0.86 0.00 93.36 03:06:35 AM 1 5.02 0.00 1.64 0.40 0.00 0.02 0.00 0.10 0.00 92.83 03:06:35 AM 2 4.73 0.00 2.15 0.23 0.00 0.02 0.00 0.82 0.00 92.05 03:06:35 AM 3 6.09 0.00 2.14 0.41 0.00 0.02 0.00 0.11 0.00 91.24 03:06:35 AM 4 4.00 0.00 1.65 0.17 0.00 0.02 0.00 0.77 0.00 93.39 03:06:35 AM 5 4.24 0.00 1.65 0.41 0.00 0.01 0.00 0.36 0.00 93.33 03:06:35 AM 6 3.20 0.00 1.57 0.12 0.00 0.02 0.00 2.70 0.00 92.39 03:06:35 AM 7 3.76 0.00 1.55 0.41 0.00 0.01 0.00 0.45 0.00 93.82
nova-scheduler 啓用 NUMATopologyFilter:
# nova.conf [DEFAULT] ... scheduler_default_filters=...,NUMATopologyFilter
Nova 的 NUMA 親和原則是:將 Guest vCPU/Mem 都分配在同一個 NUMA Node 上,充分使用 NUMA node local memory,避免跨 Node 訪問 remote memory。
openstack flavor set FLAVOR-NAME \ --property hw:numa_nodes=FLAVOR-NODES \ --property hw:numa_cpus.N=FLAVOR-CORES \ --property hw:numa_mem.N=FLAVOR-MEMORY
設定 Guest NUMA Topology 的兩種方式:
NOTE:選擇使用自動設定方式時,建議一同使用 hw:numa_mempolicy
屬性,表示 NUMA 的 Mem 訪問策略,有嚴格訪問本地內存的 strict 和寬鬆的 preferred 兩種選擇,這樣能夠最大程度下降配置參數的複雜性。並且對於某些特定工做負載的 NUMA 架構問題,好比:MySQL 「swap insanity」 問題 ,或許 preferred 會是一個不錯的選擇。
hw:numa_cpus.N
和 hw:numa_mem.N
來指定每一個 Guest NUMA nodes 上分配的 vCPUs 和 Memory Size。Nova Scheduler 會根據參數 hw:numa_nodes
來決定如何映射 Guest NUMA node。若是沒有設置該參數,那麼 Scheduler 將自由的決定在哪裏運行虛擬機,而無需關心單個 NUMA 節點是否可以知足虛擬機 flavor 中的 vCPU/Mem 配置,但仍會優先考慮選出一個 NUMA 節點就能夠知足狀況的計算節點。
NOTE 1:只有在設定了 hw:numa_nodes
後,hw:numa_cpus.N
和 hw:numa_mem.N
纔會生效。只有當 Guest NUMA nodes 存在非對稱訪問 vCPU/Mem 時(Guest NUMA Nodes 之間擁有的 vCPU 數量和 Mem 大小並不是是鏡像的),才須要去設定這些參數。
NOTE 2:N 僅僅是 Guest NUMA node 的索引,並不是實際上的 Host NUMA node 的 ID。例如,Guest NUMA node 0,可能會被映射到 Host NUMA node 1。相似的,FLAVOR-CORES 的值也僅僅是 vCPU 的索引。所以,Nova 的 NUMA 特性並不能用來約束 Guest vCPU/Mem 綁定到指定的 Host NUMA node 上。要完成 vCPU 綁定到指定的 pCPU,須要藉助 CPU Pinning policy 機制。
WARNING:若是 hw:numa_cpus.N
和 hw:numa_mem.N
的設定值大於虛擬機自己可用的 CPUs/Mem,則觸發異常。
EXAMPLE:定義虛擬機有 4 vCPU,4096MB Mem,設定 Guest NUMA topology 爲 2 Guest NUMA node:
openstack flavor set aze-FLAVOR \ --property hw:numa_nodes=2 \ --property hw:numa_cpus.0=0 \ --property hw:numa_cpus.1=1,2,3 \ --property hw:numa_mem.0=1024 \ --property hw:numa_mem.1=3072 \
NOTE:numa_cpus 指定的是 vCPUs 的序號,而非 pCPUs。
使用該 flavor 建立的虛擬機時,最後由 Libvirt Driver 完成將 Guest NUMA node 映射到 Host NUMA node 上。
除了經過 Flavor extra-specs 來設定 Guest NUMA topology 以外,還能夠經過 Image Metadata 來設定。e.g.
glance image-update --property \ hw_numa_nodes=2 \ hw_numa_cpus.0=0 \ hw_numa_mem.0=1024 \ hw_numa_cpus.1=1,2,3 \ hw_numa_mem.1=3072 \ image_name
注意,當鏡像的 NUMA 約束與 Flavor 的 NUMA 約束衝突時,以 Flavor 爲準。
NOTE 1:KVM 的宿主機會暴露出 Host NUMA Topology 的細節(e.g. NUMA 節點數量,NUMA 節點的內存 total 和 free,NUMA 節點的 CPU total 和 free),但其餘 Hypervisor 的操做系統平臺未必會將這些信息暴露出來,好比 VMware 只能經過 vSphere WS API 來得到並不 「完整」 的拓撲信息。因此,NUMA 親和特性適配度最高的仍是 KVM。
NOTE 2:nova-compute service 的 ResourceTracker 經過 Hyper Driver 定時收集宿主機的 Host NUMA Topology 信息。
Step 1. 首先查看當前物理服務器的 NUMA 拓撲
[root@localhost ~]# numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 2 4 6 node 0 size: 32690 MB node 0 free: 19626 MB node 1 cpus: 1 3 5 7 node 1 size: 32768 MB node 1 free: 19146 MB node distances: node 0 1 0: 10 20 1: 20 10 [root@localhost ~]# bash numa_topo.sh ===== CPU Topology Table ===== +--------------+---------+-----------+ | Processor ID | Core ID | Socket ID | +--------------+---------+-----------+ | 0 | 0 | 0 | +--------------+---------+-----------+ | 1 | 0 | 1 | +--------------+---------+-----------+ | 2 | 1 | 0 | +--------------+---------+-----------+ | 3 | 1 | 1 | +--------------+---------+-----------+ | 4 | 2 | 0 | +--------------+---------+-----------+ | 5 | 2 | 1 | +--------------+---------+-----------+ | 6 | 3 | 0 | +--------------+---------+-----------+ | 7 | 3 | 1 | +--------------+---------+-----------+ Socket 0: 0 2 4 6 Socket 1: 1 3 5 7 ===== CPU Info Summary ===== Logical processors: 8 Physical socket: 2 Siblings in one socket: 4 Cores in one socket: 4 Cores in total: 8 Hyper-Threading: off ===== END =====
顯而易見,當前物理服務器具備 2 個 NUMA Node,詳情爲:
Step 2. 查看邏輯 CPU 的空閒狀況
[root@localhost ~]# mpstat -P ALL Linux 3.10.0-957.1.3.el7.x86_64 (localhost) 01/19/2019 _x86_64_ (8 CPU) 03:21:19 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 03:21:19 AM all 4.35 0.00 1.73 0.32 0.00 0.02 0.00 0.77 0.00 92.80 03:21:19 AM 0 3.78 0.00 1.52 0.44 0.00 0.05 0.00 0.86 0.00 93.36 03:21:19 AM 1 5.02 0.00 1.64 0.40 0.00 0.02 0.00 0.10 0.00 92.83 03:21:19 AM 2 4.73 0.00 2.15 0.23 0.00 0.02 0.00 0.82 0.00 92.05 03:21:19 AM 3 6.09 0.00 2.14 0.41 0.00 0.02 0.00 0.11 0.00 91.24 03:21:19 AM 4 4.00 0.00 1.65 0.17 0.00 0.02 0.00 0.77 0.00 93.39 03:21:19 AM 5 4.24 0.00 1.65 0.41 0.00 0.01 0.00 0.36 0.00 93.33 03:21:19 AM 6 3.20 0.00 1.57 0.12 0.00 0.02 0.00 2.70 0.00 92.39 03:21:19 AM 7 3.76 0.00 1.55 0.41 0.00 0.01 0.00 0.45 0.00 93.82
從 %idle
字段能夠看出 8 個邏輯 CPU 都比較空閒,能夠任意使用。
Step 3. 啓用 NUMATopologyFilter
vim /etc/nova/nova.conf
[filter_scheduler] ... enabled_filters = ...,NUMATopologyFilter
重啓 nova-scheduler 服務
systemctl restart devstack@n-sch
Step 4. 建立 Nova Flavor
[root@localhost ~]# openstack flavor create --ram 2048 --disk 20 --vcpus 2 2C2G20D-NUMA-CPU_binding +----------------------------+--------------------------------------+ | Field | Value | +----------------------------+--------------------------------------+ | OS-FLV-DISABLED:disabled | False | | OS-FLV-EXT-DATA:ephemeral | 0 | | disk | 20 | | id | 95c75cca-f864-4d8d-ae25-c1544de8af53 | | name | 2C2G20D-NUMA-CPU_binding | | os-flavor-access:is_public | True | | properties | | | ram | 2048 | | rxtx_factor | 1.0 | | swap | | | vcpus | 2 | +----------------------------+--------------------------------------+
Step 5. Setup Guest NUMA Topo
[root@localhost ~]# openstack flavor set 2C2G20D-NUMA-CPU_binding \ > --property hw:numa_nodes=2 \ > --property hw:numa_cpus.0=0 \ > --property hw:numa_cpus.1=1 \ > --property hw:numa_mem.0=1024 \ > --property hw:numa_mem.1=1024
Step 6. Setup CPU Binding Policy
[root@localhost ~]# openstack flavor set 2C2G20D-NUMA-CPU_binding \ > --property hw:cpu_policy=dedicated \ > --property hw:cpu_thread_policy=isolate
Step 7. 啓動測試虛擬機
$ openstack server create --image cirros-0.3.4-x86_64-disk --network web-server-net --flavor 2C2G20D-NUMA-CPU_binding VM1 +-------------------------------------+-----------------------------------------------------------------+ | Field | Value | +-------------------------------------+-----------------------------------------------------------------+ | OS-DCF:diskConfig | MANUAL | | OS-EXT-AZ:availability_zone | | | OS-EXT-SRV-ATTR:host | None | | OS-EXT-SRV-ATTR:hypervisor_hostname | None | | OS-EXT-SRV-ATTR:instance_name | | | OS-EXT-STS:power_state | NOSTATE | | OS-EXT-STS:task_state | scheduling | | OS-EXT-STS:vm_state | building | | OS-SRV-USG:launched_at | None | | OS-SRV-USG:terminated_at | None | | accessIPv4 | | | accessIPv6 | | | addresses | | | adminPass | v2NfftjquA4K | | config_drive | | | created | 2019-01-19T08:47:36Z | | flavor | 2C2G20D-NUMA-CPU_binding (95c75cca-f864-4d8d-ae25-c1544de8af53) | | hostId | | | id | 84df4e16-c1e0-476e-8f5b-f13c656e768c | | image | cirros-0.3.4-x86_64-disk (60076364-c413-47f2-badf-48e03acb47da) | | key_name | None | | name | VM1 | | progress | 0 | | project_id | 12b567f21ed04e80b5c3f24717507464 | | properties | | | security_groups | name='default' | | status | BUILD | | updated | 2019-01-19T08:47:36Z | | user_id | 5ce6578ec1424796b6c883f06fcf834f | | volumes_attached | | +-------------------------------------+-----------------------------------------------------------------+
Step 8. 查看虛擬機的 CPU 綁定狀況
[root@localhost ~]# virsh list Id Name State ---------------------------------------------------- 8 instance-0000000c running [root@localhost ~]# virsh vcpuinfo instance-0000000c VCPU: 0 CPU: 2 State: running CPU time: 3.0s CPU Affinity: --y----- VCPU: 1 CPU: 5 State: running CPU time: 1.0s CPU Affinity: -----y--
一顆 vCPU 綁定在 Processor 2,另外一顆綁定在 Processor 5,在兩個不一樣的 NUMA node 上。
若是你建立虛擬機失敗,而且在 nova-conductor service 的日誌看見 Requested instance NUMA topology cannot fit the given host NUMA topology,那麼你或許應該檢查一下 nova-scheduler service 是否啓用了 NUMATopologyFilter 以及能夠檢查一下是否還具備足夠的 NUMA 資源了。
若是你建立虛擬機是被,而且在 nova-scheduler service 的日誌看見 Filter NUMATopologyFilter returned 0 hosts,則表示 ComputeNodes 已經沒有足夠的 NUMA 資源了。或者你能夠考慮使用 hw:cpu_policy=shared
不獨佔 CPU 的策略。