天天看點

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

目錄

最近太忙了,筆者實在懶得畫圖,文章的圖檔大多來源于網際網路,感謝創作者們(可惜找不到源出處)。

這篇博文與其說是介紹 OpenStack Nova 的高性能虛拟機,倒不如說是介紹 CPU 相關的硬體架構與應用程式之間的愛恨情仇更加貼切一些。

Nova 實作的 NUMA 親和

在 Icehouse 版本之前,Nova 定義的 libvirt.xml,不會考慮 Host NUMA 的情況。導緻 Libvirt 在預設情況下,有可能發生跨 NUMA node 擷取 CPU/Memory 資源的情況,導緻 Guest 性能下降。Openstack 在 Juno 版本中新增 NUMA 特性,使用者可以通過将 Guest 的 vCPU/Memory 綁定到 Host NUMA Node上,以此來提升 Guest 的性能。

除了上文中提到的 NUMA 基本概念之外,Nova 還自定義一些對象概念:

Cell:NUMA Node 的通名詞,供 Libvirt API 使用

vCPU:虛拟機的 CPU,根據虛拟機 NUMA 拓撲的不同,一個虛拟機 CPU 可以是一個 socket、core 或 thread。

pCPU:主控端的 CPU,根據主控端 NUMA 拓撲的不同,一個實體機 CPU 同樣可以是一個 socket、core 或 thread。

Siblings Thread:兄弟線程,即由同一個 Core 超線程出來的 Threads。

Host NUMA Topology:主控端的 NUMA 拓撲。

Guest NUMA Topology:虛拟機的 NUMA 拓撲。

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 的執行權,實際上是串行執行,沒有做到并行執行。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

單 Socket 多 Core 拓撲(多核結構):一個 Socket 內建了多個水準對稱(鏡像)的 Core,Core 之間通過 CPU 内部資料總線通信。對于多線程程式,可以通過多 Core 實作真正的并行執行。不過對于并發數或線程數要大于 Core 數的程式而言,多核結構存線上程(上下文)切換問題。這會帶來一定的開銷,但好在使用的是 CPU 内部資料總線,是以開銷會比較低。除此之外,還因為多 Core 是水準鏡像的,是以每個 Core 都有着自己的 Cache,在某些需要使用共享資料(共享資料很可能會被 Cache 住)的場景中,存在多核 Cache 資料一緻性的問題,這也會帶來一些開銷。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

多 Socket 單 Core 拓撲: 多 Socket 之間通過主機闆上的總線進行通信,內建為一個統一的計算平台。每一個 Socket 都擁有獨立的内部資料總線和 Cache。對于多線程程式,可以通過多 Socket 來實作并行執行。不同于單 Socket 多 Core 拓撲,多 Socket 單 Core 拓撲的線程切換以及 Socket 間通信走的都是外部總線,是以開銷會比使用 CPU 内部資料總線高得多、延時也更長。當然,在使用共享資料的場景中,也同樣存在多 Socket 間 Cache 一緻性的問題。多 Socket 拓撲的性能瓶頸在于 Socket 間的 I/O 通訊成本。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

超線程拓撲(Hyper-Threading):将一個 Core 虛拟為多個 Thread(邏輯處理器),實作一個 Core 也可以并行執行多個線程。Thread 擁有自己的寄存器和中斷邏輯,不過 Thread 間會共享執行單元(ALU 邏輯運算單元)和 Cache,是以性能提升是比較有限的,但也非常極緻了。

超線程結構

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

普通 CPU 内部結構

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

多 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 一緻性問題。前者屬于并發安全問題,後者則屬于性能範疇的問題。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

記憶體可見性問題:該問題在單處理器或單線程情況下是不會發生的。但在多線程環境中,因為線程會被配置設定到不同的 Core 上執行,是以會出現 Core1 和 Core2 可能會同時把主存中某個位置的值 load 到自己的一級緩存中,而 Core1 修改了自己一級緩存中的值後,卻不更新主存中對應的值,這樣對于 Core2 來說,将永遠看不到 Core1 對值的修改,進而導緻不能保證并發安全性。

Cache 一緻性問題:假如 Core1 和 Core2 同時把主存中的值 load 到自己的一級緩存,Core1 将值修改後,會通過 BUS 總線讓 Core2 中的值失效。Core2 發現自己一級緩存中的值失效後,會再通過 BUS 總線從主存中得到最新的值。但是,總線的通信帶寬是固定的,通過總線來進行各 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 的整體吞吐量。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

從上圖可以看出,應用了 HT 技術的場景,處理器執行單元閑置的情況被有效減少了,而且 Thread 1 和 Thread 2 兩個線程是被交叉處理的。

但由于 Threads 之間會争搶 Core 的實體執行資源,導緻單個 Thread 的執行時延也會相應增加,響應速度不如當初。對于 CPU 密集型任務而言,當存在超線程競争時,超線程計算能力大概是實體核的 60% 左右(非官方資料)。

OpenStack Nova 高性能虛拟機之 NUMA 架構親和

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 功能:

如果輸出上述内容則表示支援 NUMA,如果輸出 No NUMA configuration found 則表示不支援。

檢視實體伺服器的 NUMA 拓撲:

使用 numactl 指令可以檢視 NUMA 的節點及各節點上邏輯 CPU 和 RAM 的情況。

檢視實體伺服器是否開啟了超線程:可以直接執行上述 cpu_topo 腳本,也可以手動執行以下指令來判斷。

