乾貨分享 | OpenStack性能優化實踐

圖片

友情提示:全文6000多文字,預計閱讀時間16分鐘

前言

從2010年A版至2019年T版,OpenStack現在已經進入了商用成熟期,在各大廠商都有了廣泛的應用。然而,OpenStack性能一直困擾着OpenStack的使用者們。筆者在對OpenStack性能分析中發現,除去python執行效率低的問題之外,OpenStack本身代碼存在較多不合理之處,也對性能造成了較大的影響。

爲此,筆者通過Pyflame工具對OpenStack組件——neutron的性能進行量化分析,找出其cpu耗時較多的代碼片段,並對其進行優化工作,從而在代碼層面優化OpenStack存在的性能問題。經以下實踐,代碼優化工作減少了port資源創建過程中40%的cpu耗時佔比。


Pyflame介紹

Pyflame[1]是由uber工程師編寫的、一個爲Python程序生成cpu耗時火焰圖[2]的程序效率分析工具。它是唯一一個基於Linux ptrace系統調用的Python分析器,這使得Pyflame能夠獲取到Python調用堆棧的快照,這意味着可以在不修改源代碼(像cProfile/memory_profile都需要修改代碼)的情況下分析整個程序。另外,Pyflame能夠通過加載嵌入式Python解釋器,比如WSGI,完全支持分析多線程Python程序。

優化前

使用OpenStack創建虛擬機流程中,與neutron直接交互的是創建端口(create-port)操作。爲此,我們選取create-port操作、使用Pyflame工具生成neutron-server的火焰圖,分析該操作的cpu耗時所在,並確定後續的優化思路。

 

注:火焰圖中調用棧越深,火焰就越高。佔據的寬度越寬,就表示執行時間長。所以火焰圖就是看頂層的哪個函數佔據的寬度最大。只要有"平頂"(plateaus),就表示該函數可能存在性能問題。

1.1 創建port流程


使用neutron創建port,分爲三個步驟:預處理、創建port的db和後處理。其中,預處理涉及默認安全組的創建;port db涉及將port寫入db和通知其它插件有port創建完成;後處理涉及將port創建完成事件告知dhcp agent。

1.2 實驗和數據


使用Pyflame監控neutron-server進程進行50個port併發創建,選取其中一個進程進行分析。Port創建佔用cpu時間爲總過程的67.65%,根據創建port的流程,我們將其分爲三個部分,對其中佔用cpu時間過長的部分進行梳理如下:


步驟

方法

cpu耗時(%)

描述

優化?

#port創建前的預處理

self._before_create_port

_ensure_default_security_group

5.38%

1) 爲租戶創建默認安全組

Y

# 創建port  db

self._create_port_db

 

_create_port_db:self.create_port_db

->

create_port_db:  self._get_network

14.90%

2)創建port的db

Y

_ipam_get_subnets:  self._find_candidate_subnets

6.92%

3)選取候選的subnet


allocate:  ipam_subnet.allocate

->

allocate:  self._generate_ip

15.68%

4)分配ip地址


_create_port_db:  self.get_network

5.06%

5) 與2)中調用的get_network方法相同。

Y

create_port_db:  registry.notify(resources.PORT, events.PRECOMMIT_CREATE, self, **kwargs)

7.80%
 (其中,獲取network的obj佔用4.86%


Y

# port創建完成後處理

create_port: self._after_create_port


5.67%

6) 發送消息告知dhcp  agents有port創建完成


表(1)創建port流程中各方法的CPU耗時


圖片

      圖(1) 創建port流程中各方法的CPU耗時


      1.3 優化思路


從創建port各調用的cpu耗時佔比表格中,我們可以看出以下幾個問題:

  • 存在重複的方法調用。如:neutron/db/db_base_plugin_common.py:get_network方法,就在db操作中被調用了2次,二者的總共耗時約佔總cpu耗時的30%;

  • 回調函數較多,主要用於通知其它插件或服務來對port創建過程不同狀態時做相應的處理,如安全組、qos、dhcp等,cpu耗時約佔30%;

  • 創建port的大部分cpu耗時花費在db上,約佔80%。

針對上述問題,我們根據從簡單到複雜的優化目標進行下述優化工作。

優化後

結合上一節中的優化思路,我們進行代碼優化工作,並使用Pyflame對neutron-server進行性能優化的驗證。

2.1 減少流程中存在的重複調用


通過對“創建port資源”的代碼流程進行梳理和分析,整理出重複調用的方法、模塊以及其耗時。

