干货分享 | 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中的设计与使用

图片


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