如果 ​<code>​Processors == Sockets * Cores​</code>​,則表示超線程沒有開啟。如果 Processors 是 Sockets * Cores 的倍數,則表示開啟了超線程。或者說如果每個 Socket 的 Siblings 與 Core 的數量相同,表示沒有開啟超線程(Core 沒有 virtual processor)。

檢視各邏輯 CPU 的使用情況:

nova-scheduler 啟用 NUMATopologyFilter:

Nova 的 NUMA 親和原則是:将 Guest vCPU/Mem 都配置設定在同一個 NUMA Node 上,充分使用 NUMA node local memory,避免跨 Node 通路 remote memory。

FLAVOR-NODES(整數):設定 Guest NUMA nodes 的個數。如果不指定,則 Guest vCPUs 可以在任意可用的 Host NUMA nodes 上浮動。

N:整數,Guest NUMA nodes ID,取值範圍在 [0, FLAVOR-NODES-1]。

FLAVOR-CORES(逗号分隔的整數):設定配置設定到 Guest NUMA node N 上運作的 vCPUs 清單。如果不指定,vCPUs 在 Guest NUMA nodes 之間平均配置設定。

FLAVOR-MEMORY(整數):機關 MB,設定配置設定到 Guest NUMA node N 上 Memory Size。如果不指定,Memory 在 Guest NUMA nodes 之間平均配置設定。

設定 Guest NUMA Topology 的兩種方式:

自動設定 Guest NUMA Topology:僅僅需要指定 Guest NUMA nodes 的個數,然後由 Nova 根據 Flavor 設定的虛拟機規格平均将 vCPU/Mem 分布到不同的 Host NUMA nodes 上(預設從 Host NUMA node 0 開始配置設定)。

NOTE:選擇使用自動設定方式時,建議一同使用 ​<code>​hw:numa_mempolicy​</code>​ 屬性,表示 NUMA 的 Mem 通路政策,有嚴格通路本地記憶體的 strict 和寬松的 preferred 兩種選擇,這樣可以最大程度降低配置參數的複雜性。而且對于某些特定工作負載的 NUMA 架構問題,比如:MySQL “swap insanity” 問題 ,或許 preferred 會是一個不錯的選擇。

手動設定 Guest NUMA Topology:不僅指定 Guest NUMA nodes 的個數,還需要通過 ​<code>​hw:numa_cpus.N​</code>​ 和 ​<code>​hw:numa_mem.N​</code>​ 來指定每個 Guest NUMA nodes 上配置設定的 vCPUs 和 Memory Size。

Nova Scheduler 會根據參數 ​<code>​hw:numa_nodes​</code>​ 來決定如何映射 Guest NUMA node。如果沒有設定該參數,那麼 Scheduler 将自由的決定在哪裡運作虛拟機,而無需關心單個 NUMA 節點是否能夠滿足虛拟機 flavor 中的 vCPU/Mem 配置,但仍會優先考慮選出一個 NUMA 節點就可以滿足情況的計算節點。

如果 numa_nodes = 1,Scheduler 将會選擇出單個 NUMA 節點能夠滿足虛拟機 flavor 配置的計算節點。

如果 numa_nodes &gt; 1,Scheduler 将會選擇出 NUMA 節點數量以及 NUMA 節點中資源情況能夠滿足虛拟機 flavor 配置的計算節點。

NOTE 1:隻有在設定了 ​<code>​hw:numa_nodes​</code>​ 後,​<code>​hw:numa_cpus.N​</code>​ 和 ​<code>​hw:numa_mem.N​</code>​ 才會生效。隻有當 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:如果 ​<code>​hw:numa_cpus.N​</code>​ 和 ​<code>​hw:numa_mem.N​</code>​ 的設定值大于虛拟機本身可用的 CPUs/Mem,則觸發異常。

EXAMPLE:定義虛拟機有 4 vCPU,4096MB Mem,設定 Guest NUMA topology 為 2 Guest NUMA node:

Guest NUMA node 0:vCPU 0、Mem 1024MB

Guest NUMA node 1:vCPU 1/2/3、Mem 3072MB

NOTE:numa_cpus 指定的是 vCPUs 的序号,而非 pCPUs。

使用該 flavor 建立的虛拟機時,最後由 Libvirt Driver 完成将 Guest NUMA node 映射到 Host NUMA node 上。

除了通過 Flavor extra-specs 來設定 Guest NUMA topology 之外,還可以通過 Image Metadata 來設定。e.g.

注意,當鏡像的 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 拓撲

顯而易見,目前實體伺服器具有 2 個 NUMA Node,詳情為:

2 Socket

4 Core/Socket

1 Processor/Core(Hyper-Threading off)

8 Processors

Step 2. 檢視邏輯 CPU 的空閑情況

從 ​<code>​%idle​</code>​ 字段可以看出 8 個邏輯 CPU 都比較空閑,可以任意使用。

Step 3. 啟用 NUMATopologyFilter

vim /etc/nova/nova.conf

重新開機 nova-scheduler 服務

Step 4. 建立 Nova Flavor

Step 5. Setup Guest NUMA Topo

Step 6. Setup CPU Binding Policy

Step 7. 啟動測試虛拟機

Step 8. 檢視虛拟機的 CPU 綁定情況

一顆 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 資源了。或者你可以考慮使用 ​<code>​hw:cpu_policy=shared​</code>​ 不獨占 CPU 的政策。

繼續閱讀