圖片

表(2) 創建port流程中重複的方法調用


通過對錶2的分析,我們的優化思路就在於消除重複方法調用的CPU耗時。


2.1.1 減少get_network()方法調用

從表2中,根據代碼的結構和功能,保留表2中的1-2,消除其它部分get_network方法的調用。在做了代碼的調整後,我們重新生成火焰圖,如下所示。


2.1.1.1 驗證network是否存在(1-1)


圖片

圖(2) 創建port db(優化前)


圖片

圖(3)創建port db(優化後)


從優化前後的火焰圖可以看出,優化後的火焰圖更加稀疏(實際上爲圖2去掉藍框——get_network後的剩餘部分),所以CPU耗時減少。


2.1.1.2 獲取network信息、並驗證qos_policy_id是否存在(1-3)


圖片

圖(4)驗證qos_policy_id(優化前)


圖片

圖(5)驗證qos_policy_id(優化後)


從優化前後的火焰圖可以看出,優化後的火焰圖更加稀疏(實際上爲圖4去掉藍框——get_network後的剩餘部分),所以該部分CPU耗時大大減少。


2.1.1.3 dhcp agent獲取network全量信息(1-4)

create_port: self._after_create_port中存在get_network方法獲取network信息,爲此,我們可以消除該部分的cpu耗時。


圖片

圖(6) dhcp agent獲取network全量信息(優化前)


圖片

圖(7) dhcp agent獲取network全量信息(優化後)


從優化前後的火焰圖可以看出,優化後的火焰圖更加稀疏(實際上爲圖6去掉藍框——get_network後的剩餘部分),所以CPU耗時減少。


2.1.2減少_ensure_default_security_group()的方法調用

從表2中,根據代碼的結構和功能,保留2-1,消除其它部分_ensure_default_security_group方法的調用。在做了代碼的調整後,我們重新生成火焰圖,如下所示。


2.1.2.1 租戶的默認安全組創建(2-2)

在create_port: self._before_create_port中有存在回調函數重複調用” _ensure_default_security_group(self, context, tenant_id)”方法來爲租戶創建默認安全組。


圖片

圖(8)租戶的默認安全組創建(優化前)


圖片

圖(9)租戶的默認安全組創建(優化後)


從優化前後的火焰圖可以看出,優化後的火焰圖更加稀疏(實際上爲圖9去掉藍框——_ensure_default_security_group後的剩餘部分),所以CPU耗時減少。


2.1.3 CPU耗時數據分析

2.1.3.1 創建port三大主要模塊CPU耗時情況

優化前後,創建port中各模塊CPU耗時佔比統計如下:



優化前

優化後

模塊

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

create_port: self._before_create_port

5.38%

8.95%

3.89%

11.68%

create_port: self._create_port_db

49.05%

81.6%

29.05%

87.23%

create_port: self._after_create_port

5.67%

9.43%

0.36%

1.08%

合計

60.10%

100%

33.30%

100%

表(3) 創建port的三個主模塊cpu耗時情況


從表3中,可以看出,neutron-server在創建port時的cpu耗時佔比縮短了40%(經過多次測試統計,優化後cpu耗時佔比減少了25%-40%,取決併發數目大小);且“create_port: self._after_create_port”模塊的CPU耗時佔比減幅最大,從9.43%減到1.08%。(當然,這也就造成了其它兩個模塊的CPU佔比升高)。

爲此,我們對另外兩個模塊的子模塊CPU耗時佔比進行計算,確定其CPU耗時優化效果。


2.1.3.2 子模塊self._create_port_db

1)我們優化了create_port: self._create_port_db模塊中的get_network方法,針對該模塊,我們統計優化前後,其主要子模塊的cpu耗時佔比如下:



優化前

優化後

子模塊

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

_create_port_db: self.create_port_db

30.99%

77.37%

12.72%

43.78% 

_create_port_db: self.get_network

5.06%

10.31%

10.10%

34.76%

_create_port_db: registry.notify(resources.PORT,  events.PRECOMMIT_CREATE, self, **kwargs)

7.80%

15.90%

2.27%

7.81% 

合計

49.05%

100%

29.05%

100%

表(4) self._create_port_db主要子模塊的CPU耗時佔比


從表(4)中我們可以看出,優化後_create_port_db: self.create_port_db子模塊cpu耗時佔比減少了約30%,回調函數的cpu耗時佔比減少了約8%(其中,self.get_network的耗時是不變的)。


2.1.3.3 子模塊self._after_create_port


優化前

優化後

子模塊

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

比值(cpu消耗時間/統計時長)

比值(cpu消耗時間/cpu總共消耗時間)

_notify_agents: self.plugin.get_network

4.25%

76.02%

0

0

_notify_agents: self.plugin.get_dhcp_agents_hosting_networks

0.93%

16.63%

0.06%

17.64%

_notify_agents: self._schedule_network

0.23%

4.11%

0.14%

41.1%

_notify_agents: self._cast_message

0.17%

3.04%

0.14%

41.1%

合計

5.59%

100%

0.34%

100%

表(5) self._after_create_port主要子模塊的CPU耗時佔比


從表(5)中我們可以看出,優化後_notify_agents: self.plugin.get_network的cpu耗時比爲0,回調函數的cpu耗時佔比減少了約8%(其中,self.get_network的耗時是不變的)。

2.2減少回調函數的耗時


上述分析中,我們通過在回調函數中減少重複調用,減少了回調函數的CPU耗時。其實,在實際應用中,我們可以通過裁減插件來減少回調函數的調用,這需要根據實際情況進行調整。

2.3 減少db操作的時間


由於篇幅限制,暫不在此文中體現。這裏,提供兩點思路:

1)  加速數據表和neutron obj的轉化時間;

2)  引入緩存機制。由於大部分的數據庫操作涉及查詢,因此,可以將port的信息加入緩存中,以減少查詢db的時間。


總結

本文旨在使用Pyflame對python代碼進行性能分析、爲OpenStack或其它python程序的性能優化提供思路。通過Pyflame爲neutron創建port生成火焰圖、並結合對neutron“創建port”的代碼流程進行分析,提出優化思路;根據優化思路,對neutron代碼進行修改,通過實驗驗證了優化思路的正確性,降低了neutron-server創建port的cpu耗時。實驗結果表明,優化後的代碼減少了創建port時資源處理流程40%的CPU資源消耗(在rally api測試時,減少cpu耗時約20%),從而提升了neutron-server創建port API的性能。


本文給出了OpenStack代碼層面性能優化的三個思路:

1) 減少流程中存在的重複調用。對於重複調用的方法、尤其涉及數據庫操作的方法,要儘量減少其調用次數;

2) 減少回調函數中插件(qos、security_group、l3等)的不必要耗時。該部分需要結合實際的應用場景;

3) 減少db操作的耗時。對於反覆查詢數據庫的操作、要儘量縮減到最小。

三個思路並不是完全割裂,三者之間存在着交叉、重疊部分。

   

值得注意的是,在代碼的性能優化中,刪減或重構任何代碼都需要經過深思熟慮、考慮組件或函數之間的影響,以免引出其它問題。

問題

1、爲什麼併發數選取在50,而不是其它數目?


A:其實在實驗驗證中,筆者也進行了併發100、200的實驗,其實驗結果如總結所述。Rally測試結果見附錄。

2、併發數由什麼控制?


A:起初,我們通過腳本的方式進行50/100/200併發的“創建port”;之後,也使用rally進行了api併發測試。

 3、表格中分析的結果準確程度如何?總結中爲何rally api測試結果cpu耗時減少爲20%?


A:其實,筆者的測試針對的是“創建port”在neutron plugin中這段代碼本身,經驗證,這段代碼的cpu耗時確實降低了不少,如總結所述爲40%;但是,真正使用neutron去創建一個資源,除了plugin去處理,wsgi和evenlet服務存在cpu耗時,而且這兩個服務自身耗時較長,所以,在rally api測試中,發現cpu耗時減少爲20%。


參考

[1] Pyflame, https://Pyflame.readthedocs.io/en/stable/

[2] Flame graph, http://www.brendangregg.com/flamegraphs.html

註釋

火焰圖就是看頂層的哪個函數佔據的寬度最大。只要有“平頂”(plateaus),就表示該函數可能存在性能問題。

附錄

Rally api測試結果


50 併發

優化效果

優化後cpu耗時降低了22%


優化前

圖片


優化後

圖片



100 併發

優化效果

優化後cpu耗時降低了20%


優化前

圖片


優化後

圖片


-End:)


1、乾貨分享 | 基於RocketMQ構建MQTT集羣系列(1)—從MQTT協議和MQTT集羣架構說起

2、乾貨分享 | 虛擬化性能提升之本地熱遷移

3、乾貨分享 | 時序數據庫Graphite在BC-DeepWatch中的設計與使用

圖片


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章