This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
<audio id="audio" title="30 | 服务器的管理和部署:工业界近几年有哪些发展趋势?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/59/cf/59c09e723d371e2f5f9e3bb5f064ddcf.mp3"></audio>
你好,我是庄振运。
说起服务器你一定不陌生。那你知道Facebook的服务器是什么样的吗要知道Facebook同时使用着很多不同的服务器。
在应对需要高速缓存的Facebook新闻、广告投放和搜索时Facebook使用的是有比较大内存和较强CPU的服务器。现在使用的每台服务器都有256GB的主内存和两个处理器的CPU。
而在需要存储大量照片和视频的时候Facebook就选择了适用于数据和对象存储的服务器这种服务器只有很少的内存但是却有几百TB的硬盘存储空间。
今天,我们就从“服务器”入手,进入一个新的专题:容量管理工程。我们一起来看,要如何针对服务器设计、规划和部署的特点,开发出性能优越,能充分利用硬件资源的应用程序和服务。
## 如何设计一种新的服务器?
就像我前面用来举例的Facebook一样大规模互联网公司的服务器和我们家里以及办公室用的电脑可不一样一般不是直接从市场上买的而是自己设计的。
那么这些服务器是怎么设计出来的呢?其实服务器的设计和其他的硬件设计一样,也需要经过好几个阶段,你可以看一下它们的设计路线图(如下图所示)。
<img src="https://static001.geekbang.org/resource/image/d4/5d/d46d0710af39a5fb02b16cda0ddff85d.png" alt="">
在最终进入大规模批量生产MP阶段之前服务器的设计需要经历四个阶段也就是
- Pre-EVTPre-Engineering Validation Test预先工程验证测试
- EVTEngineering Validation Test工程验证测试
- DVTDesign Validation Test设计验证测试
- PVTProduction Validation Test生产验证测试
这之后才是批量生产MP阶段。
每个阶段的目的和任务都有所区别,具体来讲,**Pre-EVT阶段**进行的是比较初级的模块测试,且往往是和硬件供应商一起进行的。这一阶段通过基准测试,**得出基本的模块性能**,为下一步的、更具体的设计提供性能数据。所以,此阶段一般只需要少数几台服务器原型就可以。
第二个阶段是**EVT**,这个阶段是服务器开发的初期设计验证,重点在**考虑服务器设计的完整度是否有遗漏**。这些测试包括功能和安全规格测试。这时候的服务器是样品问题可能较多测试可能会做N次。但是这一阶段需要有一个完整的服务器会运行更加具体的基准测试并根据测试结果来调整服务器设计。一般需要几十台服务器来进行测试。
**DVT阶段**的测试,需要生产厂商把服务器运送到数据中心。到此时,几乎所有的设计已全部完成,重点是找出可能的设计问题,确保所有的设计都符合规格。此时产品基本定型,可以进行负载测试,是为了发现在生产环境中可能出现的问题。这个阶段的测试可能需要用上百台服务器。
**PVT阶段**更加注重生产流程。服务器需要用类似真实的生产线来生产,并且运送到数据中心。这时需要进一步,用更加真实的应用程序来测试。这个阶段一般就要有百台以上甚至千台的服务器了。
**MP阶段**,就是批量生产了。到这个阶段,服务器硬件准备就绪,而且需要使用真实生产流程,来进行大规模生产和应用测试。所有设计及生产问题,应该没有任何遗漏及错误,成为正式面市的产品。
## 服务器设计的机遇在哪?
仅仅知道如何设计一种新服务器还不够你还需要知道整个业界如今的趋势才能给出最有性价比的服务器设计。虽然业界会有比如全球DRAM短缺这样的短期波动但是长期来看也是有迹可循的比如摩尔定律的放缓越来越大的HDD硬盘新的SSD技术等。
这些变化给我们的服务器设计带来了挑战但同时也带来了机遇。CPU、内存、硬盘、网络、单处理器等几个方面的变化不仅仅影响各公司数据中心未来服务器的发展方向也对公司的软件和服务设计有重大而深远的影响。
### CPU的趋势
从CPU的趋势来看简单来讲摩尔定律正在失效业界对CPU的性能增强一般是通过添加更多内核来增大吞吐量。对于英特尔和其他CPU厂商比如ARM来说这意味着每一代处理器将拥有更多的内核但运行频率一般要低于当前一代的处理器。之所以有这些趋势归根结底是因为用于生产芯片的材料所面临的挑战以及物理方面的基本限制例如光子和电子的特性等。
对软件来说传统上我们比较看重针对单线程的性能进行优化。鉴于这一硬件趋势的影响这样的优化策略需要进行调整。无论处理器的体系结构x86、ARM等如何**CPU单位价格的性能和每瓦性能的提升都在逐代地显著放缓**。
所以,我们的性能优化策略,应该着重服务的**水平扩展性**,就是通过多线程和多服务器来扩展服务的总体容量。
### DRAM内存的趋势
DRAM内存的趋势如何呢在全球市场上DRAM内存仍然相对稀缺价格很高。自2017年以来每GB的价格已翻了一番以上。与影响CPU一样相同的基本问题和挑战也影响着DRAM性能和容量的提高。所以各公司的服务器设计都开始转向使用尽量少的DRAM内存。
一个好消息是新型技术比如NVM非易失性内存等也在成熟这就为许多应用提供了更便宜的DRAM替代品。现在已经有越来越多的服务器设计开始**考虑使用NVM**。
如果你的互联网服务和程序使用内存较多并且SSD这样的快速存储还是不能满足服务的要求除了尽量减少内存使用外建议你考虑采用NVM。
### 硬盘的趋势
硬盘方面,硬盘的存储容量继续增加,但增速开始降低。这种趋势,是因为硬盘行业面临的材料挑战,以及物理方面的基本限制(例如控制磁场)。
虽然存储容量变大但是硬盘提供的IO性能比如每秒随机访问IOPS的性能并没有增加。为了利用这些更大容量的驱动器我们最好**把硬件和软件设计一体化**。比如,你可以把热数据缓存在闪存或内存中,并将相对较冷的数据存储在旋转硬盘上。
### 网络的趋势
与服务器功耗相比,数据中心的网络流量增长得更快。具体来说,与计算和存储有关的网络功耗不断增长,所以需要使用更快、更多的交换机间的网络链接。
另一方面新的技术比如硅光子技术进一步降低了成本也引发了新一轮的创新和数据中心的网络升级。比如有的数据中心已经开始使用400G、800G甚至1.6T的高速网络。
但是你要记住,不管网络技术如何发展,还是需要尽量地减少跨数据中心和跨机架的网络流量,也就是**尽量让网络在本地消化掉**。这里的“本地”可以是服务器本身、本机架、本数据大厅、本数据中心等等。为什么呢?还记得我以前说过的“带宽超订”吗(参考[第18讲](https://time.geekbang.org/column/article/185737)),因为越往外走,网络的带宽总是越小。
如何才能尽量让网络流量本地消化呢?这就需要你考察公司内部各种服务之间的数据交互,尽量让有大量数据交换的服务在一起部署(比如部署在同一个机架内)。
### 单处理器的趋势
回到服务器的趋势上,有一个有趣的趋势就是:未来是单处理器的天下。
以前服务器采用多个处理器,是因为每个处理器上的内核数目有限。所以,如果我们希望一台服务器有更多的计算能力,只能采用更多的处理器。
但是现在这种情况也正在改变我们正进入高度集成的多核CPU时代也就是一个处理器上面有几十个内核越来越平常一个处理器就已经有足够强大的计算能力。比如随着每一代x86处理器的发布都会增加更多的内核和功能性能大大提高。
同时服务器的内存密度也在增加。现在的单处理器服务器能够置入的内存大小已经超过了以前服务器支持的内存密度。在很多生产环境中内存容量和内存带宽才是主要的性能瓶颈而不是CPU。因此在双处理器环境中CPU使用率往往很低。
综合以上因素,采用单个处理器的服务器,可以降低服务器硬件成本和软件许可证的成本,让各种硬件资源得到更加充分的使用。具体来说,我们在开发大型服务和程序的时候,可以尽量采用模块化的设计,比如分解成几个可以在单个处理器上运行的微服务。
## 总结
我们这一讲介绍了服务器设计的不同阶段讨论了工业界这几年的发展趋势包括CPU、内存、网络、磁盘等不同的服务器资源类型。
<img src="https://static001.geekbang.org/resource/image/8a/a8/8aad912aac2c4f56db93e30046c7b3a8.png" alt="">
这些趋势,对于我们去把握下一代服务器硬件会有帮助。我们开发的互联网服务,总归是要运行在这些服务器上面,了解了这些趋势,我们才能开发出性能优越,能充分利用硬件资源的应用程序和服务。
清朝的赵翼有一首诗说:“李杜诗篇万口传,至今已觉不新鲜。江山代有才人出,各领风骚数百年。”其实服务器的发展,又何尝不是如此呢?服务器的更新换代速度,虽然可能没有软件那么快,但是也是几乎每年都有很大变化的,每种服务器,也是“各领风骚一两年”。
## 思考题
你们公司的服务器有几种?是不是也是按照资源的大小,比如内存和存储的大小来划分的呢?
你如果在公司已经工作几年了,有没有感受到硬件更新换代的特点?如果你在开发程序和互联网服务,想一想怎么设计你的程序和服务才能充分利用这些特点呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,119 @@
<audio id="audio" title="31 | 规划部署数据中心要考虑哪些重要因素?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/44/2e/44e31abca21006204f5bd827c35a6d2e.mp3"></audio>
你好,我是庄振运。
上一讲我们讲了服务器的设计和部署,今天我们就来聊聊一个轻松的话题,一起来看看数据中心的秘密。
你们公司肯定有很多服务器。根据公司和服务的规模,可能有几十台甚至几万台服务器,大些的公司甚至会达到几百万台服务器。那么这么多的服务器都放在哪里呢?它们的家,就是数据中心。
你平时可能和服务器打交道比较多离“数据中心”就比较遥远。但是我可以肯定地说数据中心的知识与我们每个IT从业人员尤其是对于运维和性能工程的人员是非常相关的。
## 数据中心长什么样?
要讲数据中心,你可能会问数据中心到底长得什么样子?
<img src="https://static001.geekbang.org/resource/image/bb/45/bb828338451afc96bd56694422e25e45.png" alt="">
其实许多科幻片里都会有类似数据中心的场景出现。一个数据中心往往有好几个大楼每个大楼建筑物内部都有着很好的空调和通风设施。大楼内一般分成几个数据大厅Data Hall每个大厅都有一排排的机架中间留出足够的通道方便数据中心的技术人员进行维护。
<img src="https://static001.geekbang.org/resource/image/2b/0e/2b2ebaa87efdadd4f08ea5c2dfd05f0e.png" alt="">
这是机架的背面。你可以看到,机架的布线必须非常整齐,太乱的话,日常中是很难进行维护的。
<img src="https://static001.geekbang.org/resource/image/d5/ae/d580da6eb89261d102f994d8411542ae.png" alt="">
## 数据中心的规划和部署
对一个有全球用户的大互联网公司而言,它的容量通常也需要部署在全球范围内。运行公司各种服务的服务器,就放置在全球的数据中心里面。数据中心的建造成本很高,周期也较长,也有足够的复杂度,所以中小公司,往往会租赁别人的数据中心的空间,来放置自己的服务器,甚至是直接租赁服务器。
但对于大公司比如Google、Facebook、Amazon、Microsoft、阿里巴巴、腾讯等它们规模很大大到不能靠租赁来运营。而且因为规模大自己建造数据中心从经济上看更加划算。比如亚马逊就自己建造了很多数据中心。这些数据中心分布在全球各地如下图所示。
<img src="https://static001.geekbang.org/resource/image/a8/e7/a8c407cb167bf1bfe8e61f49109dfce7.png" alt="">
之所以要在全球范围内建造数据中心,主要是为了性能,而不是为了节省成本。因为一个服务如果有全球的用户,这些用户就都需要和公司提供的服务快速交互,比如上传照片,播放视频等等。对于在全球范围内运行服务的公司而言,“数据中心离用户距离近”就是唯一的选择。
那么公司需要建多少数据中心呢?这个数量问题是容易解决的,就是**根据公司的规模和实际的需求,并且适当的做一些预测和远景规划**,而这就是我们下一讲会专门讨论的容量规划和预测。
数据中心要建在哪里呢?表面上看,这个问题也容易回答。前面说过,全球建造数据中心的初衷,就是让它们靠近客户,那当然是根据客户的地点来建造数据中心了,哪里有客户就把数据中心建在哪里。
这样的回答,道理上没错,只是考虑得不够全面。数据中心的选址还需要考虑很多因素,比如电力供应的稳定性、自然灾害发生情况、社会稳定性、所在国法律、人力资源、容量供应、建造成本等等。
这些因素都很容易理解,不过有意思的是,“所在国法律”是其中非常重要的一个因素。
一个公司总会存储各种用户数据,而公司是需要保护用户隐私的。但是很多国家的法律要求,建造在本国的数据中心,必须允许本国政府访问这些用户数据。这就与公司应尽的职责构成了冲突。
我们有时候开玩笑说,放眼全球,还真找不到几个国家,能够在该国不用担心警察会突然破门而入,用枪指着头,强迫数据中心员工交出客户的数据。如果考虑诸多这些因素,地球虽大,却也难找到合适的地方建造数据中心。
数据中心建造还有一个特点就是建造周期很长从选址、规划一直到建造完成最少也需要好几年。所以为了让一个数据中心能够长时间有效使用建造一般也会刻意地分期完成。比如假设一个数据中心最终会建造6个大楼公司通常会分成3个阶段一次建造两个大楼。
## 数据中心内服务器的生命周期
我们上一讲讨论了服务器的设计。一种服务器设计完成后公司就可以部署了你也需要对这个部署过程有个了解。对于一台服务器它的生命周期经过4个阶段包括购买和运送、按服务分配、运行管理、最终退休。
**购买和运送**
公司给了预算并且确定数据中心有了放置的空间和计划,就可以订购服务器了。服务器一般都是按照机架的单位批量购买。之所以要提前做好购买计划,是因为从购买到运送,一般需要几个月的时间。
**服务分配**
服务器放置在预定义的机架位置后,需要给它们通电。通电后,机架会自动在资产跟踪系统中注册。然后预配操作将安装操作系统以及许多其他软件。新服务器安装完成后,一般是放到备用池等待分配。服务所有者会提出申请服务器的要求,然后负责分配的团队,按照要求将容量分配给他们。
**运行管理**
在服务器的整个生命周期中,服务器和机架可能都需要进行维护。维护可能需要置换有故障的磁盘、更换坏的主板、重新启动服务器、重新安装/升级/修补操作系统、运行修复软件、诊断软件等。
我们通常的服务器设计,实际上已经考虑到了很多可能的维护工作。例如,既然换出磁盘是常见的修复任务,那么服务器设计可以让更换磁盘非常容易,无需任何工具比如螺丝刀等。
**光荣退休**
机架和服务器在数据中心的使用寿命是多久呢通常约为3-4年。之后我们需要让它光荣退休将其从数据中心中移除擦除所有数据切碎磁盘并遵循所有硬件的报废流程。
为什么需要让服务器及时退休因为当超过使用寿命时它们的组件就容易发生故障。在处理维修单和更换这些组件方面会给数据中心的技术人员带来负担。同样各个组件的保修期也可能快过期甚至可能因为技术的发展置换部件已经很难买到了。更重要的是新的服务器替换旧的也可以提高性能和效率比如每一代的CPU都会比上一代更加强大。
## Facebook数据中心的网络部署
讲完了数据中心的架构和服务器的生命周期,我们再看看数据中心的网络部署。
数据中心内部的服务器之间以及用户和服务器之间有大量的数据交换这些对数据中心的网络部署也提出了很高的要求。为了能够更好地扩展现代数据中心的网络设置也在不断地演化。下面我就用Facebook来举例看看现代数据中心的情况。
Facebook的生产网络本身就是一个大型分布式系统包括边缘网络、骨干网和数据中心内部网络。Facebook的网络基础架构也在不断扩展。从Facebook到Internet的流量我们称其为“**机器到用户**”的流量,非常庞大,并且还在不断增加。但是,这种流量,相对于数据中心内部发生的“**机器到机器**”流量,就只是冰山一角了,后者是前者的百倍以上。而且这种流量的增长速度,几乎每年都增长一倍。由此,我们也可以看出数据中心内部网络的重要性。
我们公司以前的数据中心网络是使用集群Cluster构建的。集群是一个大型部署单元涉及数百个服务器机柜这些机柜的顶部TOR交换机聚集在一组大型的交换机上。但是这种以集群为中心的体系结构有很大的局限性。
所以我们的新一代数据中心网络设计就不是基于集群的也就是说不是按层次分配的集群系统。我们将网络分解为多个小的相同单元也就是服务器Pod而不是大型集群并在数据中心的所有Pod之间创建了统一的高性能网络连接。
这里的Pod只是我们新架构中的标准“**网络单元**”。每个Pod由一组称为设备交换器的四个设备提供服务从而可以根据需要进行扩展。当前的多数机架顶部交换机具有4个40G上行链路为被连接的服务器提供160G的总带宽容量。下图就展示了一个Pod和48个机架的网络连接。
<img src="https://static001.geekbang.org/resource/image/47/06/471217886c8df796e77ead47ee6ef306.png" alt="">
每个Pod的大小都一样都是48个机柜所以只需要基本的中型交换机就可以支持。对于机柜交换机的每个下行链路端口我们在Pod的交换矩阵交换机上保留相同数量的上行链路容量这使我们能够**将网络性能扩展到在统计上无阻塞的水平**。
为了实现大楼范围的连通性我们数据中心内部创建了四个独立的骨干交换机“平面”每个平面可以最多扩展48个独立设备。
每个Pod的各个交换矩阵都连接到其本地平面内的主干交换机共同构成一个模块化的网络拓扑能够容纳成千上万个连接10G的服务器。如下图所示
<img src="https://static001.geekbang.org/resource/image/ad/6b/add2ab671fbbbae972439e8510eb076b.png" alt="">
对于外部连接,我们用光纤网络配备了数量灵活的**边缘Pod**。每个边缘Pod能够为骨干网和数据中心站点上的后端提供很多Tbps的带宽并且可扩展到100G和更高的端口速度。
这种高度模块化的设计,使我们能够在一个简单统一的框架内,快速扩展任何维度的容量。
当我们需要更多计算能力时就简单地添加服务器Pod。当我们需要更多的内部网络容量时就可以在所有平面上添加骨干交换机。当我们需要更多的连接时我们可以在现有边缘交换机上添加边缘Pod或扩展上行链路。
## 总结
我们今天讨论了数据中心这个重要的容量载体,它的内部结构、网络设置、服务器的生命周期,以及规划数据中心的一些考虑因素。
<img src="https://static001.geekbang.org/resource/image/1c/58/1c392088ad67ec3085616c50f4e59958.png" alt="">
数据中心可以说是服务器的“家”,也是我们的程序最终部署的地方。唐代诗人王建说:“今夜月明人尽望,不知秋思落谁家。”我们开发程序和部署服务,最好要了解数据中心的知识和架构,因为各种互联网服务,总归是需要数据中心的服务器和网络来支撑的。了解数据中心的配置,对我们服务的开发和部署是很有用的。
对一些大规模的服务,数据中心的网络或者服务器资源,可能会成为性能和业务发展的瓶颈,所以我们也需要不断优化,并提前规划数据中心。尤其是现在的互联网服务,往往有很大的数据量,所以数据中心网络的扩展性尤其重要。
## 思考题
你们公司的服务器一定也在数据中心里,这些数据中心的地理位置在哪里?地理因素对你的互联网服务的性能比如端到端延迟有什么影响?
你们公司用的数据中心是自己建造的还是租赁的?公司对数据中心的运营成本有什么要求和策略?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,131 @@
<audio id="audio" title="32 | 服务的容量规划:怎样才能做到有备无患?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b0/a7/b0f7b0e0cc86e9ce89d547abcce9e8a7.mp3"></audio>
你好,我是庄振运。
今天我们来讨论一下在公司运营方面很重要的**容量规划**。容量规划,就是根据互联网服务的需求和公司发展目标,而决定容量的供应能力的过程。
光说概念你可能不太明白,不过你可以这么理解,容量规划是为了回答一系列和公司业务运营有关的重要问题而产生的:
- 单台服务器的最大处理能力是多少?
- 未来半年公司还会有多少可用容量?
- 春节前如果要进行一次大促,当前的容量能不能支撑?
- ……
今天我先讲容量规划的目标和基本过程,然后再一个一个地讲容量规划的六大挑战。
我们前面也大概提到过,容量规划的目的有两方面。一方面是为了保证公司的正常运营和业务增长,及时地提供足够的容量,来满足未来所需。另一方面,也是希望空闲的容量越少越好,因为每一台空闲的服务器,都消耗公司的运营成本。
现代的互联网服务规模的扩展,主要是用“水平扩展”而不是“垂直扩展”(通过增加服务器的数量和网络的带宽来提供更多的容量,而不是通过升级服务器)。相对于垂直扩展,水平扩展的优点显而易见,就是成本相对较低、调整容易、扩展性好。
## 怎么做容量规划?
整个容量规划的过程分为几部分:首先是容量测试来决定单位容量的负载能力;同时确定公司的业务增长需求,并且获取公司的运营预算。然后集成其他的考虑因素(包括时间、地域、灾难恢复等),做出合理的规划和决策。最后根据决策结果,进行容量订购。
为了方便把握,我把这几个因素模块整合在一起,如下图所示,我们一个模块一个模块地讲。
<img src="https://static001.geekbang.org/resource/image/ce/60/ce9ee8a71387400727ec1b1b0abeff60.png" alt="">
### 容量测试决定单位容量的能力
容量测试可以决定单位容量的能力。容量规划的基础,就是确定单位容量,比如一台服务器系统的处理能力。而进行容量测试,一般是让服务器处于最大负载状态,或某项性能指标达到所能接受的最大阈值下,对请求的最大处理能力。
容量测试是性能测试里的一种测试方法,它的目的就是**测量系统的最大容量**,为系统扩容、性能优化提供参考,从而达到节省成本投入,提高资源利用率的目的。
容量测试大致分为两种:线上测试和线下测试。我们在[第25讲](https://time.geekbang.org/column/article/191598)专门讲了线上测试,这里快速地复习一下。线上测试相对比较准确,因为是用真实的生产流量负载。线下测试,则比较容易进行。但是你要特别注意测试过程和结果的代表性,就是它能否真实客观地反映生产环境中的数据。尤其是测试环境的配置和驱动数据,一定要和线上的生产环境尽量保持一致。
### 预测未来需要的容量需求
要做好容量规划,除了单位容量的处理能力,另外一个必须要知道的输入,是服务和业务的需求。通俗点说,就是期望达到的用户数和数据流量大小。比如一个前端服务,要决定明年的容量需求,就需要确定明年的预期用户数目。
对于未来的容量需求的预测,主要是参考历史的相关数据,然后建立合适的模型。比如我们在[第26讲](https://time.geekbang.org/column/article/192316)提到的针对时间序列的ARIMA模型就是一个不错的模型。如果针对的业务已经有足够长的历史那么过去的数据就很值得参考。
反之,如果一个业务还没有上线,或者上线时间很短,那么历史数据就不是很可靠。如果你面对的是这种情况,有两种解决方法:一是可以类比其他相似的服务,不管是公司内部的还是业界的,都可以拿来参考。二是需要借助一些主观的推测。当然这种主观推测也不能凭空乱猜,而是要有理有据。
容量规划时,公司的业务运营预算也要考虑进去。一个正常运行的公司,运营成本总是有限的(每个业务都有预算定额)。如果对一个业务预期的容量需求,超过已经设定的预算定额,那么,你要么是把容量需求降低到预算定额,要么就得向公司申请增加预算。
### 考虑各种限制因素和挑战
另外还有一点要注意的是,未来容量的需求预测总会和实际情况有差距,也就是说,总是有不确定的因素。
所以我们一般会根据估计的信心指数对预测结果做几种不同的预测。比如90%的信心和50%的信心。这里信心指数越大,容量数值也越大,就是说越有把握满足需求。举个例子,比如我们要估计明年一个服务的容量需求,可能得出结论:
- 如果只需要保证50%的信心1000台服务器就够了
- 要保证90%的信心则需要1200台服务器。
但是无论我们多么努力地去预测总有些因素没有考虑到从而导致实际情况和预测不吻合。所以我们一般都要对容量的需求预测加上一个冗余Buffer我个人的经验是10%的Buffer。比如我们的模型预计需要1000台服务器那就加上100台的Buffer也就是需要1100台服务器。
### 及时和实时地调整
也正因为容量的规划和使用过程中,有很多的不确定因素,随着时间的推移,这些不确定因素慢慢就会变成确定的因素。比如,在一个服务还没有上线的时候,做容量规划非常困难,因为不能准确地预测用户数量。但是随着服务的上线,开始积累用户量,对未来用户数量的预测就会变得越来越容易。
所以,容量的预测和规划,也是一个持续和不断迭代的过程。开始的时候靠模型,靠历史数据和一些直觉来做大体预测。然后,定期地和实际的数据做对比,从而调整和纠正以前的预测。
## 容量规划的挑战在哪?
容量规划和预测时,除了前面讲的步骤和因素外,还有其他几个限制因素和挑战值得考虑。我总结了六个这样的挑战,包括地域的限制、服务器种类的区分、时间和季节性等等。
### 容量规划不仅仅是钱的问题
第一个挑战就是,容量规划不仅仅是钱的问题。你可能听说过:“能用钱解决的问题,都不是问题。”
但容量的供应问题,很多时候还真不是砸钱就能解决。
为什么有时候钱不能解决容量问题呢?如果公司的运营规模很大的话,需要的容量供应也就很大。在很多情况下,市场上能够找到的数据中心很有限,所以即使公司愿意投入很多资金,也不一定就能在需要的地点和需要的时间租赁到足够的容量。
有的公司自己建造数据中心,那就面临更多新的的问题和挑战,尤其是时间方面。建立数据中心的交货时间至少是两、三年。数据中心建造和扩建,需要寻找可用的建筑商和建筑工人,但是找到足够的合格建筑商和工人并非易事。因此,数据中心的建设并不像把钱扔在问题上那么简单,需要提前很久就规划和考虑,要未雨绸缪。
### 数据中心的地域因素
第二个挑战是地域因素。当公司的业务需要部署服务器在多个数据中心时,每个数据中心都需要做相应的容量规划。也就是说,仅仅只有预测和规划一个总体的容量是不够的,还需要具体到每个数据中心的内部。
数据中心往往分布在不同的地域它们内部的服务器并不能在数据中心之间随意置换。为什么呢因为我们的业务也要求服务器尽量地离客户近些从而降低网络延迟提高带宽和可靠性。像Facebook、Google、腾讯、阿里这样的公司尤其是如此它们往往在全球有很多数据中心而且用户的规模很大如果容量规划做不到数据中心这个层次是肯定不行的。
### 服务器种类的因素
下一个挑战是服务器种类的挑战。公司往往会有不同种类的服务器,以便针对特定类型的工作负载进行量身定制。例如,有专门针对内存密集型工作负载的大内存服务器。当有多种服务器可选的时候,容量规划就需要选择最合适、最经济的服务器类型,这样可以降低成本并改善服务质量。
对于一种具体的服务器而言它的性能也是随着时间的推进不断改善的。比如一种计算密集型的服务器虽然基本的机架设计不会改变但是服务器使用的CPU还是会一直更新换代的一般每年都会有新的CPU型号。
所以在做容量规划时也要把这个因素考虑在内。比如要支撑一个服务如果用现在的CPU型号预测明年需要一千台服务器。可是同时明年的CPU会升级性能提高了10%。那么考虑到这点可能只要900台服务器就够了。
### 时间和季节的因素
容量规划也要充分考虑时间和季节的因素。在[第26讲](https://time.geekbang.org/column/article/192316)里,我们一起看过正常生产环境的负载变化。对大多数互联网服务来讲,在一天内,每天的上午上班时间的负载较大;在一周内,工作日的业务负载比较大。同时,当经历节日时,比如春节、国庆等,也要注意流量的预期变化。
对于面向普通用户的社交网站比如Facebook每当新年、圣诞节时候有些业务的用户使用量会好几倍的增长。这些业务包括照片和视频上传朋友分享等。所以我们的容量规划过程会充分考虑到这些时间的业务需求提前部署足够的容量来迎接这些峰值的服务流量。
### 不同的灾难恢复场景
还有为了保证业务的可靠性灾难恢复DRDisaster Recover场景是必须考虑的。
灾难恢复准备意味着公司的容量可以容忍某个容量单位的损失。互联网服务其实有很多种不同的DR场景最常见的是数据中心的DR也就是说假如一个数据中心因为天灾人祸的原因完全不能访问其余的容量如何支撑整个互联网服务的正常运作。
灾难怎样影响服务性能呢损失的容量单位会导致其他容量单位的负载增加。负载的增加量取决于丢失单位在整个容量系统中的比例。具体来说假设丢失的容量单位平时承担的负载百分比为x那么在丢失该容量单位之后所有其他单位的流量将增加[1 /1-x-1] * 100
为了帮助你理解我先举个简单例子。假设一个服务有两个数据中心支持这两个数据中心大小是同样的都分别承担50%的服务流量。那么如果一个数据中心不能用了另外的数据中心就需要承担2倍的服务流量也就是1/1-50%=2。
再举个稍微复杂的例子。考虑以下情形其中A、B、C的三个容量单位分别占流量的40、40和20。如果单位A丢失那么单位B和C的流量将增加1 /1-0.4-1 = 67。如果C单位丢失则A和B单位的流量将增加1 /1-0.2-1 = 25
### 不同服务之间的互相影响
最后,互联网公司的业务,往往会分解成很多的服务。面对外部客户的是前端服务;还有很多后端和内部的服务来支撑前端服务。所以,如果前端服务的负载有变化,后端服务也会受到影响。要预测后端服务的容量需求,也必须充分考虑前端服务,以及其他上下游服务的影响。
一般来讲每层服务调用都会有一定的负载均衡机制在规划每层服务的容量需求以及每层服务的不同容量单元的容量需求时就要了解这些负载均衡机制从而确定相关因子。比如如果前端服务负载增加100则下游服务预计会增加多少负载如果某个下游服务的负载增加也是100则相关因子为1。
每个服务的总体负载确定后,还要考虑具体到这个服务在不同数据中心的分配,也就是需要确定每个数据中心分别分配多少。
## 总结
我们今天讨论了容量规划的过程和需要注意的地方。容量规划这个工作非常重要,正如清朝人陈澹然讲的:“不谋万世者,不足谋一时;不谋全局者,不足谋一域”。如果规划做不好,公司业务一定会受到严重的影响。要不就是没有容量可用,要不就是容量太多造成浪费。
<img src="https://static001.geekbang.org/resource/image/42/5f/424a7025a0cfcc5b8113939f1efd6e5f.png" alt="">
容量规划的工作并不好做,你从我说的几大挑战就看得出。所以需要对公司的业务特点和服务架构非常熟悉,并不断及时地调整,才能尽可能地达到预定的目标。
## 思考题
你们公司或者你的部门的业务,需要什么样的容量来支持呢?比如何种服务器?
对于容量规划,你们部门对未来半年甚至一年的容量要求是多少?是哪个部门帮你们做容量规划,他们是怎样确保容量规划的准确性呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,147 @@
<audio id="audio" title="33 | 服务效率提升:如何降低公司运营成本?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a7/2c/a7ad766716b6f65f29b7ef13b6f0422c.mp3"></audio>
你好,我是庄振运。
我们都知道,支持大量用户的互联网公司,通常会部署相当规模的系统容量来运行各种服务。
如果你想要有效地运行业务,就应使业务的**容量需求**和**容量供应**尽可能地相等。为什么这么说呢?如果容量供应不能满足需求,那么部分业务将​​因容量不足,不能部署或扩展。如果容量供应过多,那么公司基础设施的效率就降低了。
服务效率与业务的容量和公司预算直接相关。因此,**提高服务效率**和**适当地预测容量需求**,对于公司的持续成功至关重要。
那么如何才能降低公司的服务容量需求呢?这就需要**运行的服务尽量高效**,提高服务效率将会帮助降低容量的需求。
今天我就为你介绍八种提升服务效率的途径和相关的生产经验,以及提升执行服务效率的原则,希望对你有所启发。
## 服务效率提升的途径和类别
当各个服务团队的领导和技术骨干下定决心,要花时间在服务效率的时候,经常提出的问题是:怎么样做才能提高服务效率呢?
要回答这个问题,你就必须先搞清楚一个问题,就是——什么类型的工作才是提升服务效率的工作?
我多年来帮助很多不同的服务提升了服务效率;在这个过程中我逐渐意识到,一个公司的服务千差万别,可以提升效率的方式也是多种多样。但核心的一点是:任何能够让这个服务降低容量需求的工作,都是服务效率提升的工作。
我把各种各样的服务效率提升工作分为八类软件效率Software Efficiency、服务架构效率Service Architecture Efficiency、服务部署效率Service Deployment Efficiency、跨平台效率Cross-platform Efficiency、硬件效率Hardware Efficiency、容量组织效率Capacity Orgnization Efficiency、容量资源回收Capacity Resource Reclamation、用户效率User Efficiency等。
这八种效率大体上分为两组:**软件服务**和**非软件容量**。
<img src="https://static001.geekbang.org/resource/image/5f/e4/5fc0143c23b5a34e8742e761ec547ee4.png" alt="">
对每一种服务效率,我先讲基本概念,然后再用具体例子来诠释,希望可以给你些启发。
### 1.软件效率
服务的软件程序实现千差万别,但不管哪种实现,它总是可以优化的,比如通过采用更好的数据结构和语言库等等。这方面的效率提升,都可以归类为软件效率。
软件方面的效率提升比较直白就是把程序的性能提高。比如减少CPU和内存的使用等等这方面我们这个专栏前面讲了很多多数内容都可以归到这方面。
### 2.服务架构效率
对一种服务而言,如果能够改进服务的实现架构,比如重新整合了内部的微服务,从而变得更高效,那么就是服务架构的效率提升。
服务架构的效率又可以分为两种:一种是单独的一个服务,通过进行服务设计的优化来提高效率;另外一种就是通过合理借助其他服务来优化。
比如一个数据库服务,如果有很多的查询请求,那么采用另外一个缓存服务,就或许可以大幅度地降低数据库服务的资源消耗。如果两个服务(数据库和缓存)的资源使用总和,还是小于仅仅使用数据库(而没有缓存服务)的资源使用,那么这两种服务的结合就是一种更加高效的服务架构。
### 3.服务部署效率
一个服务的软件程序总是要部署到硬件容量上。生产实践中如何部署软件也很重要也会对服务效率产生影响。比如采用什么操作系统进行什么样的系统和软件配置要不要采用NUMA绑定等。这些方面的优化都可以算是服务部署方面的效率提升。
还记得我们在[第22讲](https://time.geekbang.org/column/article/189200)讲过的“使用内存大页面来装载程序的热文本区域”吗这其实就是一项服务部署的优化就是通过提高系统和程序iTLB命中率从而达到更高的服务效率。
### 4.跨平台效率
如果一个公司(尤其是后端)同时提供几种服务平台,提供的功能有重合,而且服务效率不同,那么用户可以迁移到效率更高的服务上去。这种迁移就是跨平台效率提升。
一个互联网公司,随着时间的推移和规模的增长,内部往往有很多种服务提供重叠的功能。一个明显的例子是数据存储,经常有很多服务都可以提供数据存储的功能。虽然每种存储服务提供的功能并不完全一样,但对于一个要使用存储服务的用户来说,经常同时会有好几个选择。
这种情况下,这个需要存储的用户,就可以考虑每种存储服务的服务效率;在满足所需功能的前提下,选择一种高效的服务,会帮助公司降低成本。
对于一个已经在使用某种服务的用户而言,可以重新考虑各种可用服务的效率;如果另外一种服务更加高效,就可以考虑迁移过去。这种操作就是**跨平台的迁移**,其结果也是能够提升公司的服务效率的。
我最近几年做过很多这方面的优化。比如曾经把一个用户的数据从一个低效的存储服务,整体转移到另外一个高效的存储服务,从而大幅度地降低了公司的容量和运营成本,节省了大约几千万美元。重要的是,用户的端到端性能并没有收到影响。
### 5.硬件效率
一个服务会使用各种容量资源比如CPU。所以根据资源使用最大瓶颈的不同采用不同种的服务器硬件也会导致不同的服务效率。比如假设一个服务的存储是最大瓶颈时那么采用相对较大存储的服务器结果就会比较高效。
我就提升过一个存储服务的硬件效率。该服务受到存储空间的限制,并且近年来增长迅速。我评估后得出的结论是,如果这个服务继续快速地增长,公司很快就没有足够的容量来提供给它。
所以我们决定,通过提升服务效率来减少服务的容量需求。在检查了所有效率提升的方式后,我决定首先提升硬件效率。 具体来说,我们采用了一种新型服务器,这种服务器相对旧的服务器而言,有较大存储容量,并逐步部署。
下图分别显示了服务的容量规模(也就是服务器的数目),和已部署的总存储容量。
<img src="https://static001.geekbang.org/resource/image/38/05/3897e6d4f7862781ec80e4b26e281905.png" alt="">
<img src="https://static001.geekbang.org/resource/image/7d/2e/7de61e4513c9eb1891493af8647d312e.png" alt="">
在20天的时间内我们将服务迁移到新的硬件类型服务器的数目减少了5而存储容量增加了11。值得一提的是新旧服务器的单位服务器成本差别不大所以整个服务的效率得到了很好的提升。
### 6.容量组织效率
一个互联网服务在使用数据中心提供的容量时候,一般都是用某种方式把容量(比如服务器)组织在一起,形成一个容量单位。比如把服务器分组、组成集群等。这种容量组织的方式,也可以根据服务的特点,进行调整优化,从而变得更高效,这就是容量组织效率。
一个规模很大的服务可能会需要很多容量。我曾经合作的一个后台服务,就使用了几十万台服务器。这么多服务器经常会分成几个容量单元,这样做当然有利有弊,但都是经过各种考虑的决定。有时候服务容量的分割也有历史的原因,比如一开始使用一个容量单元,但是随着服务的规模不断扩大,就建立了一个又一个的容量单元。
分成几个容量单元的一个好处是降低了容量操作的复杂度。不过也有坏处其中之一就是资源效率使用率不会特别高因为不同容量之间的资源不能互补。比如两个容量单元每个有一千台服务器。一个容量单元是CPU吃紧另外一个容量单元是内存吃紧。如果二者合并成一个容量单元或许只需要一千五百台服务器就够了。
所以适度地优化容量组织,合并容量单元,就可以降低容量的需求,提高容量的效率。
### 7.容量资源回收
一个服务所使用的容量,随着时间的变化,有些容量(比如一些服务器)就会处于空闲状态。如果及时回收,从而进行容量的再利用,也可以提升服务效率。
一个服务从诞生到成熟,会不断地演化;表现在对容量的需求方面,也是不断变化的。可能有一段时期,部署的容量恰好满足服务的需求,容量的效率很高。但是过一段时间,服务的需求和资源使用特征发送了变化,有些容量和相对应的资源使用不再使用。这种情况下就需要进行容量和资源回收。
这方面一个最直白的表现,就是可能有些容量处在空闲状态。举几个例子,假如有些服务器已经不能运行此种服务了,那么就把这些服务器回收,给其他合适的服务用。又假如一个服务对存储资源的使用下降了,那么对应的存储资源就可以减少,分给别的服务用。
### 8.用户效率
使用服务的用户,如果采用合理的方式,也可以帮助提升所使用服务的效率。这方面的效率提升就是用户效率。
用户在使用服务时,可以尽量做到高效使用。我们曾经帮助一个存储服务提升效率,通过和客户合作,大幅度降低了客户的存储数据量。具体来说,这个客户以前是将数据对象的本来数据和一大堆元数据,都放到了存储服务上,这导致了相当大的物理数据存储。
我们是怎么提高用户效率的呢我们首先分析了客户的业务逻辑和数据使用场景意识到客户存储的目的是判断数据对象是否已经更改。我们先将存储的数据分为两部分然后分别存储对象的哈希例如MD5而不是对象本身。另外我们把数据的TTL生存时间缩短并在对象哈希中定期强制清除陈旧数据。
这样的优化工作取得了很好的效果节省了93的存储空间。具体来说把客户数据大小从2.7PB降到了0.2PB,如下图所示。
<img src="https://static001.geekbang.org/resource/image/55/40/555309613ae7557ddc09718e23a5bf40.png" alt="">
## 执行服务效率提升的原则
除了前面说过的八类服务效率提升方法,在提高服务效率的生产实践中,我也获得了许多执行方面的经验。这些经验对于如何在部门之间协调,有效地执行效率提升,完成预定的效率提升目标等方面,有比较好的指导作用,希望能对你有所帮助。
1.明确的所有权和承诺。
根据公司的规模,公司可能会有许多需要提高效率的服务。对于每个这样的服务,每个效率提升工作都需要有明确的拥有者团队,并且需要得到团队的工作承诺。如果不能明确所有者,或者所有者不能承诺完成的时间,那么最后经常会互相扯皮,互相指责,工作失败。
2.以效率目标为导向的计划和执行。
对于每个服务及其效率组件要定期设定要实现的效率目标。例如针对存储服务的效率目标可以是“将明年的存储空间利用率从30提高到40”。但是注意目标设定的频率和大小必须适应服务的特征和团队的工作特点。
3.为每个效率组件部署检测工具。
你需要开发一定的工具,来监视整个服务和每个组件的效率水平,以便快速检测到异常。对于异常的效率降低,这些工具必须有自动触发或警报功能。有效的工具,能帮助你及时发现问题,并立即采取行动。
4.紧密的跨团队协作。
鉴于当今业务和服务的复杂性,许多服务效率的提高,都需要多个团队共同合作。例如,某个服务可能依赖其他服务,并且该服务的效率提高,也可能需要所依赖服务团队的协作。
5.公司范围内的协调。
尽管每个主要的独立服务,都可以通过采用我们提出的框架来独立工作,但也需要在公司范围内进行协调,因为公司需要保证总体业务的需求。
## 总结
互联网公司业务复杂,规模庞大,需要有大量的基础设施和容量去支撑。我们这一讲讨论了如何提升服务效率,降低容量需求,从而节省公司成本。
<img src="https://static001.geekbang.org/resource/image/c3/35/c3e45332a08bcd75e5963ed5aec1e535.png" alt="">
通常一个互联网服务的规模是不断增长的。开始的时候,或许你不在乎它的服务效率和系统容量,但是很快你就需要重视了。所以,你最好未雨绸缪,把相关工作定义好。正如唐朝诗人杜荀鹤的一首《小松》,有两句说:“时人不识凌云木,直待凌云始道高。”如果公司业务发展快,它的服务规模增长也很快,那么服务效率提升的工作就会越发重要。
我通过多年的容量规划和服务效率提升工作,积累了大量的服务效率优化的知识,今天的分享,希望能帮你开拓思路,提供经验。
## 思考题
提升一个互联网服务的效率有很多种方式,其中一个是服务软件的效率。我们前面几讲提到了很多性能优化的工作。你能举出几个可以归类于软件效率提升的例子吗? 提示一下,优化代码算不算是提升服务软件的效率?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,143 @@
<audio id="audio" title="34 | 服务需求控制管理:每种需求都是必需的吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/30/94/30e50e5429e2c5abdcf5fccd4a784d94.mp3"></audio>
你好,我是庄振运。
上一讲,我们探讨了如何通过提高互联网服务的效率,降低对公司服务容量的要求。今天我们讨论另一个有效手段——互联网服务的内部需求控制管理。
互联网公司内部往往有很多后端服务比如Key-Value也就是键值数据库服务。公司内部对这些后端服务会有很多使用的需求。需求自然有合理的也有不是很合理的。我们要做的就是确保那些合理的需求能够使用这些后端服务并且将那些不合理的需求尽量挡回去或者让提出需求的内部客户提高容量使用效率。
你不要小看了互联网服务的需求管理,它对保证容量供给和服务质量很重要。因为公司给一个服务的容量总是有限的,如果不能控制需求,那么有限的容量很快就会被用光,从而影响公司的正常运行。
今天我先讲讲服务需求控制管理的总体目标,然后分享一整套大规模需求控制管理的方法给你。这套方法是我在多年实践中总结出来的,实践证明它是行之有效的。
## 为什么要做服务需求控制?
想做好大规模需求控制的管理,你就需要先宏观地弄清楚服务需求控制的目标。
一个公司要想成功、有效地运行互联网业务,它就应该尽量让各个内部服务的容量需求和供应尽可能接近。
<img src="https://static001.geekbang.org/resource/image/cc/e0/cce79df848392a1b392e40a50ec85ce0.png" alt="">
如上图所示,左边是公司业务增长,导致容量需求越来越大;右边是公司预算成本有限,容量供应有限。
我们总是希望这两者能够匹配,越接近越好。如果容量供应不能满足服务需求,那么会导致部分业务的发展受到阻碍。如果容量供应过量,就会有闲置容量,造成浪费,从而降低公司基础架构的效率。
要降低容量需求有两种方法,一种就是[第33讲](https://time.geekbang.org/column/article/196699)我们谈过的**提高服务效率**,另外一种就是这一讲要说的**控制需求**。这二者最好互相配合,共同努力。
## 需求控制管理难在哪里?
现在我们就来看看需求控制管理要怎么做。不过,要理解这套方法为什么是这样的,又为什么可以说它是“行之有效”的,你需要先了解做需求控制有哪些挑战。我总结一下,主要有下面几个方面的挑战:
一是很多公司,尤其是大公司,**服务的规模会越来越大**。这个规模表现在很多方面,包括服务数量很多,使用服务的客户也很多。注意这里的客户不仅仅是外部客户,更是内部客户。这就要求我们的工作不能仅靠一个人或者一个团队,必须是分布式的。
二是服务的内部**客户的容量使用情况很不一样**。有的客户对容量使用量很小,增长也不快;有的使用量就很大,或者飞速增长。这就要求我们,在宏观上需要对客户的使用设置一定的配额,否则一个服务的容量,一旦被某些客户用光,其他客户的性能就会非常差。
三是内部**客户的资源使用可能不稳定**。客户使用的不稳定,会导致特别大的峰值出现,对服务的稳定性造成影响。所以,我们必须在微观上,实时监测客户的资源使用情况,一旦发现超出期望的效率衰退,就需要及时通知客户,让他们尽快解决。
四是**停止服务的代价高**。一个内部客户一旦开始使用一个服务,就很难让人家离开。所以必须严把入口关,不要随意地让服务上马。
五是资源使用效率的提升,必须**要经常与用户沟通**,和用户一起不断地提高使用效率,同时也需要开发一些工具来帮助用户。
## 需求控制框架如何构成?
我把整个需求控制的框架按照逻辑,分成了五大模块。如下图所示,我们一个一个地来展开阐述。
<img src="https://static001.geekbang.org/resource/image/b8/db/b87fdd22bda5fc7de9045f35461c98db.png" alt="">
### 服务入口控制:需求审核
第一个模块是服务入口控制。一个服务的需求在开始使用服务之前,必须进行上马前的审核。这个审核,需要统筹考虑需求优先级、和什么产品相关、对公司业务的影响、需求量的大小和轻重缓急、是不是已经计划好的等很多因素。
每个服务需求的请求,要求以任务的形式进行,审核人员要定期的处理这些任务。为了帮助审核人员做出适当的决定,请求者最好提供足够的信息。一般来说,按照刚刚提到的几个方面来准备就可以,越详细越好。
出于平衡利益的考量,审核人员的组成十分多样,至少要包括服务开发团队、运维团队,以及提供容量的团队。为什么这样设置呢?给你举个例子,服务的开发团队,可能希望服务的内部客户越多越好,因为客户多的话,这个服务就变得越来越重要。服务的运维团队,可能更关心服务和系统的稳定性,不要出现各种性能和可靠性问题。而提供容量的团队为了保证容量供应,可能不希望用户有太多的需求。
经过审核后,每个请求会产生几种不同的结果:或批准,或拒绝,或需要客户提供更多信息,或者要求客户降低请求的大小,又或者推迟这个请求。
之所以把这个审核放在第一位,是因为我在实践中发现,很多的客户请求都是“拍脑袋”想出来的,其实根本不合理,而且请求的大小,往往是狮子大开口。当你审核一个“需要几千台服务器容量”的请求后,你必然要与提出者沟通。而在很多情况下,他们的请求在沟通后降到了只需要几台服务器。
这个“沟通”也不难,你只要对每个请求多问几句,比如这三个“劝退”问题:
- 你为什么有这么大的需求?
- 你能讲讲你的理由吗?
- 能不能推迟一下这个请求?
差不多有一半客户在答完这三个问题后会降低请求的大小,甚至很多会直接取消请求,说:“仔细考虑后,我们其实不需要这个请求。”
### 分布式的需求控制管理和效率提升
第二个模块是分布式的需求控制管理和效率提升。
为了适应大规模服务的场景需求控制的方法必须是分布式的。我们一般是把整个公司的各种使用分成差不多10到15个产品组比如按照产品种类来分。这样我们就可以针对这些产品组分别合作让每个产品组设置特定人员和团队来和我们合作。
为什么这么做呢?因为只有这样,我们的工作才能合理扩展。
我曾经为一个互联网服务做过三年的需求控制,学到了很多经验和教训。这个服务有几千个内部客户,如果只有我一个人和所有的客户打交道,根本行不通,累死也做不过来。
更重要的是,每个产品组的内部人员,其实才是最适合审核本组需求的人,因为他们对这些请求最清楚。这个请求到底需不需要?请求的大小到底合不合适?由他们来做审核,远远比我做得好。
**产品组级别的审核**,本质上是一种有效的,可扩展的分布式模型。而且,除了需求控制和审核,还有其他一些类型的工作,也更适合在产品组级别执行。比如下面我们会提到的配额限制等。
采用这个模式,所有的服务需求,都会先确定是哪个产品组,然后将需求任务分配给产品组,让他们进行产品组级别的审核。产品组的审核决定也不是随意决定的,同样要基于几个因素,包括请求的优先级(例如功能的重要性)、增长配额以及产品组内下层团队之间的协作等。
### 宏观需求增长控制:设置增长配额
第三个模块是在宏观上控制增长,也就是设置增长限额和配额。我通过四个问题来阐述。
1.在哪个级别设置增长配额?
我刚刚讲了,每个产品组会对自己内部的服务需求进行审核。如果没有产品组级别的配额限制,产品组有可能胡乱批准他们自己的请求,从而滥用这个服务。如果对他们的服务需求,加上配额限制,事情就好办了。所以,**增长配额必须设置在产品组的级别**。
另外,在产品组下面的级别,也可以设置配额。因为有的产品组很大,内部也有几十甚至几百个团队,所以每个团队也需要设置相关的增长配额。这也有利于产品组内部的协调。这里的要求就是,产品组内部的配额总和,必须不能超过产品组级别的总配额。
2.怎样设置增长配额的频率?
这个设置可以是一年、半年,或者是一个季度。太频繁会增加工作负担,但是时间太长也有弊端。所以,我们一般都是半年或者一年设置一次。如果是一年,一般是年初设定一年的增长配额。
3.如何设置增长配额的大小?
每个产品组的实际配额大小也需要考虑几个因素,比如产品的特征、重要性、预期增长、产品发布计划、内部客户端效率等等。对每个产品组的情况都了解后,就可以根据实际情况和业务影响,来平衡各个产品组的配额。当然前提是,总体配额不超过总容量。
4.如何及时地控制需求的增长,保证不超过配额?
我们需要**搭建一些工具和配额使用情况面板**。这样就可以每天监控每个产品组的资源使用情况并将使用情况与增长配额进行比较。如果产品组的资源使用量连续7天比每日的配额快我们就通知产品组并创建对应的任务。
任务的优先级别在一开始要设置成最低的。但是如果产品组的使用量已经超过了年度总增长配额那么你可以将任务更改为高优先级来解决。如果有连续3周以上的实际使用量都超过了年度总增长配额并且产品组没有采取任何措施那么对应的任务就会提升优先级到最高级。
### 微观增长控制:资源使用的实时监测
第四个模块是在微观上进行增长控制,就是实时地进行资源使用检测。
产品组对一个互联网服务的使用有很多实例。虽然每个实例的情况都不同,需要区别对待,但不会变的核心点是:每个使用服务的实例,都需要**进行实时的、微观层面的监测**,以确保它的效率不衰退。
一旦你发现实例的效率衰退,就需要及时地通知这个实例的所有者。一般是以任务的形式发给客户,让客户诊断和根因,然后解决。
这个部分,和刚刚讲过的宏观层面的配额限制是互相呼应的。只有在微观增长方面检测和控制得好,才能更容易保证配额不超标。
### 资源使用效率:不断提升
最后一个模块,就是不断提升资源使用效率。最好的措施,就是我们上一讲说过的提高服务效率。这里的服务效率,主要是客户端的效率,目的是满足内部客户基本要求的情况下,尽可能地降低资源量。
但我们实际中碰到的一个难点是内部客户通常只对自己的指标比较了解比如QPS或者数据的大小。既然我们的最终目的是节省成本那么就需要让客户直观地了解指标和成本的对应关系比如1TB的数据相当于多少钱。
有了指标和成本的对应关系产品组就能方便地按照任务的重要性来安排工作、调配人员。一个能节省1千万元的任务当然比只能节省1百万元的任务更重要。
为此我们针对每个互联网服务都提出了一种易于计算和理解的对应指标。比如对某个存储服务我们就考虑了数据大小和QPS这两个输入指标然后根据整个服务的成本来做映射。一般来讲为了反映最新的服务状态这个指标是需要定期更新的例如服务增长、硬件效率、服务效率等。
## 总结
这一讲我们讨论了容量控制的管理方法,这套方法可以有效地降低服务的容量需求,也就节省了公司的运营成本。实际执行这条管理方法的核心是:在不影响公司正常发展和内部客户合理需求的条件下,尽量降低内部客户对每个互联网服务的需求。
<img src="https://static001.geekbang.org/resource/image/9f/71/9f58d7822457afec81a953d09218a271.png" alt="">
总体来讲,这套方法的核心是分布式和系统性。对于新的内部客户需求,要进行审核以确保需求请求是合理的。而对于现有客户的需求,要在宏观级别,确保需求配额不要超过;在微观层面上,确保资源使用效率不退化,还要不断地寻找效率提升优化的机会。
唐代诗人岑参有首很有名的边塞诗,有两句是描写塞外的寒冷:“将军角弓不得控,都护铁衣冷难着。”说的是边塞都护府的将军,手冻得拉不开弓,几乎不能控制弓箭了,铁甲也冰冷得让人难以穿着,非常辛苦和困难。我们做容量控制管理的工作也着实不容易,需要和各个内部客户产品组打交道,互相理解,为了共同的目标和公司的利益一起工作。
## 思考题
你的公司里最大的互联网服务是什么?这个服务的容量增长是怎么控制的?
如果这个服务的客户要求实现一个新的功能,所以需要很多服务器,而你负责这个服务的容量管理,你会如何回答客户呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,71 @@
<audio id="audio" title="开篇词 | 代码不要眼前的苟且,而要效率的提升和性能的优化" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/eb/2d/ebdaf1fb483a557645f0fe08233c6b2d.mp3"></audio>
你好,我是庄振运。万分高兴能有机会和你分享我的知识和经验。
最近十年我一直在美国硅谷工作也非常关注中国的互联网发展经常和国内同行交流。我曾经在QCon做过两次分享。感谢参会同行的青睐两次都被评为“明星讲师”。
2019年初极客时间的编辑找到我希望我能用专栏的形式作更多的分享。我确实也想总结一下这些年的知识、体验和感悟于是就写了这个专栏。
## 得性能者得天下
过去的三十年,我们见证了互联网的蓬勃发展和风云变幻。虽然互联网公司潮起潮落,提供的服务也日新月异,但是互联网界一个永恒的追求就是性能和效率。
程序员所写代码的性能直接决定了互联网服务的质量,也就决定了客户的去留。同时,互联网服务终归要运行在各种服务器上,公司需要部署数据中心和网络等容量,这些容量的效率直接影响了公司的运营成本。
所以,程序性能的优化和容量效率的提升也必然是每个互联网从业人员和公司的重要工作。
回首过去的学习、研究和工作经历,蓦然发现我已经专注于性能优化和容量效率这个领域将近二十年了。所涉及的范围也很广泛,从软件到硬件,从前端到后端,从服务器到数据中心,几乎涵盖了计算机和互联网的方方面面。平时,我也经常通过发表论文和参加会议的方式来总结和分享所学的知识。
这些年的从业经历也让我有机会对这一行业有些自己的观察、体会和思考。
首先,我注意到**性能优化和容量效率的工作在IT界越来越重要**。
当今互联网服务的特点是大规模(大数据、用户多等)和高要求(低延迟、高吞吐等)。这样的特点也就要求服务的高性能和容量的高效率。
不夸张地说当今互联网得性能者得天下。对IT公司而言提高了服务性能和容量效率也就降低了公司的运营成本增强了公司的竞争力。
第二,**性能和容量效率和每个IT从业人员息息相关**。
每个IT从业人员尤其是程序员和运维都需要关心服务性能和容量效率这些知识会为自己的职业发展锦上添花甚至是工作中不可或缺的。 性能和容量效率的知识对于每个程序员、运维、测试人员和管理人员都会有帮助。
具备了这些知识,程序员在开发软件时会写出高性能的代码;运维人员会懂得如何监测和提高系统的性能;软件测试人员会通览软件测试的分类和方法;管理人员可以了解如何进行容量管理,提升服务效率并降低运营成本,等等。
第三,**性能优化和容量效率这一领域的工作是“越老越吃香”**。
在互联网行业工作,很多人担心的就是年龄问题。性能和容量领域的工作特点是需要多方面的知识和技能,以及实际的经验积累。这种学习和积累需要相当长的时间,不太可能一蹴而就。
我在很多硅谷公司工作过,周围的很多同事都是年龄比较大的。他们往往是技术牛人,是挑大梁的角色。公司为什么会重用这些“大龄员工”呢?就是因为他们能够**帮助公司提升业务性能和容量效率,节省运营成本**。随着公司业务规模的扩展,节省下来的成本也越来越大,公司也越来越离不开他们。
第四,这方面的工作要求比较特殊——**需要广泛的知识面和软技能**。
除了软硬件结合的知识,以及理论联系实践以外,性能和容量效率的工作还需要各种软技能,从而和其他员工、其他团队有效地进行沟通和合作。 我用一张思维导图大体表示了这些知识和能力。
<img src="https://static001.geekbang.org/resource/image/5b/82/5b7c188f0af59eaf8baaa0a40ac39d82.jpg" alt="">
最后,这一领域和职业于总体而言**缺少合适的介绍和相关资料**。
比如性能工程这个领域虽然各种“性能测试”和“性能调优”相关的知识有很多比如JVM调优、操作系统调优等但是系统地介绍“性能工程”的资料很少。“容量效率和管理”方面更是很少见了。就连“性能和容量工程师”这个职业也很少人讲。
## 专栏设计
性能问题通常是复杂的,性能工程师就像“医生”一样,需要懂得多方面的知识才能为“病患”确诊“病因”。
医生遇到病人会“望闻问切”利用X光等手段做各种分析。根据病人的表象和分析的数据医生会做出诊断确定是什么病。然后会开药方或者给予治疗。病人服药或者接受治疗后会再次进行复检来确定治疗效果。
性能工程师对待计算机和互联网的性能问题也是如此,会观察各种参数,甚至进行主动的性能测试。根据各种参数和性能测试的结果,可以做出分析,并最终确定性能问题的根因。这之后进行性能优化来消除对应的性能问题。采取优化后,还需要重新测试来验证问题是否真正解决,亦或是另有他因,从而需要重新分析。
我用下面这张图片来类比这两种场景。
<img src="https://static001.geekbang.org/resource/image/7b/7d/7bf7a098b73b828116b827c3c3be077d.png" alt="">
第一个场景是医生诊断病情和治疗病人。第二个场景是性能工程师分析问题并且优化性能。你可以直观地看出两种场景中的每一步的相似之处。在专栏中,我也是根据这样现实中解决问题的思路(问题→测试→分析→优化→实践)来为你讲解的。
为了帮助你循序渐进地了解并掌握性能和容量工程相关知识我将这个专栏的内容设置为八个模块共36讲核心内容。
<img src="https://static001.geekbang.org/resource/image/7c/20/7cbf4ecd78ec818ca31ecf5b6d330820.jpg" alt="">
从今天开始让我用36讲的课程帮助你在性能和容量效率领域迈出坚实的36大步。学习完这个专栏之后我相信你一定能对这一领域有更多、更广和更深的了解。
书山有路,勤劳为径;学海无涯,辛苦作舟。希望这个专栏可以成为这样的一条捷径和一叶轻舟,我们一起整装出发,扬帆起航。
有道是“知音难觅”。王勃在《滕王阁序》里也说过:“杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?”很荣幸能够和你在此相遇相知,一起学习交流,也欢迎你给我留言,说说你对性能优化和容量效率的看法和疑问。

View File

@@ -0,0 +1,141 @@
<audio id="audio" title="01 | 程序员为什么要关心代码性能?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cd/08/cdfe1a5ef37d32cf2d66403a97353808.mp3"></audio>
你好,我是庄振运。
感谢你加入这个专栏的学习,我也非常高兴能有机会和你一起探索这个领域。
我在计算机和互联网行业已经研究和工作近 20 年了,一直从事性能优化和容量管理相关的工作。从今天起,我就和你分享我这些年的经验和感悟。
提起计算机和互联网多数人首先想到的职业是程序员。中国有多少程序员呢很多人估计有600万左右。全球的人数就更多了肯定超过2000万。
我虽然也在互联网领域,也做过几年写程序的工作,但是现在的工作,严格意义上不算程序员,而是性能工程师。不过我和很多程序员朋友一起工作过,也讨论过。谈到性能优化和系统容量管理的时候,一开始他们经常会问我一个问题,就是程序员为什么需要了解性能和容量这些东西?通俗点说,这个问题就是:我就是一介程序员,性能和系统容量听起来很重要,但与我何干?
这个问题问得很好。我可以和你肯定地说,程序员应该关心,也必须关心代码性能和系统容量。今天这一讲,我们先说说程序员为什么需要关心性能。
## 怎么定义“性能”和 “性能好”?
说起代码性能,首先我们需要弄清楚什么样的代码算是性能好?怎么样算是性能不好?
代码性能表现在很多方面和指标比较常见的几个指标有吞吐量Throughput、服务延迟Service latency、扩展性Scalability和资源使用效率Resource Utilization
- 吞吐量:单位时间处理请求的数量。
- 服务延迟:客户请求的处理时间。
- 扩展性:系统在高压的情况下能不能正常处理请求。
- 资源使用效率单位请求处理所需要的资源量比如CPU内存等
必须说明的是这几个指标之外根据场景还可以有其他性能指标比如可靠性Reliability。可靠性注重的是在极端情况下能不能持续处理正常的服务请求。不过我们这个专栏的讨论主要围绕前四个更常见的目标。
性能好的代码,可以用四个字来概括:“多快好省”。
<img src="https://static001.geekbang.org/resource/image/95/e6/95a0a14caf49b53ee4859fe892596fe6.jpg" alt="">
看到这四个字,你可能想起了咱们国家当年制定的大跃进总路线,那就是:“鼓足干劲、力争上游、多快好省地建设社会主义”。没错,高性能代码的要求和这个“社会主义建设总路线”相当一致。这里的“多”,就是吞吐量大;“快”,就是服务延迟低;“好”,就是扩展性好;“省”,就是资源使用量低(也即是资源使用效率高)。
用这样的四个指标来衡量,那么性能不好的代码的表现就是:吞吐量小、延迟大、扩展性差、资源使用高(资源使用效率低)。
## 程序员为什么要关心代码性能?
对程序员来讲,写出的代码就是他的产品、他的生命线、他的形象和价值。代码性能不好,就是质量差,不靠谱。轻者影响程序员的声誉,重者影响他的工作。
对一个公司来讲,产品质量差,公司或许会倒闭。对程序员所在的互联网公司而言,如果公司的业务依赖于程序员写的代码,那么代码性能差,关键时刻掉链子,比如双十一促销的时候,公司的业务性能就会经常出问题,进而会影响公司的运营和营收,这可是天大的事情。
因此,如果一个程序员写出性能很差的代码,无异于耍流氓,并且相关程序员的工作也很难保住。
反过来讲,如果写出的代码性能很高,那代码的作者必定是我们大家认可的“靠谱”程序员,少不了“人见人爱”——客户喜欢,同事喜欢,领导也喜欢。
## 不同级别的程序员都需要关心性能
还有些朋友或许认为:代码性能是**某些人或者其他人**应该负责的;我就负责把代码写出来,优化的事,他们负责。这里的“某些人和其他人”可以是指软件测试人员、运维人员、技术专家,或者是性能工程师。
这种想法也是不对的。我下面就用几个案例来举例说明,代码性能是各个级别的程序员都应该关心和负责的。事实上,程序员从学校出来开始,一步步地在职业上攀升,每一步都应该和性能结伴而行。
我用一张图来表示一个成功程序员的技术职业轨迹(注意里面的职位和年限仅供参考)。
<img src="https://static001.geekbang.org/resource/image/0b/de/0b74f5861099d26e00ec76007913b6de.jpg" alt="">
学生刚刚从学校毕业加入互联网公司一般是入门级程序员。工作1到3年后就成为普通的程序员。工作三五年后可以算是资深程序员。工作6到10年后可以成长为技术专家。10年以上可能成为高级专家或者架构师。
### 举例1刚入门的程序员
小李刚刚大学毕业,进入一个互联网公司。
领导给他的任务是写一个小模块,其中有一个需求是统计两个日期之间有几个正常工作日(也就是多少是周一到周五)。小李采取的是简单暴力法,就是用一个循环,循环的起始和截至日期就是给定的两个日期。在循环里面,对每一个日期判定一次,确定是工作日还是休息日,然后把工作日累加起来。
这样的代码显然性能不高,生产环境里面跑起来很快就会出问题。比如,如果两个日期差距很大,这个模块可能就需要很长时间才能处理完。
如果小李注重代码性能他完全可以用更高效的方法比如快速判定给定的两个日期间有多少个星期然后乘以5因为每个星期有5个工作日。然后对头尾的星期进行特殊处理。这样的代码跑起来快多了。我可以想象小李在优化完代码后或许会吟诵两句“何当金络脑快走踏清秋”来形容新代码的性能。
### 举例2普通的程序员
小王做程序员2年了在公司里已经可以独立负责一个模块了。有一天他需要把一个二维整数数组进行重新赋值于是他写出了下面的二重循环
<img src="https://static001.geekbang.org/resource/image/35/a9/3549fc43c8199499d31b2bf5432f23a9.jpg" alt="">
如果小王了解计算机内存和缓存的知识以及大小,他或许会写出下面的循环。虽然只有两个字母的差别,性能却提升了很多倍。
<img src="https://static001.geekbang.org/resource/image/20/d9/2055088aa3fc67f23d0274435c1e48d9.jpg" alt="">
原因是什么呢?
因为计算机通常都会有数量不大的缓存。数组在内存里是连续存放的,所以,如果访问数组元素的时候能够按照顺序来,缓存可以起到极大的加速作用。
小王一开始的二重循环,恰恰没有有效地使用缓存,反而对数组元素类似随机访问。第二个版本就改正了这个错误,优化了性能。
### 举例3资深的程序员
小赵工作4年了已经算是资深的C++程序员负责一个程序的开发和设计。他的一个程序需要使用一个Map的数据结构。他开始使用的是STD库的标准实现`unordered_map`。但是他发现,在数据量大的时候,键值的插入操作需要的时间很长。虽然做了各种代码优化,但性能总是不尽人意。
其实如果他了解C++有些库有更高效的Map实现比如`google::dense_hash_map`,他或许可以酌情采用,从而大幅度提升性能。
很多的测试结果显示,`google::dense_hash_map`的性能可以比`std::unordered_map`快好几倍。下图(图片来自[https://tessil.github.io/](https://tessil.github.io/) )正是同一种测试环境下,两种实现的处理时间比较,我们可以清楚地看出性能的差距。
<img src="https://static001.geekbang.org/resource/image/12/4e/124cbafaf8f452f8399c2c85a1a6534e.png" alt="">
### 举例4技术专家
小刘工作8年了在公司里已经算是不大不小的技术专家了。
有一天他看到一份项目计划其中有一段引起了他的兴趣。这份计划是为了提高服务器的CPU使用效率提出把应用程序的线程池增大建议程序线程池的主线程数目应该和服务器的逻辑CPU的数目相等。当然这里的逻辑CPU就是我们通常说的虚拟内核数。
小刘这几年对硬件和操作系统钻研良多他立刻指出这样部署不妥他建议降低主线程池大小到逻辑CPU的一半。技术讨论过程中小刘给大家仔细讲解了原因大家最后认可了他的建议小刘也获得了大家的青睐。
小刘之所以这样建议是因为他知道服务器的逻辑CPU不是物理CPU。在超线程技术Hyper Threading的情况下服务器的吞吐量不是严格按照逻辑CPU的使用率来提升的因为两个逻辑CPU其实共享很多物理资源。
比如下面这张图就表示了在一台有个逻辑CPU的服务器上如果部署超过4个线程得到的性能提升非常有限甚至可能会带来其他不好的后果。这里具体的提升率和效果取决于线程和应用程序的特性。图片来自[http://blog.stuffedcow.net](http://blog.stuffedcow.net)<br>
<img src="https://static001.geekbang.org/resource/image/fd/45/fd4b4e1797eed83f9967daaf0ea15745.png" alt="">
### 举例5:高级专家(架构师)
老周是公司里的架构师和高级专家。他最近对公司的一个重要业务进行了性能优化,用很小的代码改动,就给公司节省了几百万美元的运营成本(这是我身边发生的一个真实案例,除了名字不一样)。
这个业务的性能瓶颈是CPU。因为业务量大这个业务部署了1万台以上的服务器占用了很大一部分数据中心的容量。
老周仔细研究了业务的逻辑并且进行了性能测试和分析。他发现代码的执行过程卡在了CPU取指令的速度上因为内存和缓存的物理特性CPU花了很大一部分时间在等待指令获取从而造成了CPU浪费。
他经过考虑,决定进行**指令级别的提前获取优化**。具体来讲就是用GCC的`__builtin_prefetch`指令来预先提取关键指令从而降低缓存的缺失比例也就提高了CPU的使用效率。
下图是GCC关于这个指令的官方文档。
<img src="https://static001.geekbang.org/resource/image/95/36/95c984c03bc60ccb3f56867e0c4bcb36.png" alt="">
经过这样的优化一台服务器可以处理比以前多50%的请求从而节省了相应比例的服务器和容量。从公司成本角度来看这一优化节省了3千台以上的服务器价值几百万美元老周被CEO开会表扬也是自然的事情了。
有趣的是,整个的代码改进只需要几行代码的改动,真真切切是“一字万金”。
## 总结
重要的事情需要多说几遍每个IT从业人员尤其是程序员都需要关心代码性能。
如果不了解性能的知识,也许能写出可运行但性能不好的代码。但一个真正对工作、对公司和对自己负责的程序员一定会发现,性能不好的代码无异于耍流氓,不经用还隐患无穷,万万要不得。
换句话说,对程序员来说,生活不仅是眼前的代码,还有效率和性能的优化。唐代诗人孟郊在考中进士后写了一首《登科后》,其中有两句:“春风得意马蹄疾,一日看尽长安花。”
我们谁不希望写出来的代码也运行飞快,自己能春风得意呢?!
## 思考题
无论你工作几年了,也无论是现在具体做什么工作,你能举出一两个,因为代码性能不好并导致严重后果的例子吗?是什么样的性能问题呢?
换个角度来说,如果写代码的程序员一开始就考虑到各种性能问题,并且提前在代码里面解决,写出的代码跑得飞快,而且很稳定。这样靠谱的程序员你会不会给他点赞?
欢迎留言和我分享你的观点,也欢迎你把今天的内容分享给身边的朋友,和他一起讨论。

View File

@@ -0,0 +1,122 @@
<audio id="audio" title="02 | 程序员也要关心整个系统和公司成本吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b6/ed/b65ad4079f3ba8a6cf442777c7d8a0ed.mp3"></audio>
你好,我是庄振运。
上一讲我们谈了,作为一个程序员,你所负责的软件模块的性能是很重要的。如果写的程序性能不好,轻则通不过开发过程中的性能测试这一关,严重的话,还会为以后的业务生产环境埋下很多地雷和炸弹,随时会踩响和爆炸,从而影响公司的业务和运营。
代码性能的重要性,不仅仅局限于程序员所直接负责的软件模块,它对其他相关软件模块、模块所在的应用程序、单机系统的设计、互联网服务的质量、公司的运营成本,甚至对我们共同生活的地球都很重要。
这一讲,我们就来说说这几个方面。为了方便说清这几方面的关系,我画了下面这张图。
<img src="https://static001.geekbang.org/resource/image/10/3c/1076d5164429beb91bfb4e790f8d2a3c.jpg" alt="">
我来简单解释一下这张图:
- 首先红色模块是我们负责的模块标示0它和其他模块一起构成了整个应用程序标示1
- 这个应用程序运行在服务器和OS上面构成了一个单机系统标示2
- 几个单机系统一起组成一个互联网服务标示3来面向客户
- 这个服务和其他服务一起需要公司的硬件容量支持从而占用公司的商业成本标示4
- 最后,别忘了,我们共同生活在这个可爱的绿色星球上。
## 应用程序的性能标示1
<img src="https://static001.geekbang.org/resource/image/10/3c/1076d5164429beb91bfb4e790f8d2a3c.jpg" alt="">
我们先从标示0和1开始也就是模块和应用程序。
我们每个人负责的代码模块,一般都不是孤立存在的,都要和其他模块交互。模块之间是唇齿相依的。如果一个模块性能不好,一定会在某种情况下影响到其他模块,甚至是整个程序的性能和服务质量。唇亡齿寒的道理我们都懂,所以每个软件模块的性能都需要严格把关。
具体到我们自己的模块来说,或许在开发、测试的环境中,这个模块看起来运行正常,但等到了生产环境,一旦流量上去,性能不好的模块很快就会被曝光。尤其是在流量很高的时候,如果性能不佳,公司的运维同事一定会疲于奔命,更甚者会导致公司业务受损,这时性质就很严重了。
很多性能问题都会被根因分析。如果根因分析定位到是某人所负责的模块拖了大家的后腿,写这个模块的程序员不仅会被其他程序员鄙视,还可能会被老板找去“喝茶”。
回到性能问题上。性能问题可以表现在很多指标上比如吞吐量Throughput服务延迟Latency)可靠性Reliability)延展性Scalability等等。根据模块所在的应用程序的性质其中的一个或者几个指标会相对比较重要些。举个简单的例子如果一个应用程序所在的互联网服务是面向终端客户的比如微信用户那么客户的服务延迟一定是极为重要的性能指标。
具体来说假如端到端的服务延迟有最大的延迟允许比如不能超过2秒钟那么这个服务所需要的应用程序或者微服务一般也都会有自己的最大延迟预算。假设这个端到端服务需要三个微服务或应用程序来串联那么每个应用程序都会分到一定的延迟预算比如最大1秒。
同理我们所负责的模块也会根据程序的逻辑设计分到相应的延迟预算比如300毫秒如下图红色模块所示。
<img src="https://static001.geekbang.org/resource/image/38/8a/38553b84376fa3add7bcbc44cebec78a.jpg" alt="">
这种情况下如果我们的模块在流量适度变大时处理时间超过300毫秒的预算那这个模块的延展性显然就不够了很可能会导致整个端到端服务的延迟超标。
## 单机系统的性能标示2
讨论完了模块和程序,我们再看看单机系统。
<img src="https://static001.geekbang.org/resource/image/10/3c/1076d5164429beb91bfb4e790f8d2a3c.jpg" alt="">
我们的模块所在的应用程序(或者微服务),是运行在服务器的硬件和操作系统上面的。对这台服务器而言,这是个单机系统,包括软件和硬件的整个垂直全栈。现在的系统都非常复杂,软硬件之间的交互也复杂而微妙,并且随着各个构件的升级而经常变化。
单机系统的软硬件构件包括操作系统、程序库、存储系统、CPU、内存、还有网络等等。这些构件都会或多或少地影响上层程序的性能。
我举一个简单的例子,这个例子在后面的文章中还会仔细介绍。在我以前的公司,曾经有一个应用程序需要不断地把数据同步写到底层存储系统。这个应用程序后来被发现有性能问题,我们花了很多时间去做分析,后来终于找到原因。
这个性能问题的表现是:数据同步写入的延迟有时候会非常高。原因是**底层的存储系统同时服务好几个应用程序**。
由于底层存储系统有各方面的限制,当多个应用程序同时使用这个存储系统时,每个应用程序延迟方面的性能并不能得到保证,因此导致了某个应用程序的读写被严重推迟,并最终导致了后者的性能问题。
更进一步到设计层面来讲,从我们负责的模块和应用程序角度来看,**对下层的软硬件构件越是了解,就越有可能设计出性能优越的模块和应用程序**。
比如很多数据存储方面的服务和应用程序在设计时需要仔细考虑各种存储系统的技术趋势和性能特征。这些性能特征包括存储速度、价格、容量大小、易失性等。比如传统的DRAM内存就是一种存储它速度很快但是价格贵、容量小并且所存数据不能长期保存一断电数据就会丢失。
最近几年一种新的非易失性内存NVMNon-volatile Memory的出现打破了这一传统数据可以长期保持但是速度稍微慢一些。同样的传统的硬盘存储容量大、价格低但是速度最慢。最近几年固态硬盘SSD 的大量采用,在很多新设计的在线系统中已经作为标准配置,几乎取代了传统硬盘。
我们程序员作为自己模块甚至整个应用程序的设计者,如果能充分考虑这些硬件的性能特征和技术趋势,就可以设计出性能好、高效率的软件。
下面这张表格我列举了四种存储硬件分别是传统DRAM内存、NVM内存、硬盘和固态硬盘并且比较了它们的五个指标。
<img src="https://static001.geekbang.org/resource/image/50/23/5061b7cf9e91d10f584da89dab2a0323.jpg" alt="">
## 互联网服务的性能标示3
<img src="https://static001.geekbang.org/resource/image/10/3c/1076d5164429beb91bfb4e790f8d2a3c.jpg" alt="">
现代的互联网服务往往需要很多模块交互,并且客户流量会很大。我们所在的应用程序和系统经常只是整个互联网大服务的一部分,会有上游服务对我们产生请求,我们也会对下游服务发送请求。
比如下面的图示,我们所在的服务模块用红色标识,上游服务模块用绿色标识,下游服务模块用黄色标识。
<img src="https://static001.geekbang.org/resource/image/3e/18/3e13c191e41aa0a206fa194c72027518.jpg" alt="">
从公司运营的角度来看,整个互联网大服务的性能才是我们每个程序员真正关心和负责的。我们每人都需要从这个大局出发来考虑和分析问题,来设计自己的模块以及各种交互机制。否则,可能会出现我们的模块本身看起来设计得不错,但却对上下游模块造成不好的影响,进而影响整个大服务的性能。我来举一个真实的案例。
这个案例是从一次生产环境下的服务问题中发现的。某个下游模块出现延展性问题,服务的延迟变大,上游模块发出的请求排了很长的队。这个时候上游模块已经感觉到下游的性能问题,因为对下游请求的处理延迟已经大幅度增加了。
此时上游模块本应该怎么做呢?
它应该降低对下游模块的请求速度,从而减轻下游模块的负担。但是案例中的上游模块设计没有考虑到这一点。不但没有降低请求速度,反而发送了更多的请求,以求得更快的回答。这样无异于火上浇油,最后导致下游模块彻底挂掉,引发了整个服务的瘫痪。
后来我们学到的教训就是,串联的服务模块中,上游模块必须摒弃这样雪上加霜的服务异常尝试,应该采用**指数退避机制**Exponential Backoff ),通过快速地降低请求速度来帮助下游模块恢复(上游模块对下游资源进行重试请求的时间间隔,要随着失败次数的增加而指数加长)。
## 公司的成本标示4
<img src="https://static001.geekbang.org/resource/image/10/3c/1076d5164429beb91bfb4e790f8d2a3c.jpg" alt="">
我们所负责的互联网服务的性能直接影响公司的成本。
一个高性能的服务在服务同等数量的客户时需要的成本会比较小。具体来说如果我们的服务是计算密集型那么就应该尽量优化算法和数据结构等方面来降低CPU的使用量这样就可以用尽量少的服务器来完成同样的需求从而降低公司的成本。
现如今是大数据时代,公司在服务器和数据中心以及网络等容量方面的支出是很可观的。尤其是大的公司比如脸书,腾讯等,公司有很多的数据中心和几百万台的服务器。如果公司的每个服务都做到高性能,替公司节省的运营成本是非常巨大的。
同时,面向互联网服务的容量规划和效率管理也很重要。如果能科学地管理容量,准确地预测未来需求,并逐步提升容量的效率,就能把公司这方面的成本管理和节省好,从而不至于浪费资金在不必要的多余容量上。
## 我们共同的绿色地球
最后,让我们跳出“我们的公司”这样的小格局,放眼全球,甚至我们人类的大格局。我们只有一个共同的地球,我们有责任让她保持绿色。
现在的时代感谢互联网的发展和大数据时代的来临全球各公司的数据中心已经在消耗大量的能源。从咱们国家来看2018年国内的数据中心用掉的电量比整个上海市用电量还大占全国用电量的2.3%。全球来看也类似数据中心在2018年消耗了全球3%以上的电量。这个耗电量已经是差不多整个英国全国用电量的两倍。更严重的是,这样的用电还在飞速增长,差不多每三年或四年就翻一倍!
所以,我们每个人,其实都负有责任来降低能源消耗。虽然生活中有多种方式可以降低能源消耗,我们的日常工作其实也是重要的一环。如果每个人能把负责的代码优化一下,服务高效一些,我们就是在拯救我们共同的地球,让她永葆绿色!
## 总结
对代码和程序的性能优化,以及对系统容量的效率提升,和我们共同关心爱护的东西息息相关。从代码模块,到整个系统,到互联网服务,到公司运营,再到我们的社会,都依赖于我们每个人的责任和贡献。
你和我或许是一介普通工程师和程序员,但人们常说“位卑未敢忘忧国”。我们虽然没必要拔高到忧国忧民的高度,但是也要认真做好我们的份内份外的事情。
## 思考题
- 回顾你最近接触过的软件模块或者正在写的代码,有没有和其他的系统模块有交互关系,它们之间是如何交互的?
- 如果你的模块性能不好(不管是响应时间很慢,还是发出过多请求),有没有可能对其他系统模块造成影响?这个影响会不会造成整个系统和服务的严重后果甚至瘫痪?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,142 @@
<audio id="audio" title="03 | 导读:专栏是怎么设计的?需要哪些知识?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/71/d4/71d37661d4e106c3dcb2f18dbd6f28d4.mp3"></audio>
你好,我是庄振运。
在前面两讲中,我们看到了性能优化和容量效率提升的重要性,如果程序员在这方面的技能和知识有欠缺,只知道写代码,那么写出来的代码很可能效率低、性能差。在代码性能差的情况下,如果你再被老板或者同事威逼利诱去做性能优化,那么就成了赶鸭子上架的苦差事了,你只能感叹:“问君能有几多愁,恰似写完代码去调优”。
玩笑开完,我们还是从正能量的角度去看一看吧。
这就要从一个典故说起了美国福特汽车公司当年要排除一台大型发动机的故障请了很多专家但都束手无策。最后请来了著名的电机专家斯坦门茨Charles Proteus Steinmetz。斯坦门茨仔细检查了机器后用粉笔在机器外壳的某处画了一道线然后说“把做记号处的电机线匝减少16圈。”难题居然就迎刃而解了。
斯坦门茨索要了1万美元作为报酬很多人觉得实在是太多了。因为当时福特公司最著名的薪酬口号就是“日薪5美元”也就是说一个工人每年能赚1千美元已经是很高薪了。但是斯坦门茨回答道“用粉笔画一条线顶多值1美元但是知道在哪里画线值9999美元。”当公司总裁福特先生得知后十分欣赏斯坦门茨并很痛快地给了1万美元的酬金。
我们做性能工作也是如此,虽然性能优化的方法和最终解决方案或许看起来很简单直白,但是要知道**在哪里做优化**和**做什么样的优化**,却需要很多的测试和分析的工作经验。
每个程序员和运维人员都应该尽量多地了解性能优化和容量效率提升的知识,学习这方面的技能,因为在自己的职业的不同阶段都可以从这些技能获得收益。
那么,性能优化和容量效率提升需要什么样的知识和技能呢? 这一讲我就和你聊一聊。
## 总体要求
性能和容量管理工作需要的知识面比较广, 而且最好具有其他相关方面的能力,比如要有一定的**和人打交道**的软技能,从而和其他员工以及其他团队进行有效地沟通和合作。
大体上讲,性能相关的工作有三个方面的特点,我分别说明一下。
第一个特点是**知识面要广,并且软硬结合**。
计算机方面的知识可以大体上分为两大类:软件相关和硬件相关。很多程序员对软件相关的知识了解多些,但对硬件方面了解不多。但是,因为很多性能问题会牵扯到硬件,所以基本要求就是“能软能硬”,两方面的知识都要足够。
第二个特点是“**理论联系实际**”。
一方面需要理论根底深厚,因为有时候性能问题是某些不明显的原因导致的,所以需要对各种协议,对软件和操作系统,对硬件和网络都要非常熟悉。同时,性能分析的过程需要做些实验来使真正的问题暴露出来,也就是需要进行验证,所以对动手能力的要求也比较高。
第三个特点是**不但要会性能测试,还要会性能分析和性能优化**。
我画了张图,希望能帮助你理清思路。通俗点说吧,这些方面对我们的要求就是:**写得了代码,查得出异常;理得清问题,做得了测量;找得到病根,开得出药方**。
<img src="https://static001.geekbang.org/resource/image/57/c6/579b9872d17b084e28a9e0c63613c6c6.jpg" alt="">
## 广阔的知识面
我们现在先从知识方面讲起,看看性能和容量工作需要什么样的知识(参考上面的图)。
首先是**软件**方面,其实内容很多。图里面仅仅表示了几个方面,你可以参照我们上学时开的各种计算机方面的课程,尤其是操作系统、数据结构和算法、编译原理以及各类协议。这方面也包括很多数学和统计方面的知识。
**硬件**方面包括服务器本身、存储系统、各类网络、数据中心等等。这方面的重点是服务器本身的部件比如CPU、内存等等。
至于**实践经验**,主要是关于性能方面的。比如很多系统的命令来观察系统资源的使用率,各种调试和测试工具,以及如何进行性能分析和性能优化的实践。
**软技能**方面,我会在这个专栏的最后两讲详细进行介绍,这里大概讲一下。做性能优化和容量管理工作经常需要和其他的团队和人员打交道。这些团队包括开发团队、数据中心团队、运维团队等等,需要和他们紧密而愉快地合作。同时,性能分析和优化经常需要把数据和分析展示给别人和领导看,这就需要你拥有一定的演讲能力。
## 性能工程师的工作很像“医生”的工作
性能问题通常是复杂的,性能工程师的工作就很像“医生”的工作,需要多方面的知识才能确诊(这个观点我在开篇词里有介绍,这里还是有必要再详细说说)。
虽然性能问题有时候明显的是表现在某个组件的问题,比如网络拥塞,但很多情况下并不容易确定是哪里出了问题。所以,就需要做很多种不同的测试和数据分析。
另外,很多程序和服务内部有很多模块,外部也牵扯到其他子系统。即使每个子系统分开来测试时都性能不错,但是在某些特殊情况下,会发生级联和连锁故障。所以,除了需要了解每个子系统内部的原理,还必须弄清楚不同模块和子系统之间的协作交互关系。
具体来讲,软件硬件各个子系统都可能因为各种情况下的交互而产生性能问题。尤其是垂直的软件硬件栈(比如程序、操作系统、硬件等),这样的跨层交互,更会产生各种复杂的性能特性,这方面的内容后面会详细展开。
我经常把性能工程师比作医生。
医生遇到有健康问题的病人会做“望闻问切”利用B超、X光等做各种分析。根据各种数据和病人的表象医生会做出诊断确定是什么病。然后会开药方或者给予相应的治疗。病人吃药和接受治疗后会再次进行复检来确定病情是已痊愈是稍有缓解还是加重了
性能工程师对待计算机和互联网的性能问题也是如此,会首先观察各种参数,甚至进行主动的场景测试。根据性能测试的结果,可以做出分析,并最终确定性能问题的根因。这之后可以进行相应的性能优化来消除对应的性能问题。采取优化后,还需要进行重新测试来确定问题真正得到解决,亦或是另有他因,从而需要重新分析。
我用下图来类比这两种场景。
<img src="https://static001.geekbang.org/resource/image/7b/7d/7bf7a098b73b828116b827c3c3be077d.png" alt="">
第一个场景是医生诊断病情和治疗病人。第二个场景是性能工程师分析问题并且优化性能。我们可以直观地看出两种场景中的每一步的相似之处。
## 这个专栏的组织架构
我是怎样组织这个专栏的呢?首先重申,写这个专栏的初心是向你介绍性能和容量管理工程这一工作,并分享我近二十年来的学习和工作经验。
需要说明的是,这一领域和工作牵涉的内容其实非常广泛,包括所有的计算机互联网方面的知识。所以在我们这个专栏有限的时间内,面面俱到是不太现实的。出于这样的考虑,在这个专栏里我的侧重点及讲述方式和其它相关专栏和课程不太一样。
我在专栏文章中尽量做到深浅结合,既要让你能了解这一工作的重要性和大体情况,也要有足够的干货,希望能让你有恍然大悟的感觉。
计算机科学在国外大学里面一般不在工程学院下面开设而是归类于艺术“Art”设置在艺术和科学学院下面。不管这种分类的渊源如何我确实觉得计算机科学和性能调优真的是一门艺术它需要知识需要经验也需要天分。既然是艺术就和其它艺术形式比如文学有其相通之处。
我个人喜好诗词古文,对生活中和学习中的很多感悟,我经常会跳出一层去体会。很多时候我发现不同领域的东西,它们的感觉和道理是完全相似或相通的。修辞学上有一种手法叫“通感”,就是不同的感觉方式的类似体验(比如听觉和视觉的相通等),也是这个道理。
所以,我在分享知识和经验的时候,有时会忍不住加上几句唐诗宋词古文,目的有好几个,第一是不希望一直干巴巴地讲课,希望加点调味料,提高你的兴趣;第二是理工科的技术和文科的文艺的确经常有异曲同工之妙,希望帮助你体会;第三呢,顺便帮你复习一下诗词,增加学习乐趣。
先一起来看看我们要学习的内容大纲。
### 开篇:代码性能和系统容量
考虑到很多朋友对这方面的工作不是特别了解,所以我开始在**开篇**这一部分用两讲来做一个宏观介绍让你了解性能问题为什么对每一个IT人员都重要尤其是对程序员。我分成“代码性能”和“系统性能及公司成本”来分别说明。
### 性能定律和数理基础
性能工程离不开理论基础。我接下来会用几讲来介绍最基础但也是最重要的数理基础和几大定律。这些数理基础包括一些基本的统计知识,以及对数据的分析和展示的方法。我还会把一些重要的性能相关的数字总结出来,让你参考和记忆。对待这些数字,你应该像对待九九乘法表一样,每个都铭记在心,因为工作中时时要用到。
### 性能测试
性能工程离不开测试。性能测试是一切性能工作的基础和开端。
很多公司的性能工程师其实多数时间是花在性能测试上,包括进行测试的设计和分析测试结果。虽然测试工作看起来简单直白,可是真正做好性能测试并不容易,这里有相当多需要注意的地方。我会梳理其中一些经验和指导原则讲解给你。
当然,测试的工具也很重要,一个好的测试工具绝对让你事半功倍,所以我也会介绍常用的好工具。
### 性能分析
我们需要对性能测试得到的数据进行仔细的分析和研究,只有这样才能发现真正的问题和找到性能问题的根本原因。而**性能分析**就是关键的一步。我会首先介绍进行性能数据分析的原则然后抓住几个重点领域包括CPU、内存、存储和网络来分别介绍常见的性能问题让你以后碰到这方面的问题时心里有数。
### 性能优化
性能分析的目的是找到性能问题的根因,然后进行**性能优化**来解决问题。性能优化做得好,必须有相关方面的知识和实践经验。我会给你介绍性能优化的六大原则和十大常用策略,并分几个领域用生产中的案例做具体的展示。
### 性能工程的进阶实践
我还特意准备了几讲稍微进阶的内容和实践案例。这些内容是我过去在几个大公司的亲身实践,每一讲都是针对某个具体场景的生产实战经验。
### 容量规划和服务管理
对公司,尤其是大公司来说,容量的规划管理和效率提升是很重要的,因为这直接关系着公司的运营成本。我注意到这方面的参考资料比较少,所以特意来和你介绍这一领域的知识。我会分成几部分来分享,包括服务器的部署、数据中心、容量规划、容量的效率提升以及服务需求的控制等等。
一个公司要成功运营,成本和对应的容量是总有限的,所以需要量入为出,对服务的需求进行适当的管理,尽量精打细算。管理的实践需要考虑很多因素,一方面尽量节省容量,另一方面也不能妨碍公司业务的扩展。如何把握这个度,我会讲讲经验。
### 专栏总结:性能和容量工程的工作特点
最后两讲是介绍性能和容量工程师这个职业,包括这一工作的特点和职业前景。
随着大数据和互联网的飞速发展,我坚信这方面的工作越来越重要。尤其是大公司,都会专门招聘这方面的人才组成特殊的性能优化和容量管理团队。所以,针对有志于从事这一行业的朋友,我会分享这方面的面试经验。
## 总结
得益于这种工作的特点,性能优化和容量效率需要了解的知识和技能比较广泛。人们常说:“读书破万卷,下笔如有神。”读书写文章是这个道理,做性能优化工作也是如此。只有不断学习计算机各方面的相关知识,博览群书,才能在解决性能问题时得心应手。
我们在前三讲里面一起探讨了为什么我们需要关心代码性能,系统性能和容量效率;并且了解了这方面的工作需要什么样的知识和技能。从下一讲开始,我们就一起学习这些知识和技能。
## 思考题
- 你碰到过或者听说过什么领域的性能问题吗?
- 对这个性能问题做根因分析和解决需要哪方面的知识和经验?
- 彻底解决这个问题需要和其他人和团队合作吗?
- 如果需要和其他人和团队合作,再假设你是带头人,你会怎么去推动呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,145 @@
<audio id="audio" title="19 | 性能优化六大原则:三要三不要,快速有效地进行优化" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/06/01/06d0b1ac11cea369cec367f878074b01.mp3"></audio>
你好,我是庄振运。
今天我们进入了专栏的新模块性能优化。在这个模块里我会先从“性能优化的六大原则”开始讲起然后再为你讲解实践中普遍采用的十个性能优化策略并且分别针对CPU、系统、存储以及跨层这几个领域讲讲具体的优化案例。
我们今天先探讨性能优化的原则。在讲具体原则之前,我想先给你讲一个有趣的往事。
我曾经负责过一个存储服务的性能优化和容量效率。那个服务的容量需求很大但它的最大性能瓶颈不是CPU而是存储的空间。
所以虽然有很多人给我们各种建议让我们花时间做CPU优化我都尽量挡了回去。因为我知道CPU不是最大问题所以坚持不懈地通过各种途径优化数据大小甚至以牺牲CPU为代价。最后的结果很好大幅度地降低了那个服务的容量需求。
在这个性能优化的场景,我们遵循了一个原则,那就是**优先优化最大性能瓶颈**。这其实就是马上要讲到的“三要”原则中的第一个。
## 性能优化的原则概述
在实际的性能优化中,我们需要考虑的因素很多,也经常需要在多个角度和目标间做一些平衡和取舍。为了帮助你把握这些,我个人总结出了六条原则,我把它们概括为:“**三要,三不要**”。
<img src="https://static001.geekbang.org/resource/image/bc/bb/bc08d73063d641b231db155d7ffe26bb.jpg" alt="">
- 三个“要”原则是:要优先查最大的性能瓶颈,性能分析要确诊性能问题的根因,性能优化要考虑各种的情况。
- 三个“不要”的原则是:不要做过度的、反常态的优化,不要过早做不成熟的优化,不要做表面的肤浅优化。
接下来,我们一个个地讲一下这六个优化原则。
## 三个“要”的原则
你可能已经发现了,这三个“要”的原则之间,其实是步步递进的关系。也就是首先需要查找最大性能瓶颈,然后确诊性能瓶颈产生的原因,最后针锋相对地提出最好的解决方案。
而这个最优解,往往是在考虑各种情况之后提出来,并最终被选中的。
### 要优先查最大的性能瓶颈
任何一个应用程序或者系统,总会有很多地方可以优化。
可是你知道要从何处下手吗?
其实你可以参考古人的观点,唐代的诗人杜甫说过:“射人先射马,擒贼先擒王”。会打仗的将军,一定会从“最值得打的地方”开始,性能优化工作也是如此。
我们永远都要优先从**最大的性能瓶颈**入手。
一般来讲,如果找到最大的性能瓶颈,并且解决了它,那这个系统的性能会得到最大的提升(参见[第4讲](https://time.geekbang.org/column/article/174462)帕累托法则)。反之,如果不解决最大的性能瓶颈,反而退而求其次,去解决了其他的性能问题,整个系统的性能或许会更高一些,但是提升的程度往往是非常有限的。
比如一个应用程序的最大性能瓶颈是CPU的使用率太高而其他种类型的资源比如网络和存储都很有富余。这种情况下如果你去优化网络和存储方面显然是不能大幅度地提升整体性能的。
不过这里有个稍微特殊的情况,就是**内存**。因为内存的分配和回收也会消耗一些CPU资源如果这时去优化内存使用很多时候的确会帮助你降低CPU使用率不过降低的幅度一定是很有限的。
归根结底要降低CPU的使用率**最有效的方法是做性能分析和剖析找出程序中使用CPU最多的地方**,然后对症下药地做优化。
### 要确诊性能问题的根因
我们前面讲过程序和系统如果在某个地方有性能瓶颈肯定是这个地方的资源不够用了不管是CPU、内存还是网络参见[第14讲](https://time.geekbang.org/column/article/182915))。
所以,当我们确定了最大的性能瓶颈后,就需要对这一性能瓶颈做彻底的性能分析,找出资源不够使用的原因,也就是考察使用资源的地方。
一种资源被使用的地方往往有好几个,我们需要一个一个地去分析考虑。只有彻底分析了各种使用的情况,才能进一步找出最主要的,也是最可能优化的原因,对症下药。
有些资源使用的原因也许是完全合理的。对这些合理的使用,有些或许已经仔细优化过了,很难再做优化。而另外一些则有可能继续优化。对资源的不合理使用,我们就要尽量想办法去掉。
对于需要优化的地方,我们就需要进一步考虑**优化工作的投入产出比例**,就是**既考虑成本,也考虑带来的好处**。因为有些情况下,虽然你可以去优化,但获得的收益并不大,所以不值得去做。
另外要提醒你的是,确诊性能问题的原因有时候非常困难,需要做多方面的性能测试、假设分析并验证。
比如CPU的使用有操作系统的原因有应用程序的原因但也有些CPU的问题是在非常边缘的场景下才发生的。为了暴露问题我们经常需要创造特殊的场景来重现遇到的性能问题。
### 要考虑各种情况下的性能
性能问题确诊原因后,我们就可以进入下一步,找解决方案了。一般说来,找一个解决方案并不难,甚至找好几个方案也不难;但是找出一个好的、最优的解决方案是真心不容易。
为什么这么说呢?
因为实际生产环境很复杂,而且会出现各种各样的特殊场景。
**针对某个具体场景提出的一个解决方案,多半并不能适应所有的场景。**
所以,对提出的各种方案进行评估时,我们必须考虑各种情况下这个方案可能的表现。如果一个方案在某些情况下会导致其他严重的问题,这个方案或许就不是一个好的方案。
但同时你也需要意识到任何解决方案都有长短有Tradeoff。如果苛求一个能在所有场景下都最优的解决方案往往是缘木求鱼是不现实的。
比如一种优化方案,可以让平均响应时间最小,但高百分位比较高。另外一种优化方案正好相反。那我们就需要考虑对自己来说哪种指标更重要。也就是说,我们经常需要在**不同性能指标间权衡**,以找到一个最优解能**达到总体和整体最优**。
这就需要我们**有一个整体的意识和判断**。
或许一个方案并不能面面俱到,在有些场景下性能不好。但是不同场景的出现概率不同,对其它模块造成的影响也不一样,并且最终客户的体验也不尽相同。这些因素都要考虑到取舍的决策过程中。
综合以上因素,在实际的优化过程中,我们经常会**反复权衡利弊和取舍来做最终决定**。
## 三个“不要”的原则
讲完了三个“要”的原则,我们接着来看三个“不要”的原则。
### 不要过度地反常态优化
性能优化的目标,是追求**最合适的性价比**或**最高的投入产出比**,在满足要求的情况下,尽量不要做过度的优化。过度的优化会增加系统复杂度和维护成本,使得开发和测试周期变长。虽然性能上带来了一定程度的提升,但是和导致的缺点来比,孰轻孰重尚不可知,需要仔细斟酌,衡量得失。
我的建议是,**根据产品的性能要求来决策**。
在设计产品时,我们对产品的性能会有一定的要求,比如吞吐量,或者客户响应时间要达到多少多少。如果达不到这个既定指标,就需要去优化。反之,如果能满足这些指标,那么就不必要花费太多时间精力去优化。
比如,我们要设计一个内部查询系统,预计最多只有一百个人同时在线使用的话,就完全不用按照百万在线用户的目标去过度优化。
更重要的是,多数的优化方法是并不是完美无缺的,是有缺点的,尤其是可能会对系统设计的简化性,对代码的可读性和可维护性有副作用。如果系统简化性和代码可读性更加重要,当然就更不能过度优化。
### 不要过早的不成熟优化
要体会这一原则我们先引用著名计算机科学家高德纳Donald Knuth的一段话“现实中的最大问题是程序员往往花太多时间来在错误的地方和错误的时间来试图提高效率和性能。过早的优化是编程中所有邪恶和悲剧或至少是大多数邪恶和悲剧的根源。”
<img src="https://static001.geekbang.org/resource/image/69/a7/6964528af8b4212dfa2291749b7415a7.png" alt="">
你只要稍微思考一下高德纳的话,就会发现,这句话在很多场景下都是很有道理的。
比如,在敏捷开发过程中,尤其是在面对一个全新的产品时,在业界没有先例和经验可遵循的情况下,最看重的特点是快速的迭代与试错,“尽快推出产品”是最重要的。这时,过早的优化很可能优化错地方,也就是优化的地方并非真正的性能瓶颈,因此让“优化工作”成为了无用功。而且,越早的优化就越容易造成负面影响,比如影响代码的可读性和维护性。
我个人认为,**如果一个产品已经在业界很成熟**,大家非常清楚它的生产环境特点和性能瓶颈,那么**优化的重要性可以适当提高**。否则的话,在没有实际数据指标的基础上,为了一点点的性能提升而进行盲目优化,的确是得不偿失的。
### 不要表面的肤浅优化
性能优化很忌讳表面和肤浅的优化,也就是那种“头痛医头,脚痛医脚”的所谓“优化”。如果对一个程序和服务没有全局的把握,没有理解底层运行机制,任何优化方案都很难达到最好的优化效果。
比如如果你发现一个应用程序的CPU使用率并不高但是吞吐率上不去表面的优化方式可能是增大线程池来提升CPU使用率。这样的简单“优化”或许当时能马上看到效果比如吞吐率也上去了但是如果你仔细想想就会发现如此的表面优化非常有问题。
这样的情况下,线程池开多大最合适?需不需要根据底层硬件和上层请求的变化而对线程池的大小调优呢?如果需要,那么手工调整线程池大小就是一个典型的“头痛医头”的优化。
为什么呢?
因为部署环境不会一成不变比如以后CPU升级了核数变多了你怎么办再次手工去调整吗这样做很快会让人疲于奔命难以应付并且很容易出错。
对这样的场景正确的优化方式是彻底了解线程的特性以优化线程为主。至于线程池的大小最好能够自动调整。千万别动不动就手工调优。如果这样手工调整的参数多了就会做出一个有很多可调参数的复杂系统很难用也很难调优很不可取。就比如我们都熟悉的JVM调优有上千个可调参数非常被人诟病。
## 总结
唐朝的名相魏征说过:“求木之长者,必固其根本”。意思是说,如果要一棵树长得高,必须让它的根牢固。否则的话,正如魏征自己所说:“根不固而求木之长”,一定是“知其不可”。根基不牢固,就不可能长成参天大树。
<img src="https://static001.geekbang.org/resource/image/b0/e9/b07a06f7da4768d7398e1a174326cce9.png" alt="">
同理,对现代互联网的服务和系统来说,性能问题是根本的问题。如果不知道系统的性能瓶颈,查不出性能根因,不知道如何解决,无法做合理的优化,这个服务和系统一定不会高效。
这一讲我们总结了六个性能优化的原则,这些原则的终极目的,就是找出性能的最大瓶颈,查出根因,并作做相应的最优优化,从而让我们的系统这棵树长高、长大。
## 思考题
- 今天介绍的六大原则里面,你认为哪个原则最重要?为什么?
- 你过去的工作中有没有因为没有遵循这几个原则而吃后悔药的时候比如你在选择一个数据结构的具体实现时比如Set有没有考虑各种场景下的性能
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,151 @@
<audio id="audio" title="20 | 性能优化十大策略:如何系统地有层次地优化性能问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/91/8ce7e55b982a131a6c3809e00333a291.mp3"></audio>
你好,我是庄振运。
上一讲中,我们聊了性能优化的六大原则。原则有了,但是在针对实际的性能问题的时候,用什么样的解决方案才可以提升性能呢?这就需要你了解**具体的优化策略**了。
现实中的性能问题和具体领域千差万别,我也不可能面面俱到。但是为了帮助你理解,我总结了十大常用的优化策略。
<img src="https://static001.geekbang.org/resource/image/5c/84/5cc1f7f09fb87ec47cccaeda6948d484.png" alt="">
我将这十大策略分成五个类别,每个类别对应两个相关策略,帮助你掌握。这五个类别是:时空相互转换、并行/异步操作、预先/延后处理、缓存/批量合并、算法设计和数据结构。我们现在一个个来讲。
## 时空转换
第一个策略类别是“时空转换”。我们看科幻电影和小说的时候,经常会看到时空转换这个题材。性能优化里面有两个策略恰好组成了这个类别,包括“用时间换空间”和“用空间换时间”这两个看似互相对立的策略。
### 1.用时间换空间
用时间换空间的策略,出发点是内存和存储这样的“空间”资源,有时会成为最稀缺的资源,所以需要尽量减少占用的空间。比如,一个系统的最大性能瓶颈如果是内存使用量,那么减少内存的使用就是最重要的性能优化。
这个策略具体的操作方法有几种:
- 改变应用程序本身的数据结构或者数据格式,减少需要存储的数据的大小;
- 想方设法压缩存在内存中的数据,比如采用某种压缩算法,真正使用时再解压缩;
- 把一些内存数据,存放到外部的、更加便宜的存储系统里面,到需要时再取回来。
这些节省内存空间的方法,一般都需要付出时间的代价。
除了内存,还有一种常见的场景是,降低数据的大小来方便网络传输和外部存储。压缩的方法和算法有很多种, 比如现在比较流行的ZStandardZSTD和LZ4。这些算法之间有空间和时间的取舍。
衡量任何压缩算法,基本上看三个指标:**压缩比例**、**压缩速度**以及**使用内存**。
如果系统的瓶颈在网络传输速度或者存储空间大小上,那就尽量采取高压缩比的算法,这样用时间来换空间,就能够节省时间或者其他方面的成本。
### 2.用空间换时间
“用空间换时间”就是对“用时间换空间”策略反其道而行之。有些场景下,时间和速度更加重要,但是空间尚有富余,这时我们就可以考虑用空间来换时间。
这里要注意的一点是,我们后面还会讲一条关于使用缓存的策略。虽然缓存的策略理论上也是一种“空间换时间”的方式,但我们在这里把它分开来讲,这是因为缓存策略的“空间”定义与一般的“空间换时间”不同。一般来讲,“缓存”使用的空间,和原来的空间不在同一个层次上,添加的缓存往往比原来的空间高出一个档次。而我们这里“空间换时间”的策略,里面的“空间”是和原来的空间相似的空间。
互联网的服务往往规模很大比如全国的服务甚至是全球的服务。用户分布在各地它们对访问时间的要求很高这就要求被访问的数据和服务要尽量放在离他们很近的地方。“空间换时间”就是对数据和服务进行多份拷贝尽可能地完美覆盖大多数的用户。我们前面讲过的CDN内容分发网络技术就可以归类于此。
其实我们部署的任何大规模系统,都或多或少地采用了用空间换时间的策略,比如在集群和服务器间进行负载均衡,就是同时用很多个服务器(空间)来换取延迟的减少(时间)。
## 预先和延后处理
优化策略的第二大类是“预先和延后处理”,这一类别也有两个互相对立的策略。一个是预先或者提前处理,另外一个是延后或者惰性处理。
### 3.预先/提前处理
预先/提前处理策略同样也表现在很多领域比如网站页面资源的提前加载。Web标准规定了至少两种提前加载的方式preload和prefetch分别用不同的优先级来加载资源可以显著地提升页面下载性能。
很多文件系统有预读的功能,就是提前从磁盘读取额外的数据,为下次上层应用程序读数据做准备。这个功能对顺序读取非常有效,可以明显地减少磁盘请求的数量,从而提升读数据的性能。
CPU和内存也有相应的预取操作就是将内存中的指令和数据提前存放到缓存中从而加快处理器执行速度。缓存预取可以通过硬件或者软件实现也就是分为**硬件预取**和**软件预取**两类。
硬件预取是通过处理器中的硬件来实现的。该硬件会一直监控正在执行程序中请求的指令或数据,并且根据既定规则,识别下一个程序需要的数据或指令并预取。
软件预取是在程序编译的过程中主动插入预取指令prefetech这个预取指令可以是编译器自己加的也可以是我们加的代码。这样在执行过程中在指定位置就会进行预取的操作。
### 4.延后/惰性处理
延后/惰性处理策略和前面说的预先/提前处理正好相反。就是尽量将操作(比如计算),推迟到必需执行的时刻,这样很可能避免多余的操作,甚至根本不用操作。
运用这一策略最有名的例子就是COWCopy On Write写时复制。假设多个线程都想操作一份数据一般情况下每个线程可以自己拷贝一份放到自己的空间里面。但是拷贝的操作很费时间。系统如果采用惰性处理就会将拷贝的操作推迟。如果多个线程对这份数据只有读的请求那么同一个数据资源是可以共享的因为“读”的操作不会改变这份数据。当某个线程需要修改这一数据时写操作系统就将资源拷贝一份给该线程使用允许改写这样就不会影响别的线程。
COW最广为人知的应用场景有两个。一个是Unix系统fork调用产生的子进程共享父进程的地址空间只有到某个子进程需要进行写操作才会拷贝一份。另一个是高级语言的类和容器比如Java中的CopyOnWrite容器用于多线程并发情况下的高效访问。
## 并行/异步操作
优化策略的第三大类是“并行/异步操作”。并行和异步两种操作虽然看起来很不一样,其实有异曲同工之妙,就是都把一条流水线和处理过程分成了几条,不管是物理上分还是逻辑上分。
### 5.并行操作
并行操作是一种**物理上**把一条流水线分成好几条的策略。直观上说一个人干不完的活那就多找几个人来干。并行操作既增加了系统的吞吐量又减少了用户的平均等待时间。比如现代的CPU都有很多核每个核上都可以独立地运行线程这就是并行操作。
并行操作需要我们的程序有扩展性,不能扩展的程序,就无法进行并行处理。这里的并行概念有不同的粒度,比如是在服务器的粒度(所谓的横向扩展),还是在多线程的粒度,甚至是在指令级别的粒度。
绝大多数互联网服务器要么使用多进程要么使用多线程来处理用户的请求以充分利用多核CPU。另外一种情况就是在有IO阻塞的地方也是非常适合使用多线程并行操作的因为这种情况CPU基本上是空闲状态多线程可以让CPU多干点活。
### 6.异步操作
异步操作这一策略和并行操作不同,这是一种**逻辑上**把一条流水线分成几条的策略。
我们首先在编程的领域澄清一下概念:同步和异步。同步和异步的区别在于一个函数调用之后,是否直接返回结果。如果函数挂起,直到获得结果才返回,这是同步;如果函数马上返回,等数据到达再通知函数,那么这就是异步。
我们知道Unix下的文件操作是有block和non-block的方式的有些系统调用也是block式的Socket下的select等。如果我们的程序一直是同步操作那么就会非常影响性能。采用异步操作的话虽然稍微增加一点程序的复杂度但会让性能的吞吐率有很大提升。
现代的语言往往对异步操作有比较好的支持,使得异步编程变得更加简单,可读性也更好。
## 缓存/批量合并
“缓存/批量合并”是优化策略中的第四大类。缓存和批量合并这两个策略,有些场景下会同时起作用,所以我把它们放在一起。
### 7.缓存数据
缓存的本质是加速访问。这是一个用得非常普遍的策略几乎体现在计算机系统里面每一个模块和领域CPU、内存、文件系统、存储系统、内容分布、数据库等等都会遵循这样的策略。
我们最熟悉的应该就是CPU的各级缓存了。在文件系统、存储系统和数据库系统里面也有快速缓存来存储经常访问的数据目的是尽量提高缓存命中率从而避免访问比较慢的存储介质。
对于一个基于Web的应用服务前端会有浏览器缓存有CDN存放在边缘服务器上有反向代理提供的静态内容缓存后端则还会有服务器本地缓存。
程序设计中,对于可能重复创建和销毁,且创建销毁代价很大的对象(比如套接字和线程),也可以缓存,对应的缓存形式,就是连接池和线程池等。
对于消耗较大的计算,也可以将计算结果缓存起来,下次可以直接读取结果。比如对递归代码的一个有效优化手段,就是缓存中间结果。
### 8.批量合并处理
在有IO比如网络IO和磁盘IO的时候合并操作和批量操作往往能提升吞吐量提高性能。
我们最常见的是批量IO读写。就是在有多次IO的时候可以把它们合并成一次读写数据。这样可以减少读写时间和协议负担。比如GFS写文件的时候尽量批量写以减少IO开销。
对数据库的读写操作,也可以尽量合并。比如,对键值数据库的查询,最好一次查询多个键,而不要分成多次。
涉及到网络请求的时候网络传输的时间可能远大于请求的处理时间因此合并网络请求也很有必要。上层协议呢端到端对话次数尽量不要太频繁Chatty否则的话总的应用层吞吐量不会很高。
## 更先进算法和数据结构
优化策略中的最后一个大类就是“更先进算法和数据结构”。这两个策略是紧密配合的,比如先进的算法有时候会需要先进的数据结构;而且它们往往和程序的设计代码直接相关,所以放在一起。
### 9.先进的算法
同一个问题,肯定会有不同的算法实现,进而就会有不同的性能。比如各种排序算法,就是各有千秋。有的实现可能是时间换空间,有的实现可能是空间换时间,那么就需要根据你自己的实际情况做权衡。
对每一种具体的场景(包括输入集合大小、时间空间的要求、数据的大小分布等),总会有一种算法是最适合的。我们需要考虑实际情况,来选择这一最优的算法。
### 10.高效的数据结构
和算法的情况类似,不同的数据结构的特性,也是千差万别。
没有一个数据结构是在所有情况下都是最好的比如你可能经常用到的Java里面列表的各种实现包括各种口味的List、Vector、LinkedList它们孰优孰劣取决于很多个指标添加元素、删除元素、查询元素、遍历耗时等等。我们同样要权衡取舍找出实际场合下最适合的高效的数据结构。
## 总结
各种性能问题的解决,需要采用一些策略;而且不同的人和不同的场景中,会采用有时相同有时迥异的策略,恰如韩愈所说的“草树知春不久归,百般红紫斗芳菲”。但花草树木争奇斗艳,说到底是因为“知春不久归”。
<img src="https://static001.geekbang.org/resource/image/ad/c2/adf36cb5087d32aecb3d9872f093cbc2.png" alt="">
同样的道理,这些性能优化策略,有时候很容易想到,有时候并不是那么直观。所以,我们需要系统地有层次地思考,而这一讲就是帮助你建立这样的思路。
通过总结十大策略,希望你可以多从不同角度,思考同一个问题;有时候一个问题看似无解,但多方位思考,可能会突然发现非常好的解决方案。
陆游曾经说:“山重水复疑无路,柳暗花明又一村”。我们做性能优化的时候,也会经常有这样的感觉的。
## 思考题
这十大策略也许你已经在工作中使用过,你曾经用过哪些呢?你自己归纳过它们吗?
你现在正在使用的编程语言,有没有对一种数据结构(比如列表,集合)提供了很多种不同的实现方法,它们之间在不同场景下的性能对比如何?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="21 | CPU案例如何提高LLC最后一级缓存的命中率" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/48/23ca1986b418b75c84f2a41d1cfc8c48.mp3"></audio>
你好,我是庄振运。
前面两讲中,我介绍了性能优化的六大原则和十大策略。从今天开始,我们来通过具体案例的解决方案讲解,了解这些原则和策略是如何应用的。
首先我们要来探讨的是一个CPU相关的性能优化案例。
这个性能案例是关于CPU的最后一级缓存的。你应该知道最后一级缓存一般也就是L3如果命中率不高的话对系统性能会有极坏的影响相关基础知识建议回顾[第15讲](https://time.geekbang.org/column/article/183357))。所以对这一问题,我们要及时准确地监测、暴露出来。
至于具体解决方案,我这里建议采取三种性能优化策略,来提高最后一级缓存的命中率。分别是:**紧凑化数据结构**、**软件预取数据**和**去除伪共享缓存**。它们分别适用于不同的情况。
## 性能问题最后一级缓存LLC不命中率太高
一切问题的解决都要从性能问题开始入手,我们首先来看看**最后一级缓存不命中率太高**这个性能问题本身。
缓存的命中率是CPU性能的一个关键性能指标。我们知道CPU里面有好几级缓存Cache每一级缓存都比后面一级缓存访问速度快。最后一级缓存叫LLCLast Level CacheLLC的后面就是内存。
当CPU需要访问一块数据或者指令时它会首先查看最靠近的一级缓存L1如果数据存在那么就是缓存命中Cache Hit否则就是不命中Cache Miss需要继续查询下一级缓存。
缓存不命中的比例对CPU的性能影响很大尤其是最后一级缓存的不命中时对性能的损害尤其严重。这个损害主要有两方面的性能影响
第一个方面的影响很直白,就是**CPU的速度**受影响。我们前面讲过内存的访问延迟是LLC的延迟的很多倍比如五倍所以LLC不命中对计算速度的影响可想而知。
第二个方面的影响就没有那么直白了,这方面是关于**内存带宽**。我们知道如果LLC没有命中那么就只能从内存里面去取了。LLC不命中的计数其实就是对内存访问的计数因为CPU对内存的访问总是要经过LLC不会跳过LLC的。所以每一次LLC不命中就会导致一次内存访问反之也是成立的每一次内存访问都是因为LLC没有命中。
更重要的是我们知道一个系统的内存带宽是有限制的很有可能会成为性能瓶颈。从内存里取数据就会占用内存带宽。因此如果LLC不命中很高那么对内存带宽的使用就会很大。内存带宽使用率很高的情况下内存的存取延迟会急剧上升。更严重的是最近几年计算机和互联网发展的趋势是后台系统需要对越来越多的数据进行处理因此**内存带宽越来越成为性能瓶颈**。
## LLC不命中率的测量
针对LLC不命中率高的问题我们需要衡量一下问题的严重程度。在Linux系统里可以用Perf这个工具来测量LLC的不命中率在[第15讲](https://time.geekbang.org/column/article/183357)中提到过)。
那么Perf工具是怎么工作的呢
它是在内部使用性能监视单元也就是PMUPerformance Monitoring Units硬件来收集各种相关CPU硬件事件的数据例如缓存访问和缓存未命中并且不会给系统带来太大开销。 这里需要你注意的是PMU硬件是针对每种处理器特别实现的所以支持的事件集合以及具体事件原理在处理器之间可能有所不同。
PMU尤其可以监测LLC相关的指标数据比如LLC读写计数、LLC不命中计数、LLC预先提取计数等指标。具体用Perf来测量LLC各种计数的命令格式是
```
perf stat -e LLC-loads,LLC-load-misses,LLC-stores,LLC-store-misses
```
下图显示的是一次Perf执行结果。
<img src="https://static001.geekbang.org/resource/image/15/1e/15211eb7e1e6da1d46b66e7cebdf1e1e.png" alt="">
我们可以看到在这段取样时间内有1951M19.51亿次LLC的读取大约16%是不命中。有313M3.13亿次LLC的写入差不多24%是不命中。
## 如何降低LLC的不命中率
那么如何降低LLC的不命中率也就是提高它的命中率呢根据具体的问题至少有三个解决方案。而且这三个方案也不是互相排斥的完全可以同时使用。
第一个方案,也是最直白的方案,就是**缩小数据结构**,让数据变得紧凑。
这样做的道理很简单对一个系统而言所有的缓存大小包括最后一级缓存LLC都是固定的。如果每个数据变小各级缓存自然就可以缓存更多条数据也就可以提高缓存的命中率。这个方案很容易理解。
举个例子开源的C++ [Folly库](https://github.com/facebook/folly/tree/master/folly)里面有很多类比如F14ValueMap就比一般的标准库实现小很多从而占用比较少的内存采用它的话自然缓存的命中率就比较高。
第二个方案,是**用软件方式来预取数据**。
这个方案也就是通过合理预测,把以后可能要读取的数据提前取出,放到缓存里面,这样就可以减少缓存不命中率。“用软件方式来预取数据”理论上也算是一种“**用空间来换时间**”的策略(参见[第20讲](https://time.geekbang.org/column/article/187540)),因为付出的代价是占用了缓存空间。当然,这个预测的结果可能会不正确。
第三个方案,是具体为了解决一种特殊问题:就是伪共享缓存。伪共享缓存这个问题,我会在后面详细讲到。这个方案也算是一种“**空间换时间**”的策略,是通过让每个数据结构变大,牺牲一点存储空间,来解决伪共享缓存的问题。
除了最直白的缩小数据结构,另外两个解决方案(用软件方式来预取数据、去除伪共享缓存)都需要着重探讨。
### 软件提前预取指令
我们先展开讨论一下第二种方案,也就是用软件提前预取指令。
现代CPU其实一般都有**硬件指令**和**数据预取**功能,也就是根据程序的运行状态进行预测,并提前把指令和数据预取到缓存中。这种硬件预测针对连续性的内存访问特别有效。
但是在相当多的情况下,程序对内存的访问模式是随机、不规则的,也就是不连续的。硬件预取器对于这种随机的访问模式,根本无法做出正确的预测,这就需要使用**软件预取**。
软件预取就是这样一种预取到缓存中的技术以便及时提供给CPU减少CPU停顿从而降低缓存的不命中率也就提高了CPU的使用效率。
现代CPU都提供相应的预取指令具体来讲Windows下可以使用VC++提供的_mm_prefetch函数Linux下可以使用GCC提供的__builtin_prefetch函数。GCC提供了这样的接口允许开发人员向编译器提供提示从而帮助GCC为底层的编译处理器产生预取指令。这种策略在硬件预取不能正确、及时地预取数据时极为有用。
但是软件预取也是有代价的。
一是预取的操作本身也是一种CPU指令执行它就会占用CPU的周期。更重要的是预取的内存数据总是会占用缓存空间。因为缓存空间很有限这样可能会踢出其他的缓存的内容从而造成被踢出内容的缓存不命中。如果预取的数据没有及时被用到或者带来的好处不大甚至小于带来的踢出其他缓存相对应的代价那么软件预取就不会提升性能。
我自己在这方面的实践经验,有这么几条:
1. 软件预取最好只针对绝对必要的情况就是对会实际严重导致CPU停顿的数据进行预取。
1. 对于很长的循环(就是循环次数比较多),尽量提前预取后面的两到三个循环所需要的数据。
1. 而对于短些的循环(循环次数比较少),可以试试在进入循环之前,就把数据提前预取到。
### 去除伪共享缓存
好了,我们接着来讨论第三个方案:去除伪共享缓存。
什么是伪共享缓存呢?
我们都知道内存缓存系统中一般是以缓存行Cache Line为单位存储的。最常见的缓存行大小是64个字节。现代CPU为了保证缓存相对于内存的一致性必须实时监测每个核对缓存相对应的内存位置的修改。如果不同核所对应的缓存其实是对应内存的同一个位置那么对于这些缓存位置的修改就必须轮流有序地执行以保证内存一致性。
但是,这将导致核与核之间产生竞争关系,因为一个核对内存的修改,将导致另外的核在该处内存上的缓存失效。在多线程的场景下就会导致这样的问题。当多线程修改看似互相独立的变量时,如果这些变量共享同一个缓存行,就会在无意中影响彼此的性能,这就是**伪共享**。
你可以参考下面这张Intel公司提供的图两个线程运行在不同的核上每个核都有自己单独的缓存并且两个线程访问同一个缓存行。
<img src="https://static001.geekbang.org/resource/image/42/c9/42be053ba1c46fece881b97b1f328ac9.png" alt="">
如果线程0修改了缓存行的一部分比如一个字节那么为了保证缓存一致性这个核上的整个缓存行的64字节都必须写回到内存这就导致其他核的对应缓存行失效。其他核的缓存就必须从内存读取最新的缓存行数据。这就造成了其他线程比如线程1相对较大的停顿。
这个问题就是**伪共享缓存**。之所以称为“伪共享”,是因为,单单从程序代码上看,好像线程间没有冲突,可以完美共享内存,所以看不出什么问题。由于这种冲突性共享导致的问题不是程序本意,而是由于底层缓存按块存取和缓存一致性的机制导致的,所以才称为“伪共享”。
我工作中也观察到好多次这样的伪共享缓存问题。经常会有产品组来找我们,说他们的产品吞吐量上不去,后来发现就是这方面的问题。所以,我们开发程序时,不同线程的数据要尽量放到不同的缓存行,避免多线程同时频繁地修改同一个缓存行。
举个具体例子,假如我们要写一个多线程的程序来做分布式的统计工作,为了避免线程对于同一个变量的竞争,我们一般会定义一个数组,让每个线程修改其中一个元素。当需要总体统计信息时,再将所有元素相加得到结果。
但是如果这个数组的元素是整数因为一个整数只占用几个字节那么一个64字节的缓存行会包含多个整数就会导致几个线程共享一个缓存行产生“伪共享”问题。
这个问题的解决方案,是**让每个元素单独占用一个缓存行**比如64字节也就是按缓存行的大小来对齐Cache Line Alignment。具体方法怎么实现呢其实就是插入一些无用的字节Padding。这样的好处是多个线程可以修改各自的元素和对应的缓存行不会存在缓存行竞争也就避免了“伪共享”问题。
## 总结
这一讲我们介绍了CPU方面的优化案例重点讨论了**如何降低LLC的缓存不命中率**。我们提出了三个方案,分别是紧凑化数据、软件指令预取和去除伪共享缓存。
<img src="https://static001.geekbang.org/resource/image/fc/ba/fc86555b197673e40096980571af44ba.png" alt="">
尤其是第三个方案解决的伪共享缓存问题,对大多数程序员和运维人员而言,不太容易理解。为什么难理解?是因为它牵扯了软件(比如多线程)和硬件(比如缓存一致性和缓存行的大小)的交互。
当多线程共用同一个缓存行,并且各自频繁访问时,会导致严重的称为“伪共享”的性能问题。这种问题,恰如清代词人朱彝尊的两句词,“共眠一舸听秋雨,小簟轻衾各自寒”。所以需要我们狠狠心,把它们强行分开;“棒打鸳鸯”,让它们“大难临头各自飞”,其实呢,是为了它们都好。
## 思考题
硬件指令预取的基本原理是什么?为什么有时候非常有效,但有时候会有害呢?分别试举出一个具体的模块开发中的例子,来说明为什么有效和有害。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,155 @@
<audio id="audio" title="22 | 系统案例如何提高iTLB指令地址映射的命中率" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/81/80/813b746df3bd4337203baf624fe26a80.mp3"></audio>
你好,我是庄振运。
我们今天继续探讨性能优化的实践介绍一个系统方面的优化案例。这个案例涉及好几个方面包括CPU的使用效率、地址映射、运维部署等。
开发项目时当程序开发完成后生成的二进制程序需要部署到服务器上并运行。运行这个程序时我们会不断衡量各种性能指标。而生产实践中我们经常发现一个问题是指令地址映射的不命中率太高High iTLB miss rate导致程序运行不够快。我们今天就探讨这个问题。
在我过去的生产实践中,针对这一问题,曾经采取的一个行之有效的解决方案,就是同时进行**二进制程序的编译优化**和**采用大页面的部署优化**。我下面就详细地分享这两个优化策略,并介绍如何在公司生产环境中,把这两个策略进行无缝整合。
## 为什么要关注指令地址映射的不命中率?
我们先来看看**为什么需要关注iTLB的命中率**。
在以往从事的性能工作实践中我观察到CPU资源是最常见的性能瓶颈之一因此提高CPU性能一直是许多性能工作的重点。
导致CPU性能不高的原因有很多其中有一种原因就是**较高的iTLB不命中率**。这里的iTLB就是Instruction Translation Lookaside Buffer也就是**指令转换后备缓冲区**。iTLB命中率不高就会导致CPU无法高效运行。
那么TLB转换后备缓冲区又起到了什么作用呢
我们知道在虚拟内存管理中内核需要维护一个地址映射表将虚拟内存地址映射到实际的物理地址对于每个内存里的页面操作内核都需要加载相关的地址映射。在x86计算体系结构中是用内存的页表page table来存储虚拟内存和物理内存之间的内存映射的。但是内存页表的访问相对于CPU的运算速度那是远远不够快的。
所以为了能进行快速的虚拟到物理地址转换TLB转换后备缓冲区这种专门的硬件就被发明出来了它可以作为内存页表的缓存。TLB有两种**数据TLB**Data和**指令TLB**Instruction也就是iTLB和dTLB因为处理器的大小限制这两者的大小也就是条目数都不是很大只能存储为数不多的地址条目。
为什么说TLB的命中率很重要呢
这是因为内存页表的访问延迟比TLB高得多因此命中TLB的地址转换比未命中TLB也就快得多。因为CPU无时无刻不在执行指令所以iTLB的性能尤其关键。iTLB未命中率是衡量因iTLB未命中而导致的性能损失的度量标准。当iTLB未命中率很高时CPU将花费大量周期来处理未命中这就导致指令执行速度变慢。
具体来讲iTLB命中和不命中之间的访问延迟差异可能是10到100倍。命中的话仅需要1个时钟周期而不命中就需要10-100个时钟周期因此iTLB不命中的代价是极高的。
我们可以用具体的数据来感受一下。假设这两种情况分别需要1和60个时钟周期未命中率为1将导致平均访问延迟为1.59个周期相比全部命中的情况即1个周期的访问延迟足足高出59
## 如何提高指令地址映射的命中率?
对于iTLB命中率不高的系统如果能提高命中率可以大大提高CPU性能并加快服务运行时间。我们在生产过程中实践过两种方案下面分别介绍。
第一种方案是优化软件的二进制文件来减少iTLB不命中率。
一般而言根据编译源代码的不同阶段即编译、链接、链接后等阶段分别存在三种优化方法。这样的例子包括优化编译器选项来对函数进行重新排序以便将经常调用的所谓“热函数”放置在一起或者使用FDOFeedback-Directed Optimization就是基于反馈的优化来减少代码区域的大小。
FDO是什么呢简单来说就是把一个程序放在生产环境中运行剖析真实的生产数据并且用这些信息来对这个程序进行精准地优化。比如可以确切地知道在生产环境中每个函数的调用频率。
那么要如何进行二进制的优化呢?
我们可以通过编译优化来将频繁被访问的指令汇总到一起放在二进制文件中的同一个地方以提高空间局部性这样就可以提高iTLB命中。这块放置频繁访问指令的区域就叫**热区域**Hot Text
在热区域的指令,它们的提取和预取会更快地完成。还记得我们在[第4讲](https://time.geekbang.org/column/article/174462)学过的**帕累托法则**吗?根据对许多服务的研究,帕累托法则在这里依然适用。
通常情况下有超过80的代码是“冷的指令”其余的是“热指令”。通过将热指令与冷指令分开昂贵的微体系结构资源比如iTLB和缓存就可以更有效地处理二进制文件的热区域从而提高系统性能。
具体的二进制优化过程,包括以下三个大体步骤:
首先是通过分析正在运行的二进制文件来识别热指令。我们可以用Linux的perf工具来达成此目的。你有两种方法来进行识别可以使用堆栈跟踪也可以使用[LBR](https://lwn.net/Articles/680985/)Last Branch Record最后分支记录。LBR比较适宜是因为它的好处是能提高数据质量并减少数据占用量。
其次,根据函数的访问频率,对配置文件函数进行排序。我们可以使用名为[HFSort](https://github.com/facebook/hhvm/tree/master/hphp/tools/hfsort)的工具,来为热函数创建优化表单。
最后,链接器脚本将根据访问顺序,优化二进制文件中的函数布局。
这些步骤执行完毕后的结果就是一个优化的二进制文件。我说明一下,如果这里面提到的工具你没有用过,也没有关系。这里知道大体原理就行了,当你真正用到的时候,可以再仔细去研究。
第二种方案就是采用大页面。什么是大页面呢?
现代计算机系统除了传统的4KB页面大小之外通常还支持更大的页面大小比如x86_64上分别为2MB和1GB。这两种页面都称为大页面。使用较大的页面好处是减少了覆盖二进制文件的工作集所需的TLB条目数从而用较少的页面表就可以覆盖所有用到的地址也就相应地降低了采用页面表地址转换的成本。
在Linux上有两种获取大页面的方法
1. 手工:预先为应用程序预留大页面;
1. 自动:使用透明大页面,也就是[THP](https://www.kernel.org/doc/Documentation/vm/transhuge.txt)Transparent Huge Pages
THP就像名字一样是由操作系统来自动管理大页面不需要用户去预留大页面。THP的显著优点是**不需要对应用程序做任何更改**;但是也有缺点,就是**不能保证大页面的可用性**。预留大页面的方式则需要在启动内核时应用配置。假如我们想保留64个大页面每个2MB就用下面的配置。
```
hugepagesz = 2MB hugepages = 64
```
我们在服务器上运行程序时需要将相应的二进制文件加载到内存中。二进制文件由一组函数指令组成它们共同位于二进制文件的文本段中每个页面都尝试占用一个iTLB条目来进行虚拟到物理页面的转换。
如果内存页比如4KB很小那么对于一定大小的程序需要加载的内存页就会较多内核会加载更多的映射表条目而这会降低性能。通常在执行过程中我们使用4KB的普通页面。如果使用“大内存页”页面变大了比如2MB是4KB的512倍自然所需要的页数就变少了也就大大减少了由内核加载的映射表的数量。这样就提高了内核级别的性能最终提升了应用程序的性能。这就是大页面为什么会被引入的原因。
由于服务器通常只有数量有限的iTLB条目如果文本段太大大于iTLB条目可以覆盖的范围则会发生iTLB不命中。
例如,[Intel HasWell](https://en.wikipedia.org/wiki/Haswell_(microarchitecture))架构中4KB页面有128个条目每个条目覆盖4KB总共只能覆盖512KB大小的文本段。如果应用程序大于512KB就会有iTLB不命中从而需要去访问内存的地址映射表这就比较慢了。iTLB未命中的处理是计入CPU使用时间的所以等待访问内存地址映射的过程就实际上浪费了CPU时间。
我们提出的第二个方案就是使用大页面来装载程序的热文本区域。通过在大页面上放置热文本可以进一步提升iTLB命中率。使用大页面iTLB条目时单个TLB条目覆盖的代码是标准4K页面的512倍。
更重要的是当代的CPU体系结构通常为大页面提供一些单独的TLB条目如果我们不使用大页面这些条目将处于空闲状态。所以通过使用大页面也可以充分利用那些TLB条目。
## 如何获得最佳优化结果?
我们总共提出了两个方案,就是**采用热文本**和**采用大页面放置**。这两个其实是互补的优化方案,它们可以独立工作,也可以整合起来一起作用,这样可以获得最佳的优化结果。
采用热文本和大页面放置的传统方法需要多个步骤,比如在链接阶段,将源代码和配置文件数据混合在一起,并进行各种手动配置和刷新,这就导致整个过程非常复杂。这样的整合方案也就很难广泛应用到所有的系统中。
我们在生产中构建了一个流程来自动化整个过程。这样该解决方案就成为能被几乎所有服务简单采用的方案而且几乎是免维护的解决方案。这个解决方案的流程图如下整个系统包含三大模块程序剖析Profiling、编译链接Linking和加载部署loading
<img src="https://static001.geekbang.org/resource/image/50/f4/50307c9f2a002e2063a632c8dddf13f4.png" alt="">
### 程序剖析
剖析模块显示在图的顶部。这个模块定期,比如每周执行一次数据收集作业,以剖析测量正在运行的服务。
Dataswarm是我们曾经采用的数据收集框架这是Facebook自己开发和使用的数据存储和处理的解决方案。这个作业剖析了服务的运行信息例如热函数并且对配置文件进行控制以使其开销很小。最后它会把分析好的数据发送到名为[Everstore](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-muralidhar.pdf)的永久存储,其实这是一个基于磁盘的存储服务。
### 编译链接
在构建服务包时链接程序脚本会从Everstore检索已配置的热函数并根据配置文件对二进制文件中的功能进行重新排序。这个模块的运行结果就是已经优化的二进制程序。
### 加载部署
加载服务二进制文件时,操作系统会尽最大努力,在大页面上放置热文本。如果没有可用的大页面,则放在常规内存页面上。
## 生产环境的性能提升
这样的解决方案实际效果如何呢?
我们曾经在Facebook的生产环境中广泛地采用这一优化策略。通过几十个互联网服务观察和测量我们发现应用程序和服务器系统的性能都得到了不错的提升应用程序的吞吐量差不多提高了15服务等待时间减少了20
我们也观察了系统级别的指标。系统级别的指标我们一般考虑主机cpu使用情况和iTLB不命中率。 iTLB的不命中率几乎降低了一半CPU使用率降低了5到10%。我们还估计在这里面约有一半的CPU使用率降低是来自热文本另一半来自大页面。
为了帮你更好的认识和体会性能的提升,我下面展示一个具体的互联网服务,在采用这个解决方案后的性能对比。
这张图显示了iTLB不命中率的变化。
<img src="https://static001.geekbang.org/resource/image/6b/96/6bb6e77b54a6d91b6ace387309fa5e96.png" alt="">
在应用该解决方案之前iTLB不命中率在峰值期间高达每百万条指令800个如蓝色线表示。采用我们的解决方案优化部署后iTLB不命中率几乎下降了一半。具体而言在峰值期间最高的iTLB不命中率降低为每百万条指令425次如黄线表示相对优化以前下降了49差不多是一半。
应用程序级别指标,显示在下图中。
<img src="https://static001.geekbang.org/resource/image/26/ce/26ff2cf5472f3fef27503107ad3161ce.png" alt="">
蓝色曲线为优化前黄色曲线为优化后。我们可以看到应用程序请求查询延迟的中位数P50下降最多25P90百分位数下降最多10P99下降最多60
应用程序吞吐量QPS如下图所示优化后增长了15也就是说峰值吞吐量从3.6万QPS增加到了4.1万QPS。你可以看到应用程序级别的吞吐量和访问延迟都得到了改善。
<img src="https://static001.geekbang.org/resource/image/c0/c3/c09f61e1ee303ac2ff01c30cfc3542c3.png" alt="">
## 总结
这一讲我们讨论了如何有效地降低指令地址映射的不命中率太高的问题。
完整的解决方案包括两部分:**编译优化**和**部署优化**。对编译的优化是进行指令级别的划分把经常访问的指令放在一起形成Hot Text区域。对程序部署的优化是采用大页面。这两个部分在真正的生产环境中可以一起使用。
<img src="https://static001.geekbang.org/resource/image/e8/79/e8286137049016140a5433d5cdbf0979.png" alt="">
唐代的诗人张籍曾经鼓励一个出身寒门的朋友说:“越女新妆出镜心,自知明艳更沉吟。齐纨未足时人贵,一曲菱歌敌万金”。最后两句的意思是,大家追求的畅销东西,比如齐国的珍贵丝绸,恰如社会上横流的物欲,虽然贵重,但是见得多了也就不足为奇了。倒是平时不流行的东西,比如一首好听的采菱歌曲,更值得人称道看重。
操作系统的内存页面管理也有类似的道理虽然普通的4KB页面容易管理几乎每个程序都在用大家已经习以为常但是在某些部署场景下大页面的使用会让系统性能大增颇有点惊艳的效果。
## 思考题
Linux操作系统中一个常用的大页面相当于多少个普通页面的大小相对于普通页面大页面有哪些优点又有哪些缺点呢
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,120 @@
<audio id="audio" title="23 | 存储案例如何降低SSD峰值延迟" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/19/68e9fe050a1a60c1cbeaf1a002630a19.mp3"></audio>
你好,我是庄振运。
我们之前讲过,存储系统的性能很关键(参见[第17讲](https://time.geekbang.org/column/article/185154)。我们这一讲就探讨存储方面的优化案例是关于SSD性能的。
现在很多公司里面的高性能存储系统一般都是基于SSD的这主要归功于SSD价格在近几年的大幅度下降。但是SSD也不是包治百病的灵丹妙药也有自己的特殊性能问题。我们今天就重点讲述两点**SSD的损耗**和**IO访问延迟偶尔过大**的问题。
这里的第二个问题可能听起来很让人吃惊不是说SSD延迟很低吗
一般情况下是的。但是特殊情况下就不一定了这个就说来话长了它和SSD的内部原理有关。我们会一步步地探讨问题形成的原因和解决的策略。
## SSD为什么会损耗
我们的[第17讲](https://time.geekbang.org/column/article/185154)是关于存储系统的讲过SSD的工作原理和性能。为了防止你忘记我们就在这里快速地回顾一下其中的一个重要概念**写入放大**。
什么是写入放大呢当写入SSD的物理数据量大于应用程序打算写入SSD的逻辑数据量时就会造成“写入放大”。
如果是传统硬盘HDD就不会有写入放大的问题。那么SSD为什么会有写入放大呢这是因为SSD内部的工作原理和硬盘很不一样。
我们知道HDD是可以直接写入覆盖的。和HDD不同SSD里面的页面只能写入一次要重写的话必须先回收擦除而且只能在“块”这个级别进行擦除。因此呢SSD内部就需要不断地移动所存储的数据来清空需要回收的块。也就是说SSD内部需要进行块级别的“垃圾回收”。垃圾收集器必须有效地在SSD内部不断地回收块回收以前使用的页面空间然后才能在这个块上写入新数据。
因此对SSD的写入需求比对HDD的写入需求更高。
写入放大的缺点是什么?就是**会更快地损耗SSD的生命**。
每个SSD都有固定数量的擦除周期如果在很短时间内写到SSD太多数据就会导致SSD损耗太快有可能过早烧坏SSD。换句话说很高的写入速率可能会导致SSD在到达其预期使用寿命之前就发生故障。
所以我们要注意一个常用的指标叫年损耗率Burn Rate。这个指标是怎么定义的呢是用SSD的预期寿命推导出来的。比如一个SSD预期寿命是4年。那么每年可以损耗25%,这就是年损耗率。
## 如何减少SSD损耗
前面讲的“写入放大”,其实也可以用一个相应的具体指标来衡量,就是“**写入放大系数**”它代表物理写入SSD的数据与应用程序写入的逻辑数据之比。比如如果写入放大系数是2就表示写入每10KB的逻辑数据SSD实际上写了20KB。为了控制SSD的年损耗率我们需要尽量降低写入放大系数。
那么如何减少写入放大系数呢?常见的方法有两种:
1. 是**保留一定的空闲存储空间**这是因为写入放大系数是和SSD存储空闲率相关的。
1. 是**使用Trim**。
这两种方法可以同时使用,我们下面分别介绍。
我们先简单说一下第一种方法。每个SSD都有一定数量的预留空间这个空间不是SSD可用容量的一部分。这样做是有原因的。尽管我们可以使用工具来调整SSD卡上的可用容量但是我不建议你减少预配置的可用空间因为这将降低写入性能并可能大大缩减SSD的使用寿命。
我们在存储数据到SSD时候也不要存得太满也就是不要追求太高的空间使用率。那么我们将SSD可用存储容量的使用率目标定为多少比较合适呢一般来说我们可以定为80至85以保持较低的写入放大率。
**SSD的空闲可用空间越多内部垃圾收集的开销就越低就越有可能降低写入放大系数。**但是这种关系不是线性的,所以存在着收益递减的问题。
第二种方法是用Trim。我首先为你讲解一下什么是Trim。
Trim是个命令是操作系统发给SSD控制器的特殊命令。使用Trim命令操作系统可以通知SSD某些页面存储的数据不再有效了。比如对于文件删除操作操作系统会将文件的扇区标记为空闲以容纳新数据然后就可以将Trim命令发送到SSD。
Trim命令有什么好处呢
SSD收到Trim命令后SSD内部的控制器会更新其内部数据页面地图以便在写入新数据时不去保留无效页面。并且在垃圾回收期间不会复制无效页面这样就实现了更有效的垃圾收集也就减少了写操作和写入放大系数同时获得了更高的写吞吐量延长了驱动器的使用寿命。
Trim命令和机制虽然看起来很美好但是实际中会产生一些问题。原因在于不同的SSD厂商对Trim命令的处理方式以及具体的垃圾回收机制很不一样有的实现还不错有的就差强人意了因此Trim的性能在每种SSD那里会有所不同。我们后面会提到有些SSD的厂商的某些SSD因为对Trim的支持不太好会造成某些情况下性能非常差。
还要注意的是默认情况下操作系统一般不启用Trim。因此当文件系统删除文件时它只是将数据块标记为“未使用”。但是SSD控制器并不知道设备上的哪些页面可用因此无法真正释放设备上的无效空间。所以在没有启动Trim的情况下一旦SSD设备的可用容量填满即使文件系统知道设备上有可用容量SSD也会认为它自己已经存满。
那么怎么启动Trim呢要在SSD上启用连续Trim必须在mount这块SSD的时候使用“Discard”安装选项。如果一块SSD已经安装了想启动Trim那就需要卸载后重新安装“Discard”选项才能生效。也就是说使用remount命令是不起作用的。
所以对于单个系统而言最好在grub中启用mount选项并重新启动。
## 想减少SSD损耗却导致访问延迟过大
Trim的使用虽然带来了**降低SSD损耗**的好处,但也带来了一些坏处,特别是**IO访问可能延迟加大**的问题。
为什么Trim会影响应用程序性能呢
原因和SSD内部的实际机制有关。每个SSD内部都有一个FTLFlash Translation Layer映射表该表将操作系统的逻辑块地址LBALogical Block Address映射到SSD上的物理页面地址PPAPhysical Page Address。映射表在驱动器被写入时不断更新以后每个读取和写入IO都要引用。
一般来说映射表是存储在SSD驱动器的RAM中以便快速访问但是它的副本也存储在SSD中目的是在电源故障时能够保留LBA到PPA的映射。随着SSD上面内容和数据的不断变化这些变化包括新写入IO或垃圾回收RAM中的映射表也不断更新并且持续写入SSD中。
如果在文件系统上启用了Discard选项那么每次删除文件时都会生成Trim命令。因为每次Trim都会更改映射表所以对映射表的更改也就实际地记录到SSD中。这项操作可能需要花费比较长的时间比如几毫秒的时间才能完成在这个更改过程中普通的数据读取和写入I /O会阻塞并且阻塞到所有的映射表调整都被完全处理为止。当今业界的大多数SSD都是这样工作的。
上面我们看到由于Trim的处理会阻塞普通的数据读取和写入I /O直到Trim完成映射表记录才返回所以Trim的延迟对普通读写I O的延迟具有重大影响尤其对高分位数比如P99、99.9)的读写IO延迟影响更大。减少Trim延迟就是减少IO延迟。所以我们需要尽量减少Trim的等待处理时间。
另外值得你注意的是每个SSD厂商和每款SSD对Trim的具体处理方式都可能不同颇有些厂商的某些SSD具有严重的问题。我们生产实践中碰到过好几种这样的SSD比如有厂商的一种SSD在大量删除数据时有很大的延迟。这就要求我们在选购SSD时候要特别小心尤其是要做彻底的性能测试。
## 如何避免Trim带来的延迟
我们刚才讲了用Trim的好处和坏处。好处是可以减少SSD的损耗延长SSD的寿命坏处是会造成应用程序的IO读写延迟变大。
那么怎么才能尽量避免Trim带来的坏处呢我们这里谈两种方式一是对Discard选项本身的调优二是使用[fstrim](http://man7.org/linux/man-pages/man8/fstrim.8.html)命令。这两种方式分别对应使用Discard被启用和不被启用的两种情况。
第一种方式是在启用了Discard后对Discard的调优。对于已经启用Discard的场景下Trim命令默认是没有大小限制也就是说一次发送会尽可能多的删除命令。但是如果一次删除的数据太多SSD可能需要很长的时间才能返回其他读写IO就会感受到很大的延迟。
那么我们就可以微调了这里我们就可以借助另外一个参数discard_max_bytes对Discard进行调优。这个参数是一个操作系统内核参数从名字也听得出它可以指定一次Trim的最大数据量。
调整这个参数的优点是可以根据实际可接受IO延迟的需要来随意微调。举个例子假如可接受IO延迟比较大那就可以设置一个较大的discard_max_byes数值比如2GB。使用这个参数的坏处是当有大文件删除时如果没有相应的重新调整参数Trim的吞吐量会受影响。
第二种方式是在没有启用Discard的场景下采用fstrim来调优。fstrim也是一个命令它可以控制Trim来删除掉SSD上的文件系统不再使用的数据。默认情况下fstrim将删除文件系统中所有未使用的块但是这个命令有其他的选项根据删除范围或大小来进行微调。
这个命令一般用于Discard没有被启用的场景下。为了达到最好的效果都是周期性的或者采用外部事件触发来运行这个命令比如用Cron来每天固定时间运行或者每当SSD存储使用率到了某个大小就运行。
下图展示了一个实际生产环境中的性能数据。
<img src="https://static001.geekbang.org/resource/image/00/76/0032afc607be8d57a6b049bd541a6576.png" alt="">
这是一个采用fstrim而降低IO延迟的例子。横轴是时间纵轴是对SSD进行读操作的IO延迟。红色箭头是运行fstrim的时间。我们可以看到在fstrim后IO延迟大幅度地降低了。
采用fstrim这个方式的优点是可以根据实际需要来决定何时运行并且更好地微调和控制Trim的工作。
这个方式也有缺点就是如果不够小心运行这个命令时可能导致很长时间的SSD读写挂起阻塞在这个阻塞的过程中SSD完全没有响应不能读写。我见过几次这样的生产例子阻塞了好几分钟甚至几个小时的时间整个SSD完全不能写入和读取数据。
## 总结
SSD不断地重写会损坏其存储能力就如同一口宝刀不断地征战砍伐后也会有缺口。这让我想起了唐代诗人马戴写的一首气势磅礴的《出塞词》“金带连环束战袍马头冲雪度临洮。卷旗夜劫单于帐乱斫胡兵缺宝刀。”
<img src="https://static001.geekbang.org/resource/image/97/b0/973542a2647ce99fb76e06a2416560b0.png" alt="">
为了延长SSD的寿命我们可以采用Trim方式以去除不必要的内部重写。
但是这种方式在某些特殊情况下会增大外部IO的访问延迟。解决这一问题的方法是对Trim进行调优。我们这一讲就集中探讨了几种调优解决方案来解决这一特殊情况下的问题。
## 思考题
你们公司部署SSD了吗有没有遇到关于损耗过大的问题以及Trim的问题和相关讨论最后采取的解决方案是什么呢
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,185 @@
<audio id="audio" title="24 | 跨层案例如何优化程序、OS和存储系统的交互" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/7f/03d05af82ee01088dd22fb482e10687f.mp3"></audio>
你好,我是庄振运。
我们前面几讲介绍了性能优化的原则和策略并且集中探讨了CPU、内存和存储三个最关键的领域。
今天我们来讲一个**比较复杂的JVM场景和超大延迟的性能问题**;这是本模块,也就是性能优化模块的最后一讲。
我们会一步步地探讨这个性能问题的表象、问题的重现、性能分析的过程和解决方案。这个性能问题的复杂性表现在它牵扯了计算机技术的很多层次——从最上层的应用程序到中间层JVM的机制再到操作系统和文件系统的特性最后还涉及到硬件存储的特点。
更重要的是这几个层次互相影响最后导致了平时我们不容易看到的严重性能问题——非常大的JVM卡顿。
今天我会把问题的核心和分析过程阐述清楚,而对于其他的一些背景和更多的性能数据,你可以参考我发表在[IEEE Cloud](https://ieeexplore.ieee.org/document/7820334)上的论文。
## 生产环境下偶尔很大的响应延迟是怎么回事?
我们先来看看这个性能问题的表象:就是在生产环境中,偶尔会出现非常大的响应延迟。
由于大多数互联网业务都是面向在线客户的例如在线游戏和在线聊天所以确保客户相应的低延迟非常重要。各种研究也都表明200毫秒延迟是多数在线用户可以忍受的最大延迟。因此确保低于200毫秒甚至更短的延迟已经成为定义的SLA服务水平协议的一部分。
鉴于Java的普及和强大功能当今的互联网服务中有很大一部分都在运行Java。Java程序的一个问题是JVM卡顿也就是大家常说的STWStop The World、JVMJava虚拟机暂停。根据我的经验尽管我们或许已经仔细考虑了很多方面来优化但Java应用程序有时仍会遇到很大的响应延迟。
这个STW的产生和JVM的运行机制是直接相关的。
Java应用程序在JVM中运行使用的内存空间叫**堆**。JVM负责管理应用程序在内存里面的对象。堆空间经常被GC回收垃圾收集这个过程是JVM操作的。Java应用程序可能在GC和JVM活动期间停止这就会给应用程序带来STW暂停。
这些GC和JVM活动信息很重要根据启动JVM时提供的JVM选项各种类型的相关信息都将记录到GC的日志文件中。
尽管某些GC引起的STW暂停众所周知比如JVM导致的Full GC但是我们在生产中发现其他因素比如**操作系统本身也会导致一些相当大的STW暂停**。
比如我举个例子。文中的图片就显示了一个STW暂停和GC日志这个暂停时间超过了11秒我就经常在生产环境中看到这样的STW暂停。
<img src="https://static001.geekbang.org/resource/image/98/21/9811fd17d7055b2f25308a36f729b921.png" alt="">
你注意一下图片中有较大的字体的那行里面显示了User、Sys和Real的计时分别对应着用户、系统和实际的计时。比如“Real=11.45”就表示实际的STW暂停是11.45秒钟。
在这个GC日志中这种暂停11秒钟的STW非常讨厌因为这样大的暂停是不能容忍的。而且这个问题很难理解完全不能用GC期间的应用程序活动和垃圾回收活动来解释。
从日志中我们也看到这个JVM的堆并不大只有4GB垃圾收集基本不会超过1秒钟。正如图中显示的那样用户和系统时间都可以忽略不计User和Sys的暂停时间分别是0.18秒钟和0.01秒钟但是实际上JVM暂停了11.45秒钟!
因此GC所做的工作量根本无法解释如此之大的暂停值。
## 搭建测试环境重现问题
为了搞清这个问题,我们做了彻底的性能分析和各种测试。为了去掉很多其他的干扰因素,以便方便根因分析,我们首先希望**实验室环境中重现该问题**,这样就比较方便从根本上解释原因。
出于**可控制性**和**可重复性**的考虑我们使用了自己设计的一个简单Java程序。为了方便对比我们根据**有没有后台背景IO活动**而测试了两种场景这里的后台背景IO就是各种磁盘IO。不存在后台IO的场景是基准场景而引入后台IO的另一场景将重现我们观测到的性能问题。
我们使用的Java程序的逻辑也很简单直白就是不断地分配和删除特定大小的对象。程序一直在不断分配对象当对象数目达到某阈值时就会删除堆中的对象。堆的大小约为1GB。每次运行固定时间是5分钟。
为了真实地模拟生产环境我们在第二种场景中注入后台IO。 这些IO由bash脚本生成该脚本就是不断复制很大的文件。在我们的实验室环境中后台工作模块能够产生每秒150MB的磁盘写入负载差不多可以使服务器配备的镜像硬盘驱动器饱和。这个应用程序和后台IO脚本的源代码在[GitHub](https://github.com/zhenyun/JavaGCworkload)上开源。
我们考虑的主要性能指标是关于应用程序的STW暂停具体考虑了两个指标
1. 总暂停时间即所有STW暂停的总暂停时间
1. 较大的STW暂停计数和。
下面让我们一起来看看结果。
场景A是基准场景Java程序在没有后台IO负载的情况下运行。我们在实验室环境中执行了许多次运行得到的结果基本是一致的。
<img src="https://static001.geekbang.org/resource/image/9e/c9/9e14b3630deefda85d0b1b4128d15fc9.png" alt="">
图片中显示的是一个持续5分钟的运行就是沿着5分钟的时间线显示了所有JVM STW暂停的时间序列数据。我们观察到所有暂停都非常小并且STW暂停都不会超过0.25秒。 STW的总暂停时间约为32.8秒。
场景B是有后台IO负载情况下运行相同的Java程序。在实际的生产过程中IO负载可能来自很多地方比如操作系统、同一机器上的其他应用程序或者来自同一Java应用程序的各种IO活动。
<img src="https://static001.geekbang.org/resource/image/d3/35/d3a781387eb9ebc20513146a63a4ad35.png" alt="">
在图片中我们同样沿着5分钟的时间线显示了所有JVM STW暂停的时间序列数据。我们发现当后台IO运行时相同的Java程序在短短5分钟的运行中看到1个STW暂停超过3.6秒3个暂停超过0.5秒结果是STW的总暂停时间为36.8秒比基准场景多了12
而STW总暂停时间多也就意味着应用程序的实际工作吞吐量比较低因为JVM多花了时间在STW暂停上面。
## 响应延迟的根本原因在哪里?
为了弄清楚STW暂停的原因我们接着进行了深入的分析。
我们发现STW大的暂停是由GC日志记录write()调用被阻塞导致的。这些write()调用虽然以缓冲写入模式即非阻塞IO发出但由于操作系统有“回写”IO的机制所以仍然可能被操作系统的“回写”IO阻塞。
操作系统的“回写”机制是什么呢?就是文件系统定期地把一些被改变了的磁盘文件,从内存页面写回存储系统。
具体来说当缓冲的write()需要写入文件时它首先需要写入OS缓存中的内存页面。这些内存页是有可能被“回写”的OS缓存机制锁定的而且当后台IO流量很重时该机制可能导致这些内存页面被锁定相当长的时间。
为了彻底查清原因我们使用Linux下的Strace工具来剖析函数调用和STW暂停的时间相关性。
<img src="https://static001.geekbang.org/resource/image/5a/4e/5a307738734b433b0d82723c016e9d4e.png" alt="">
这个图表示的是**JVM STW暂停**和strace工具报告的**JVM进行write()系统调用的延迟**。图片集中显示了一个1.59秒的JVM STW暂停的快照。
我们仔细检查了两个时间序列数据发现尽管JVM的GC日志记录使用缓冲写入但是GC暂停和write()延迟之间有极大的相关性。
这些时间序列的相关性表明由于某些原因GC日志记录的缓冲写入仍然被阻塞了。
那这个原因是什么呢我们就要通过仔细阅读分析JVM GC的日志和Strace的输出日志如下图所示来寻找。
<img src="https://static001.geekbang.org/resource/image/53/f6/53fd4368c2fc468b368f938ca3aaf2f6.png" alt="">
让我沿着时间轴线来具体解释一下图片中的数据。
在时间35.04秒时,也就是第 2 行日志一个新的JVM新生代GC启动并用了0.12秒才完成。新生代GC在35.17秒时结束并且JVM尝试发出write()系统调用第4行将新生代GC统计的信息输出到GC日志文件。write()调用被阻塞1.47秒所以最后在时间36.64第5行结束总共耗时1.47秒。当write()调用在36.64返回给JVM时JVM记录此STW暂停为1.59秒即0.12 + 1.47第3行
这些数据表明GC日志记录过程恰好位于JVM的STW暂停路径上而日志记录所花费的时间也是STW暂停的一部分。如果日志记录也就是write()调用被阻塞那么就会导致STW暂停。换句话说实际的STW暂停时间由两部分组成
1. 实际的GC时间例如新生代GC时间。
1. GC日志记录时间例如write()执行时被OS阻塞的时间。
我用下图来清楚地表示它们之间的关系。
<img src="https://static001.geekbang.org/resource/image/6f/16/6fb8cb8e982ec471bb371fb42d2c9416.png" alt="">
左边是JVM的活动右边是操作系统的活动。时间T1时垃圾回收开始T2时GC结束并开始调用write()写日志。这之间的延迟就是GC的延迟。T3时write()调用开始T4时write()调用返回。这之间的延迟就是OS导致的阻塞延迟。所以总的STW暂停就是这两部分延迟的和也就是T4-T1。
那么接下来的一个很难理解的问题是为什么非阻塞IO还会被阻塞
在深入研究各种资源包括操作系统内核源代码我们意识到非阻塞IO写入还是可能会停留并被阻塞在内核代码执行过程中。具体原因有好几个其中包括**页面写入稳定**Stable Page Writing和**文件系统日志提交**Journal Logging。下面分别说明。
JVM写入GC日志文件时首先会改变也就是“弄脏”相应的文件缓存页面。即使以后通过操作系统的写回机制将缓存页面写到磁盘文件中被改变内存中的缓存页面仍然会由于稳定的页面写入而导致**页面竞争**。
根据页面写入稳定的机制如果页面处于OS回写状态则对该页面的write()必须等待回写完成。这是为了避免将部分全新的页面保留到磁盘(会导致数据不一致),来确保磁盘数据的一致性。
对于日志文件系统在文件写入过程中会生成适当的日志。当附加到GC日志文件而需要分配新的文件块时文件系统需要首先将日志数据保存到磁盘。在日志保存期间如果操作系统具有其他IO活动则可能需要等待。如果后台IO活动繁重则等待时间可能会很长。
## 解决方案如何落地?
我们已经看到由于操作系统的各种机制的原因包括页面缓存写回、日志文件系统等JVM可能在GC日志期间被长时间阻塞。
那么怎么解决这个问题呢?
我们思考了三种方案可以缓解这个问题,分别是:
1. 修改JVM
1. 减少后台IO
1. 将GC日志记录与其他IO分开。
修改JVM是将GC日志记录活动与导致STW暂停的关键JVM GC进程分开。这样一来由GC日志阻塞引起的问题就将消失。
比如JVM可以将GC日志放到另一个线程中该线程可以独立处理日志文件的写入也就不会造成另外一个应用程序线程的STW暂停。这个方案的缺点是采用分线程方法可能会在JVM崩溃期间丢失最后的GC日志信息。
第二种方案是减少后台IO。
后台IO引起的STW暂停的程度取决于后台IO的强度。因此可以采用各种方法来降低这些IO的强度。比如在JVM应用程序运行的服务器上不要再部署其他IO密集型应用程序。
第三种方案是将GC日志与其他IO分开。
对延迟敏感的应用程序例如为交互式用户提供服务的在线应用程序通常无法忍受较大的STW暂停。这时可以考虑将GC日志记录到其他地方比如另外一个文件系统或者磁盘上。
比如这个文件系统可以是临时文件系统tmpfs一种基于内存的文件系统。它具有非常低的写入延迟的优势因为它不会引起实际的磁盘写入。但是基于tmpfs的方法存在持久性问题。由于tmpfs没有备份磁盘因此在系统崩溃期间GC日志文件将丢失。
而另一种方法是将GC日志文件放在更快的磁盘上例如SSD。我们知道就写入延迟和IOPS而言SSD具有更好的IO性能。
## 如何证明解决方案是否有效?
得出方案后,我们就需要对它们进行验证了。
在我们提出的三种方案中第一种方案也就是改进JVM是暂时难以实现的因为它需要修改JVM的实现机制。第二种方案呢是不言自明。如果没有背景IO或者有较少背景IO那么自然背景IO的影响就变小了或者减少GC日志输出的频率就不会有那么多次STW暂停了。
因此我们这里直接来验证第三种方案将GC日志记录与其他IO分开。
我们的方法是将GC日志文件放在SSD文件系统上来验证这种方案。我们运行与前面实验场景中相同的Java应用程序和后台IO负载。下图中显示了一个5分钟运行时段的所有STW暂停和相应的时间戳。
<img src="https://static001.geekbang.org/resource/image/8e/35/8e79fbff8312b961d1d9a65ab3d07335.png" alt="">
对于STW暂停的信息我们注意到所有的JVM的暂停都非常小所有暂停都在0.25秒以下。可以说延迟暂停方面的性能得到了很大的提高这表明如果这样分开GC日志文件即使有很大的后台IO负载也不会导致JVM程序发生较大的STW暂停这样的结果也就验证了这一方案的有效性。
## 总结
今天我们探讨了一个跨层的性能分析和优化案例。由于计算机几个层面的技术互相影响实际生产环境中会出现非常大的响应时间延时严重影响公司业务。这些层面包括应用程序、JVM机制、操作系统和存储系统。
<img src="https://static001.geekbang.org/resource/image/ee/7a/eee73e11928fdfbe864008124aa2957a.png" alt="">
我们通过合理的性能测试和性能分析包括搭建合适的测试环境进行问题重现、详细的根因分析最终提出了几种解决方案。简单来说就是JVM在GC时会输出日志文件写入磁盘时会因为背景IO而被阻塞。把日志文件和背景IO分开放在不同磁盘从而让它们互相不影响的话就能大幅度降低STW延迟。
从这个案例我们也可以再次了解,从事性能优化工作需要通晓几乎所有层面的知识。而且不光要知识面广,还要能深入下去,才能进行彻底的根因分析。
唐代诗人刘禹锡有一首诗说:“莫道谗言如浪深,莫言迁客似沙沉。千淘万漉虽辛苦,吹尽狂沙始到金。”对待这样复杂的性能问题,我们也需要不怕困难,不惧浪深沙沉。因为只有经过千淘万漉的辛苦,才能淘到真金,发现问题的本质并彻底解决它。
## 思考题
既然这个问题是Java应用程序偶尔发生大延迟原因是JVM垃圾回收GC的Log日志写到硬盘的文件系统引起的我们如何可以让JVM把GC日志写到内存去吗
>
Tips考虑一下JVM参数。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,118 @@
<audio id="audio" title="14 | 性能分析概述:性能问题归根结底是什么原因?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/b2/75ec3d7a277b43ec2c43e8a6388e85b2.mp3"></audio>
你好,我是庄振运。
前面几讲,我们讨论了有关性能测试的内容,了解了各种测试的种类、测试的规划、测试的工具以及执行测试的经验教训。在整个性能优化的世界里,性能测试是基础,在这一基础上,我们才能对各种性能问题进行性能分析,并进行相应的性能优化。
从这一讲开始我们来探讨一下常见的各种性能问题和如何进行相应的性能分析。我们首先宏观地介绍一下性能的外部指标、内部瓶颈和资源制约以及如何正确地进行性能分析。然后接下来的几讲分别重点讨论CPU、内存、存储和网络几个领域。
## 性能的外部指标
谈论应用程序系统和互联网服务的性能时我们首先要清楚外部的性能指标是什么。最重要也最普遍的指标有三个服务延迟Service Latency、吞吐率Throughput和资源使用率Resource Utilization。我们分别来看看。
### 服务延迟
**服务延迟**(或者叫访问延迟),指的是客户发出的请求被成功服务的时间。
虽然具体的定义和度量有很多种比如有些度量只考虑系统本身的服务时间而不考虑其它因素比如网络传输时间。但我个人坚持这个指标是直接针对客户体验的因此不能仅仅从自己的系统角度衡量而必须是端到端的延迟度量End To End, or E2E。因为只有这样来定义这一指标才能准确地抓住“外部性能”这一特点。
任何系统和服务的设计和搭建都是为了给客户来用的,如果不紧紧抓住“客户体验”这一根本目标,性能测试、性能分析,以至于性能优化也就成了无的放矢,基本失去了意义。
### 吞吐率
**吞吐率**指的是单位时间(比如每秒钟)可以成功处理的请求数或任务数。
这一指标和前面讲的访问延迟指标相辅相成,一个注重时间,就是服务延迟;一个注重空间,也就是系统容量。一个系统的外部性能主要受到这两个条件的约束,缺一不可。
比如一个在线聊天服务系统可以提供每秒钟一百万的吞吐率但是客户的访问延迟是5分钟以上那么这个“一百万的吞吐率”没啥意义。反之访问延迟很短但是吞吐率很低同样没有意义。
所以,一个系统的性能必然受到这两个条件的同时作用。
### 资源使用率
一个系统和服务总是需要软硬件容量来支撑的,那么**资源的使用率**就至关重要了。因为它直接决定了系统和服务的运营成本。
这一指标虽然主要是面向系统容量的但其实和客户也直接相关。如果资源使用率低比如CPU使用率低在系统容量固定比如服务器数目固定的情况下吞吐率也会较低或者访问延迟会较高因为系统资源没有被充分利用。
## 外部性能指标的变化
我们还需要知道,这三个性能指标的变化有它们自己的特点,而且经常会互相影响。
对一个系统而言,如果吞吐率很低,**服务延迟**往往会非常稳定。当吞吐率增高时,访问延迟一般会快速增加。
下图展示了一个有代表性的系统的吞吐率和访问延迟的变化。
<img src="https://static001.geekbang.org/resource/image/07/d8/0784f05afc0c2a37205e1b0b5827aad8.png" alt="">
在吞吐率低于每秒600时访问延迟小于5毫秒这个延迟相对稳定。然后随着吞吐率变大访问延迟飞速攀升。一般而言根据系统的延迟可接受大小我们需要控制负载流量以免访问延迟过大而影响客户体验。
我们在测量访问延迟的时候不仅要计算均值还需要注意延迟的分布情况比如有百分之几的在服务允许的范围有百分之几的略微超出了有百分之几的完全不可接受。多数情况下平均延迟达标了但是其中可能有很大比例比如20%)远远超出了我们可接受的范围。
所以我们在规定延迟标准的时候除了均值还需要定义百分位数的可接受值。比如平均延迟10毫秒P90小于30毫秒P99小于50毫秒等等。
关于**吞吐率**,现实中的系统往往有一个峰值极限。
超过这个峰值极限,系统就会超载,除了服务延迟超标,还会造成一系列的性能问题(比如系统挂掉)。这个峰值极限往往需要经过仔细的性能测试,并且结合访问延迟标准来确定。有了这个峰值极限值后,系统的设计和运维就需要确保系统的负载不要超过这个值。
除了影响运营成本和系统容量,**资源使用率**的标准也需要考虑其他几个重要因素。
一个因素是意外事件的缓冲Buffer和灾难恢复Disaster Recovery, or DR。一个现实世界中的系统随时都会有意外事件比如流量波动或者部分网络故障这就需要整个系统资源保留一定的缓冲来应付这些意外和从发生的灾难中恢复。比如CPU的使用率虽然理论上可以到100%但考虑这些因素实际的使用率指标往往远远低于100%。
## 性能问题归根结底是某个资源不够
所有的性能问题虽然表现方式各异归根结底都是某种资源受到制约不够用了。这里的资源指的是一个计算机系统程序和互联网服务会用到的每一种资源比如CPU、网络等。换句话说客户的请求在处理时在某个地方“卡住了”。这个卡住的地方就叫“瓶颈”或者叫卡点Choke point
根据我的经验,我在下面这张图表中展示了一个系统常见的十大瓶颈,基本上覆盖了所有可能出现性能问题的地方。
<img src="https://static001.geekbang.org/resource/image/ba/d8/ba67d606c4d8246075779100502308d8.png" alt="">
这十大瓶颈可以大致分为四类,在后面的几讲中我们会详细讨论分析每一类别里面的具体性能问题。这四类是:
1. 软件系统:包括操作系统、应用程序、各种类库以及文件系统。
1. CPU和内存包括CPU性能、QPIQuickPath Interconnect处理器之间的快速通道互联和缓存内存。
1. 存储和外部IO包括处理器的IO的接口性能、各种存储系统尤其是HDD和SSD性能
1. 网络包括服务器到机柜交换机的网络、数据中心的网络、CDN和互联网。
总体上来讲,性能分析的目的,是提供高性能、低延迟、高效率的服务。
要实现这一目的,就需要找到系统和服务的性能瓶颈,然后尽可能的消除瓶颈,或者降低瓶颈带来的影响。系统和服务有性能瓶颈就说明这个地方的资源不够用了。所谓最大的性能瓶颈,就是说这个地方的资源短缺程度最大,相对而言,其他地方的资源有富余。
如何找到最大的性能瓶颈?
这就需要进行性能测试和性能分析了。性能分析时需要知道三个层次的知识:
第一个层次是可能的性能瓶颈,比如我们刚刚讨论的十大瓶颈。知道了瓶颈才能有目标的去分析。
第二个层次是每个瓶颈有哪些资源有可能短缺。比如内存就有很多种不同的资源,不仅仅是简单的内存大小。除了内存使用量,还有内存带宽和内存访问延迟。
第三个层次是对每个瓶颈的每种资源要了解它和其他模块是如何交互的,对整个系统性能是如何影响的,它的正常值和极限值是多少,如何分析测量等等。
找到性能最大瓶颈后,具体的优化方式就是什么资源不够就加什么资源,同时尽量降低资源消耗,这样就可以做到在资源总量一定的情况下,有能力支撑更高的吞吐率和实现更低的延迟。
## 依据数据和剖析Profiling来分析
做性能分析时,必须采用科学的方法,尽量依据数据,来引导我们的分析和验证我们的推论,而不是完全凭空猜测。
当我们有了比较多的性能分析和优化的经验后,慢慢就会对一个系统的内部各个模块的交互,以及各种性能问题肚里有数了。这种时候,适度地做一些理论推测是合理的。就像一个有经验的医生,往往稍微了解一下病人的情况,就猜个八九不离十。这就是经验的重要性。
不过,再有经验的医生,还是需要做进一步的检验,尤其是面对复杂的病人和病情。同样的,无论性能分析的经验多丰富,我们也需要谨慎地做性能测试和数据分析,尤其是在针对重要系统的时候。
这一点可以说是性能分析和优化的第一原则。当我们怀疑性能有问题的时候应该通过合理的测试、日志分析并作合适的剖析Profillig来分析出哪里有问题从而有的放矢而不是凭感觉、撞运气。
比如如果是CPU相关的性能问题。按照我们学过的帕累托80/20定律系统绝大多数的时间应该都耗费在少量的代码片段里面。如何找出这些需要优化的代码呢唯一可靠的办法就是profile。现代流行的各种编程语言比如Java和Python等都有相关的profile工具。所以会熟练使用这些profile工具是性能分析和优化的必要条件。
我们举几个例子比如Java语言就有很多工具像JVMTIJVM Tools InterfaceJVM工具接口就为性能分析器提供了方便的钩子可以用来跟踪诸如函数调用、线程相关的事件、类加载之类的事件。再比如对Python语言来说我们可以用sys.setprofile函数跟踪Python的函数调用返回异常等事件。
## 总结
我们的儒家思想提倡“格物致知”,就是说要深入探究事物的原理,而从中获得知识和智慧。性能优化能否成功,也需要探究一个系统中性能的真正问题,找到性能的最大瓶颈。
<img src="https://static001.geekbang.org/resource/image/61/af/613abf15528375121c11570320080eaf.png" alt="">
格物致知时还需要“正心“和”诚意”就是要实事求是和端正态度从而科学而系统地收获知识。我们做性能分析时候也是需要根据实际的数据和Profiling等测试结果找到性能的瓶颈并合理地解决性能问题。
## 思考题
你工作中有没有碰到没有搞明白的性能问题?如果有,想想能不能按照今天讲的几个可能的性能问题领域来一个个考虑并验证一下?说不定会有“守得云开见月明”的恍然大悟呢。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,136 @@
<audio id="audio" title="15 | CPU篇如何让CPU的运行不受阻碍" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ce/60/cedd5a8624ba5169aa3e4661de8ba360.mp3"></audio>
你好,我是庄振运。
从这一讲开始我们探讨分析几个最容易发生性能问题的领域CPU、内存、存储和网络。
这一讲先来讨论关于CPU的常见性能问题。首先我们从硬件的角度来看看CPU的性能取决于哪些因素然后分析一下CPU的内部结构。接着我们探讨和CPU性能相关的软件系统看看CPU运行时侯的调度和切换。
## CPU的性能决定因素
宏观来讲一台服务器里面的CPU性能取决于好几个因素包括有多少处理器、多少个核、时钟主频是多少、有没有Turbo模式、处理器内部的运算架构以及和CPU紧密交互的其他部件的性能。
CPU的更新换代很频繁基本上每两年就会更新一代。比如Intel的CPU最近10年已经经历了5代左右。每一代都有主频的变化而且有好几个变种。
下面的表格描述了从十年前也就是2009年的SandyBridge到后来的IvyBridge、Haswell、Broadwell直到Skylake。注意对后面的三代我分别列出了其中的两种变化——单处理器1P和双处理器2P
<img src="https://static001.geekbang.org/resource/image/0c/09/0ccfee296bbd3ab792b41ee0feb26209.png" alt="">
大体上我们可以看出虽然CPU更新换代但是处理器的时钟主频基本不再提高甚至变得更低了。这样的目的是降低CPU的功耗。比如SandyBridge的时钟频率是2.6GHz但是到了Skylake反而降低到了2GHz。
为了提升单个处理器的性能每个处理器里面的核数却越来越多这样就可以尽量的提升并行处理能力。比如SandyBridge的每个处理器只有8个核而Skylake则多达20个核。
而且我们也看到每一代CPU都允许Turbo模式就是让CPU的主频提高。目的是可以让处理器在特殊情况下用提高功耗的代价来增加主频从而获得更高性能。
## CPU的内部结构
CPU的性能也取决于它的内部结构设计。很多程序员对CPU的内部机构不是完全清楚尤其是对相关的术语之间的区别和联系一知半解比如多处理器和多核、逻辑CPU和硬件线程、超线程以及L1/L2/L3三级缓存等。
之所以对这些结构不甚了解主要原因是现代处理器变得复杂普遍采用多处理器多核以及内部的各种优化处理来提高CPU性能。我们今天就从外到内从宏观到微观地介绍一下。
我注意到,这方面的很多中文术语,大家有时候用法不一致,所以很容易混淆。为了清楚描述,你尤其要注意一下我用的术语(包括英文)。
### 多处理器和NUMA
现在的CPU普遍采用多处理器Socket来提高CPU性能每个处理器都有自己可以直接访问的本地内存Local Memory。一般来讲这里面每个处理器的性能和内存大小都是一样的。每个处理器也都可以访问其他处理器的内存这些内存就相当于是外地/远程内存Remote Memory
当CPU处理器访问本地内存时会有较短的响应时间称为本地访问Local Access。而如果需要访问外地/远程内存时候就需要通过互联通道访问响应时间就相比本地内存变慢了称为远端访问Remote Access。所以NUMANon-Uniform Memory Access就此得名。
下图展示了两个处理器的NUMA架构。
<img src="https://static001.geekbang.org/resource/image/88/33/88b6a75956af211654f2fe6c13a0c933.png" alt="">
如果处理器A访问内存A就是本地访问。如果它访问内存B就是远端访问内存的访问延迟大大增加。
采用多处理器和NUMA架构的主要原因是提高整个CPU的并行处理性能。每一台服务器可以同时运行很多程序和进程。对每一个进程和线程而言当它运行在某一个处理器上时它所对应的内存使用默认的分配方案是——优先尝试在请求线程当前所处的处理器的本地内存上分配。如果本地内存不足才会分配到外地/远程内存上去。
### 多核结构和多级缓存
我们再看看每个处理器内部的结构。我们刚刚讲到的处理器内部一般都是多核Core架构。随着多核处理器的发展CPU的缓存通常分成了三个级别L1、L2和L3。
级别越小就越接近CPU速度更快同时容量也越小。L1和L2一般在核的内部我们下一讲还会详细讲。L3缓存是三级缓存中最大的一级同时也是最慢的一级在同一个处理器内部的核会共享同一个 L3 缓存。
除了多个核以及L3缓存外处理器上一般还有非核心处理器Uncore里面含有和指令运行不直接相关的组件包括QPI控制器和存储器一致性监测组件如下图所示。
<img src="https://static001.geekbang.org/resource/image/35/e7/354764adfca130abdc7ae15a0fafd0e7.png" alt="">
### 超线程HyperthreadingHT
一个核还可以进一步分成几个逻辑核,来执行多个控制流程,这样可以进一步提高并行程度,这一技术就叫超线程,有时叫做 simultaneous multi-threadingSMT
超线程技术主要的出发点是,当处理器在运行一个线程,执行指令代码时,很多时候处理器并不会使用到全部的计算能力,部分计算能力就会处于空闲状态。而超线程技术就是通过多线程来进一步“压榨”处理器。
举个例子如果一个线程运行过程中必须要等到一些数据加载到缓存中以后才能继续执行此时CPU就可以切换到另一个线程去执行其他指令而不用去处于空闲状态等待当前线程的数据加载完毕。
通常一个传统的处理器在线程之间切换可能需要几万个时钟周期。而一个具有HT超线程技术的处理器只需要1个时钟周期。因此就大大减小了线程之间切换的成本从而最大限度地让处理器满负荷运转。
一个核分成几个超线程呢?
这个数字会根据CPU架构有所变化Intel一般是把一个核分成2个。
“这台计算机有多少CPU
我们经常会问这个问题,结合我们刚刚讲的知识,就很容易回答了。
比如如果一台计算机有两个处理器每个处理器有12个核而且采用了HT超线程那么总的CPU数目就是48就是2×12×2。这个数字48就是我们平时用监控软件和命令看到的CPU的数量。比如Linux的top或者vmstat命令显示的CPU个数就是这样算出来的。
## CPU性能指标和常见性能问题
我们继续探讨CPU的性能指标和常见性能问题这方面很多资料都有涉及我们提纲挈领地总结一下。
最表层的CPU性能指标就是CPU的负载情况和使用率。CPU使用率又进一步分成系统CPU、用户CPU、IO等待CPU等几个指标。你执行一下top命令就会看到。
需要注意的是因为CPU架构的复杂性以及和其他部件的交互CPU的使用率和负载的关系往往不是线性的。
也就是说如果10%的CPU使用率可以每秒处理1千个请求那么80%的CPU使用率能够处理多少请求呢不太可能处理每秒8千个请求往往会远远小于这个数字。
衡量一个应用程序对CPU使用效率时往往会考察CPICycles Per Instruction每指令的周期数和 IPCInstructions Per Cycle每周期的指令数。这两个指标有助于识别运行效率高或低的应用程序。而一台计算机的CPU性能一般用MIPSMillions of Instructions Per Second来衡量表示每秒能运行多少个百万指令MIPS越高性能越高。MIPS的计算很简单就是时钟频率×IPC。
继续往深处分析CPU常见的各种中断包括软中断和硬中断。除此之外还有一种特殊的中断上下文切换。这些指标需要和每个核挂钩理想情况下是各个核上的中断能够均衡。如果数量不均衡往往会造成严重的性能问题——有的核会超载而导致系统响应缓慢但是其他的核反而空闲。
和CPU相关的性能问题基本上就是表现为CPU超载或者空闲。
如果是CPU超载那么就要分析为什么超载。多数情况下都不一定是合理的超载比如说多核之间的负载没有平衡好或者CPU干了很多没用的活或者应用程序本身的设计需要优化等等。反之如果是CPU空闲那就需要了解为什么空闲或许是指令里面太多内存数据操作从而造成CPU停顿也或许是太多的分支预测错误等这就需要具体分析和对症下药的优化。
CPU对多线程的执行顺序是谁定的呢
是由内核的进程调度来决定的。内核进程调度负责管理和分配CPU资源合理决定哪个进程该使用 CPU哪个进程该等待。进程调度给不同的线程和任务分配了不同的优先级优先级最高的是硬件中断其次是内核系统进程最后是用户进程。每个逻辑CPU都维护着一个可运行队列用来存放可运行的线程来调度。
## CPU的性能监测工具
我们最后讲一下CPU性能监测方面的工具。和CPU监测相关的工具挺多的而且往往每个工具都包含很多不同的有用的信息。
比如在Linux上最常用的Top系统进程监控命令。Top是一个万金油的工具可以显示出CPU的使用、内存的使用、交换内存和缓存大小、缓冲区大小、各个进程信息等。
如果想要查看过去的CPU负载情况可以用uptime。也可以用mpstat和pidstat来分别查看每个核还有每个进程的情况。另一个常用的vmstat命令可以用于显示虚拟内存、内核线程、磁盘、系统进程、I/O模块、中断等信息。
对有经验的性能工程师来讲有一个类似于“瑞士军刀”一样的好工具Perf。
Perf是Linux上的性能剖析profiling工具极为有用。它是基于事件采样原理以性能事件为基础利用内核中的计数器来进行性能统计。它不但可以分析指定应用程序的性能问题也可以用来分析内核的性能问题。
## 总结
这一讲我们讨论了计算机的运算核心CPU的结构尤其是它内部的和性能相关的部件并澄清了一些术语。
<img src="https://static001.geekbang.org/resource/image/b7/7c/b7600ae6757369212e30aec6e829997c.png" alt="">
CPU是服务器性能的最重要的部分因为不管程序代码如何优化最后都要转换成指令让CPU来执行。不管其他部件如何和CPU交互最终目的是让CPU尽快地拿到指令并满载执行。
唐代有个诗人叫李贺,他曾经形容跨马奔驰的愿景:
“大漠沙如雪,燕山月似钩。<br>
何当金络脑,快走踏清秋。”
我们常把CPU类比大脑CPU性能优化的目标就是让它的运行不受阻碍如千里马一样任意驰骋。
现代CPU提升性能的主要途径是并行化这方面的策略包括多处理器、多核、超线程另外还有流水线架构和超标量等等都是为了提高并行处理能力。
## 思考题
你工作中一定碰到过CPU方面的性能问题吧总结一下有几种表现形式
>
比如是总体CPU使用量太高还是计算机的几个核负载不均衡负载不均衡是什么原因导致的呢是线程数不够还是系统调度的问题
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,129 @@
<audio id="audio" title="16 | 内存篇:如何减少延迟提升内存分配效率?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/70/ec/706dfa50bd94f96ac1b38c51ab81beec.mp3"></audio>
你好,我是庄振运。
上一讲我们讨论了关于CPU的性能指标和分析。CPU和内存是和程序性能最相关的两个领域那么这一讲我们就来讨论和内存相关的性能指标和性能分析的工具。
内存方面的性能指标主要有缓存命中率、缓存一致性、内存带宽、内存延迟、内存的使用大小及碎片、内存的分配和回收速度等接下来我会逐一进行介绍。现代很多CPU都是NUMA架构的所以我也会介绍NUMA的影响和常用的工具。
## 缓存和缓存命中率
我们先看看缓存也就是Cache。
缓存是CPU与内存之间的临时数据交换器是为了解决两种速度不匹配的矛盾而设计的。这个矛盾就是**CPU运行处理速度**与**内存读写速度**不匹配的矛盾。CPU处理指令的速度比内存的速度快得多了有百倍的差别这一点我们已经在上一讲讨论过。
缓存的概念极为重要。不止是CPU缓存的策略也用在计算机和互联网服务中很多其他的地方比如外部存储、文件系统以及程序设计上。有人甚至开玩笑说计算机的各种技术说到底就是三种——Cache缓存、Hash哈希处理和Trash资源回收。这种说法当然有点偏颇但你也能从中看到缓存技术的重要性。
现在回到CPU缓存的讨论上来。
<img src="https://static001.geekbang.org/resource/image/75/53/754b694aed8f28c2e215876fc596cf53.png" alt="">
我们前面也讲了随着多核CPU的发展CPU缓存通常分成了三个级别L1、L2、L3。一般而言每个核上都有L1和L2缓存。L1缓存其实分成两部分一个用于存数据也就是L1d CacheData Cache另外一个用于存指令L1i CacheInstruction Cache
L1缓存相对较小每部分差不多只有几十KB。L2缓存更大一些有几百KB速度就要慢一些了。L2一般是一个统一的缓存不把数据和指令分开。L3缓存则是三级缓存中最大的一级可以达到几个MB大小同时也是最慢的一级了。你要注意在同一个处理器上所有核共享一个L3缓存。
为什么要采用多级缓存,并逐级增加缓存大小呢?
这个目的就是为了提高各级缓存的命中率从而最大限度地降低直接访问内存的概率。每一级缓存的命中率都很重要尤其是L1的命中率。这是因为缓存的命中率对总体的访问时间延迟影响很大而且下一级缓存的访问延迟往往是上一级缓存延迟的很多倍。
为了加深你的理解,我还是用文章中图片里的延迟数据来举例说明一下。
<img src="https://static001.geekbang.org/resource/image/75/53/754b694aed8f28c2e215876fc596cf53.png" alt="">
在图片里你可以看到L1的访问时间是3个时钟周期L2的访问时间是12个时钟周期。假如在理想情况下L1i的命中率是100%就是说每条指令都从L1i里面取那么平均指令访问时间也是3个时钟周期。作为对比如果L1i命中率变成90%也就是只有90%的指令从L1i里面取到而剩下的10%需要L2来提供。
那么平均指令访问时间就变成了3.9个指令周期也就是90%*3+10%*12。虽然看起来只有10%的指令没有命中但是相对于L1命中率100%的情况平均访问时间延迟差不多增大了多少呢高达30%。
## 缓存一致性
虽然缓存能够极大地提升运算性能但也带来了一些其他的问题比如“缓存一致性问题cache coherence”。
如果不能保证缓存一致性就可能造成结果错误。因为每个核都有自己的L1和L2缓存当在不同核上运行同一个进程的不同线程时如果这些线程同时操作同一个进程内存就可能互相冲突最终产生错误的结果。
举个例子你可以设想这样一个场景假设处理器有两个核core-A和core-B。这两个核同时运行两个线程都操作共同的变量i。假设它们并行执行i++。如果i的初始值是0当两个线程执行完毕后我们预期的结果是i变成2。但是如果不采取必要的措施那么在实际执行中就可能会出错什么样的错误呢我们来探讨一下。
运行开始时每个核都存储了i的值0。当第core-A做i++的时候其缓存中的值变成了1i需要马上回写到内存内存之中的i也就变成了1。但是core-B缓存中的i值依然是0当运行在它上面的线程执行i++然后回写到内存时就会覆盖core-A内核的操作使得最终i 的结果是1而不是预期中的2。
为了达到数据访问的一致,就需要各个处理器和内核,在访问缓存和写回内存时遵循一些协议,这样的协议就叫**缓存一致性协议**。常见的缓存一致性协议有MSI、MESI等。
缓存一致性协议解决了缓存内容不一致的问题,但同时也造成了缓存性能的下降。在有些情况下性能还会受到严重影响。我们下一讲还会仔细分析这一点,并且讨论怎样通过优化代码来克服这样的性能问题。
## 内存带宽和延迟
我们讨论了缓存,接下来探讨内存带宽和内存访问延迟。
计算机性能方面的一个趋势就是,内存越来越变成主要的性能瓶颈。内存对性能的制约包括三个方面:内存大小、内存访问延迟和内存带宽。
第一个方面就是内存的使用大小,这个最直观,大家都懂。这方面的优化方式也有很多,包括采用高效的,使用内存少的算法和数据结构。
第二个方面是内存访问延迟,这个也比较好理解,我们刚刚讨论的各级缓存,都是为了降低内存的直接访问,从而间接地降低内存访问延迟的。如果我们尽量降低数据和程序的大小,那么各级缓存的命中率也会相应地提高,这是因为缓存可以覆盖的代码和数据比例会增大。
第三个方面就是内存带宽,也就是单位时间内,可以并行读取或写入内存的数据量,通常以字节/秒为单位表示。一款CPU的最大内存带宽往往是有限而确定的。并且一般来说这个最大内存带宽只是个理论最大值实际中我们的程序使用只能达到最大带宽利用率的60。如果超出这个百分比内存的访问延迟会急剧上升。
文章中的图片就展示了几款Intel的CPU的内存访问延迟和内存带宽的关系图片来自[https://images.anandtech.com](https://images.anandtech.com))。
<img src="https://static001.geekbang.org/resource/image/a1/7b/a1a03d7bdcb021be9c4653d0ef49ae7b.png" alt="">
图中内存带宽使用是横轴,相对应的,内存访问延迟是纵轴。你可以清楚地看到,当内存带宽较小时,内存访问延迟很小,而且基本固定,最多缓慢上升。而当内存带宽超过一定值后,访问延迟会快速上升,最终增加到不能接受的程度。
那么一款处理器的内存总带宽取决于哪些因素呢?
答案是有四个因素内存总带宽的大小就是这些因素的乘积。这四个因素是DRAM时钟频率、每时钟的数据传输次数、内存总线带宽一般是64bit、内存通道数量。
我们来用几个实际的Intel CPU为例来看看内存带宽的变化。
<img src="https://static001.geekbang.org/resource/image/b1/55/b16a437792c79f5c2c195fd632206255.png" alt="">
文章中的这个表格大体上总结了5款Intel CPU的各级缓存大小、内存通道数目、可使用内存带宽这里取最大值的50%、内存频率和速度。你可以看到每款新的CPU它的内存带宽一般还是增加的这主要归功于内存频率的提升。
## 内存的分配
讲过内存带宽,我们再来看看内存的分配。程序使用的内存大小很关键,是影响一个程序性能的重要因素,所以我们应该尽量对程序的内存使用大小进行调优,从而让程序尽量少地使用内存。
不知道你有没有过系统内存用光的经验每当发生这种情况系统就会被迫杀掉一些进程并且抛出一个系统错误内存用光“OOMOut of memory”。所以一个应用程序用的内存越少那么OOM错误就越不太可能发生。
还有,服务器等容量也是公司运营成本的一部分。如果一台服务器的内存资源足够,那么这样一个服务器系统就可以同时运行多个程序或进程,以最大限度地提高系统利用率,这样就节省了公司运营成本。
再进一步讲,应用程序向操作系统申请内存时,系统会分配内存,这中间总要花些时间,因为操作系统需要查看可用内存并分配。一个系统的空闲内存越多,应用程序向操作系统申请内存的时候,就越快地拿到所申请的内存。反之,应用程序就有可能经历很大的内存请求分配延迟。
比如说在系统空闲内存很少的时候程序很可能会变得超级慢。因为操作系统对内存请求进行比如malloc())处理时,如果空闲内存不够,系统需要采取措施回收内存,这个过程可能会阻塞。
我们写程序时或许习惯直接使用new、malloc等API申请分配内存直观又方便。但这样做有个很大的缺点就是所申请内存块的大小不定。当这样的内存申请频繁操作时会造成大量的内存碎片这些内存碎片会导致系统性能下降。
一般来讲开发应用程序时采用内存池Memory Pool可以看作是一种内存分配方式的优化。
所谓的内存池,就是提前申请分配一定数量的、大小仔细考虑的内存块留作备用。当线程有新的内存需求时,就从内存池中分出一部分内存块。如果已分配的内存块不够,那么可以继续申请新的内存块。同样,线程释放的内存也暂时不返还给操作系统,而是放在内存池内留着备用。
这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率和系统的总体内存使用效率得到提升。
## NUMA的影响
我们刚刚谈了内存性能的几个方面最后看看多处理器使用内存的情景也就是NUMA场景。NUMA系统现在非常普遍它和CPU和内存的性能都很相关。简单来说NUMA包含多个处理器或者节点它们之间通过高速互连网络连接而成。每个处理器都有自己的本地内存但所有处理器可以访问全部内存。
因为访问远端内存的延迟远远大于本地内存访问,操作系统的设计已经将内存分布的特点考虑进去了。比如一个线程运行在一个处理器中,那么为这个线程所分配的内存,一般是该处理器的本地内存,而不是外部内存。但是,在特殊情况下,比如本地内存已经用光,那就只能分配远端内存。
我们部署应用程序时最好将访问相同数据的多个线程放在相同的处理器上。根据情况有时候也需要强制去绑定线程到某个节点或者CPU核上。
## 工具
内存相关的工具也挺多的。比如你最熟的内存监测命令或许是free了。这个命令会简单地报告总的内存、使用的内存、空闲内存等。
vmstatVirtual Meomory Statistics 虚拟内存统计也是Linux中监控内存的常用工具可以对操作系统的虚拟内存、进程、CPU等的整体情况进行监视。
我建议你也尽量熟悉一下Linux下的/proc文件系统。这是一个虚拟文件系统只存在内存当中而不占用外存空间。这个目录下有很多文件每一个文件的内容都是动态创建的。这些文件提供了一种在Linux内核空间和用户间之间进行通信的方法。比如/proc/meminfo就对内存方面的监测非常有用。这个文件里面有几十个条目比如SwapFree显示的是空闲swap总量等。
另外,/proc这个目录下还可以根据进程的ID来查看每个进程的详细信息包括分配到进程的内存使用。比如/proc/PID/maps文件里面的每一行都描述进程或线程中连续虚拟内存的区域这些信息提供了更深层次的内存剖析。
## 总结
<img src="https://static001.geekbang.org/resource/image/19/db/19ec66b033e2fd90d3d8123bc66b19db.png" alt="">
我们都知道宋代词人辛弃疾,他曾经这样憧憬他的战场梦想:“马作的卢飞快,弓如霹雳弦惊。” 我们开发的应用程序对内存的分配请求延迟,也有相似的期盼,就是要动作飞快。如果内存分配延迟太大,整个程序的性能自然也高不上去。
如何实现这个梦想呢?就需要我们的代码和程序,尽量降低对内存的使用大小和内存带宽,尽量少地请求分配和释放内存,帮助系统内存状态不至于太过碎片化,并且对代码结构做一些相应地优化。
## 思考题
你正在开发的系统或者模块,会运行在什么样的服务器上?服务器上有多少内存?如果内存大小可能不够,你会采取什么措施来降低内存使用量呢?再进一步,内存带宽会是瓶颈吗?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,159 @@
<audio id="audio" title="17 | 存储篇:如何根据性能优缺点选择最合适的存储系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/89/a7/897a66f0c3cf12fa85d0802b7bb579a7.mp3"></audio>
你好,我是庄振运。
前面两讲我们讨论了CPU和内存今天我们讨论第三个重要的主题存储系统。现在是大数据时代这些数据终归要保存到各种存储系统里面以供读写和分析因此讨论存储系统的性能问题就很有必要了。
狭义上的存储往往是硬件比如磁盘、磁带还有固态硬盘。而广义上的存储系统除了指硬件的硬盘还包括基于网络的存储系统比如SANStorage Area Network, 存储区域网络和NAS存储Network Attached Storage网络接入存储
各种存储系统各有优缺点,尤其是性能和成本,所以对不同的需求,我们要选择最合适的存储系统。
我们首先讲存储系统最重要的三大性能指标IOPS、访问延迟和带宽然后讲传统硬盘HDDHard Disk Drive的性能。因为传统硬盘的特性相对简单直白毕竟业界已经用了几十年了。这之后再讲固态硬盘的性能固态硬盘就是SSD也叫Flash。相对于传统硬盘SSD的内部工作原理很不一样这也就导致它们的性能特性大相径庭。 最后,我们再延伸到基于网络的存储系统,并且介绍几个常用的和存储相关的工具。
## 存储系统的三大性能指标
一个存储系统的性能最主要的是三个:**IOPS**、**访问延迟**、**吞吐率/带宽**。这三个指标其实是互相关联和影响的,但是我们一般还是分开来衡量。
**IOPS**Input/Output Per Second即每秒钟能处理的读写请求数量这是衡量存储性能的主要指标之一。每个IO的请求都有自己的特性比如读还是写是顺序读写还是随机读写IO的大小是多少等。
什么是顺序读写呢就是访问存储设备中相邻位置的数据随机读写呢则是访问存储设备中非相邻位置的数据。对随机读写进行性能衡量时一般假定IO大小是4KB。
既然IO有这些特点所以我们讨论存储系统IOPS性能的时候经常需要更加具体的描述。比如顺序读IOPS、随机写IOPS等。
IOPS的数值会随这样的参数不同而有很大的不同这些参数的变化包括读取和写入的比例、其中顺序读写及随机读写的比例、读写大小、线程数量及读写队列深度等。此外系统配置等因素也会影响IOPS的结果例如操作系统的设置、存储设备的驱动程序特点、操作系统后台运行的作业等。
**访问延迟**Access Time和**响应时间**Response Time指的是从发起IO请求到存储系统把IO处理完成的时间间隔常以毫秒ms或者微妙us为单位。对这一性能指标我们通常会考虑它的平均值和高位百分数比如P99、P95。
**吞吐率**Throughput或者带宽Bandwidth衡量的是存储系统的实际数据传输速率通常以MB/s或GB/s为单位。一般来讲IOPS与吞吐率是紧密相关的它们之间的关系是吞吐率等于IOPS和IO大小的乘积。
这个也很容易理解比如对一个硬盘的读写IO是1MB硬盘的IOPS是100那么硬盘总的吞吐率就是100MB/s。需要强调的是这里IO的具体特性很重要比如是顺序还是随机IO大小等。
还有一点要注意,有些存储系统会因为其**IO队列深度增加**而获得更好的IO性能比如吞吐率会升高平均访问延迟会降低。
这是为什么呢这是因为存储系统的IO队列处理机制可以对IO进行重新排序从而获得好的性能。比如它可以合并几个相邻的IO把随机IO重新排序为顺序IO等。
## HDD传统硬盘的性能
我们先从你熟知的传统硬盘开始讨论。对于传统硬盘,我们应该比较熟悉它的内部是如何操作的。
简单来说当应用程序发出硬盘IO请求后这个请求就会进入硬盘的IO队列。如果前面有其他IO那么这个请求可能需要排队等待。当轮到这个IO来存取数据时磁头需要机械运动到数据存放的位置这就需要磁头寻址到相应的磁道并旋转到相应的扇区然后才是数据的传输。所以讨论硬盘IO的性能时需要充分考虑这一点。
我们有时候需要把**硬盘响应时间**和**硬盘访问时间**分开对待。它们之间的关系是硬盘响应时间除了包括访问时间外还包括IO排队的延迟如下图所示。
<img src="https://static001.geekbang.org/resource/image/03/19/030b5f050f8230d70654dd1df78c3119.png" alt="">
我们如果拿起一块硬盘仔细看看,硬盘上面往往会标注后面三个参数,分别是平均寻址时间、盘片旋转速度,以及数据传输速度,这三个参数就可以提供给我们计算上述三个步骤的时间。
平均寻址时间一般是几个毫秒。平均旋转时间可以从硬盘转动速度RPM来算出。因为每个IO请求平均下来需要转半圈那么如果硬盘磁头每分钟转一万圈10K RPM转半圈就需要3毫秒。
要注意的是硬盘上面标注的数据传输速度参数往往是最大值实际的数据传输时间要取决于IO的大小。
对于一块普通硬盘而言我们前面讲常用的性能数字时也提过随机IO读写延迟就是8毫秒左右IO带宽大约每秒100MB而随机IOPS一般是100左右。
硬盘的技术也在发展现代的硬盘也有很多变种。比如采用了多磁头技术或者几块硬盘组成磁盘阵列这样的整体IO性能也会相应地提升。
## SSD固态硬盘的技术背景
讲完了传统硬盘我们接着看看固态硬盘——SSD。SSD的内部工作方式和HDD大相径庭我们来了解一下。
### 单元Cell、页面Page、块Block
当今的主流SSD是基于NAND的它是将数字位存储在单元中。每个SSD单元可以存储一位SLCSingle Level Cell单级单元、两位MLC多级单元、三位TLC三级单元甚至四位QLC
SSD的特点是对SSD单元的每次擦除都会降低单元的寿命因此每一个单元只能承受一定数量的擦除。所以不同的SSD就有这几方面的考虑和平衡。单元存储的位数越多制造成本就越少SSD的容量也就越大。但是耐久性擦除次数也会降低。所以高端的SSD比如企业级的基本都是基于SLC的。
一个页面包括很多单元典型的页面大小是4KB。页面也是读写的最小存储单位。我们知道HDD可以直接对任何字节重写和覆盖但是对SSD而言不能直接进行上述的“覆盖”操作。SSD的一个页面里面的所有单元一旦写入内容后就不能进行重写必须和其它相邻页面一起被整体擦除、重置。
在SSD内部多个页面会组合成**块**。一个块的典型大小为512KB或1MB也就是大约128或256页。块是擦除的基本单位每次擦除都是整个块内的所有页面都被重置。
### I/O和垃圾回收Garbage Collection
我们总结一下对SSD的IO操作一共有三种类型**读取**、**写入**和**擦除**。读取和写入是以页为单位的,也就是说最少也要读取写入一个页面。
IO写入的延迟具体取决于磁盘的历史状态因为如果SSD已经存储了许多数据那么对页的写入有时需要移动已有的数据这种情况下写入延迟就比较大。但多数情况下读写延迟都很低一般在微秒级别远远低于HDD。
擦除是以块为单位。擦除速度相对很慢通常为几毫秒。所以对同步的IO请求发出IO的应用程序可能会因为块的擦除而经历很大的写入延迟。为了尽量地减少这样的场景发生一块SSD最好保持一定数量的空闲块这样可以保证SSD的写入速度足够快。
SSD内部有垃圾回收GC机制它的目的就在于此就是不断回收不用的块进行擦除从而产生新的空闲块来备用。这样可以确保以后的页写入能快速分配到一个全新的页。
<img src="https://static001.geekbang.org/resource/image/b3/6e/b3e76cd89e5471b21900c489f839396e.png" alt="">
### 写入放大Write Amplification, or WA
这是SSD相对于HDD的一个缺点即实际写入SSD的物理数据量有可能是应用层写入数据量的多倍。
这是因为一方面页级别的写入需要移动已有的数据来腾空页面来写入。另一方面GC的操作,也会移动用户数据来进行块级别的擦除。
所以对SSD真正的写操作的数据肯定比实际写的数据量大这就是写入放大。因为一块SSD只能进行有限的擦除次数也称为编程/擦除P/E周期所以写入放大效用会缩短SSD的寿命。
### 耗损平衡Wear Leveling
对每一个块而言一旦擦除造成的损耗达到最大数量该块就会“死亡”再也不能存储数据了。对于SLC类型的块P/E周期的典型数目是十万次对于MLC块P/E周期的数目是一万而对于TLC块则可能是几千。为了确保整块SSD的容量、性能和可靠性SSD内部需要对整个SSD的各块做平衡尽量在擦除次数上保持类似。
SSD控制器具有这样一种机制也叫“耗损平衡”来实现这一目标。在损耗平衡操作时数据在各个块之间移动以实现均衡的损耗。但是这种机制也有害处就是会对前面讲的写入放大推波助澜。
## SSD的性能和应用程序的设计
性能方面SSD的IO性能相对于HDD来说IOPS和访问延迟提升了上千倍吞吐率也是提高了几十倍。但是SSD的缺点也很明显。主要有三个缺点
1. 贵;
1. 容量小;
1. 易损耗。
好消息是,随着技术的发展,这三个缺点近几年在弱化。
如今越来越多的应用程序采用SSD来减轻I/O性能瓶颈。许多测试和实践的结果都表明与HDD相比采用SSD带来了极大的应用程序性能提升。
但是我想强调的一点是在大多数采用SSD的部署方案中SSD仅被视为一种“更快的HDD”并没有真正发挥SSD的潜力。
我为什么这么说呢因为尽管使用SSD作为存储时应用程序可以获得更好的性能但是这些收益主要归因于**SSD提供的更高的IOPS和带宽**。
但是SSD除了提供这些之外它还有其它特点比如易损耗以及其独特的内部机制。如果应用程序的设计能充分考虑SSD的内部机制设计出对SSD友好的应用程序就可以更大程度地优化SSD从而进一步提高应用程序性能也可以延长SSD的寿命并降低运营成本。关于这方面我后面会有一讲专门讨论。
## 基于网络的存储系统
我们前面讨论了存储硬件,这些存储硬件可以直接安装在服务器上,构成单机系统。和单机系统和场景相对应,也有很多非单机使用的场景。
在非单机使用的场景里这些存储硬件也被包装在各种基于网络的存储系统里。这样的存储系统也有很多种比如DAS、NAS和SAN。
DASDirected Attached Storage是直连式存储。这是以服务器为中心的存储系统存储设备直接通过I/O总线连在服务器主机上。这种存储一般运行SATA或者SAS等协议可以让网络的客户端直接使用。
NASNetwork Attached Storage是网络接入存储。在NAS存储结构中存储系统不再通过I/O总线只属于某个特定的服务器而是通过网络接口直接与网络相连。NAS提供的是文件服务器的功能比如NFS和CIFS供客户通过网络访问。
SANStorage Area Network是存储区域网络。SAN是一种以网络为中心的存储系统通常有高性能专用网络比如光纤来支持运行iSCSI等协议。
## 工具
最后,我们看看常用的存储系统性能监测和测试工具。存储系统的测试和监控命令工具非常多,下面简单介绍几个。
Linux系统上可以采用fio工具进行各种组合的IO测试。这些组合包括读写比例、随机还是顺序读写、IO大小等等。
IOMeter也是不错的测试磁盘性能的工具比如可以测试I/O的传输速度和平均的I/O响应时间。
IOZone是一个文件系统基准测试工具可以测试不同的操作系统中文件系统的读写性能。
Bonnie++是基于Linux平台的开源磁盘IO测试的工具可以用它来测试磁盘和文件系统的I/O性能。
hdparm可以用来作跳过文件系统的纯硬件操作测试。
iostat这个工具可以查看进程发出IO请求的数量、系统处理IO请求的耗时、磁盘的利用率等也可以分析进程与操作系统的交互过程中IO方面是否存在瓶颈。
## 总结
<img src="https://static001.geekbang.org/resource/image/a9/b3/a946f03c8cee8ac2a5ed4e0b875716b3.png" alt="">
存储系统,顾名思义,是用来存放我们各种程序和服务的数据。随着现代互联网服务的大数据化,几乎所有的业务都离不开存储系统的支持。
各种存储系统的基础是传统硬盘或者固态硬盘,这两种硬盘在成本、性能、大小方面各有千秋。我想起了宋代有首诗叫《雪梅》,里面比较了白雪和梅花的优缺点:
“梅须逊雪三分白,雪却输梅一段香。”
这句诗用来形容传统硬盘和固态硬盘的关系还挺合适的。
在实际使用中我们要注意它们的性能优缺点从而适当来作取舍。比如如果系统对IOPS或者延迟要求很高恐怕只有SSD才能满足要求。反之如果数据量极大需要降低成本那么只能选择磁盘或者磁带系统了。
## 思考题
你对SSD这种新型存储了解多少你公司里面有没有系统使用SSD它们碰到的性能问题有哪几个方面运维和开发人员是怎么一起解决这些问题的
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,153 @@
<audio id="audio" title="18 | 网络篇:如何步步拆解处理复杂的网络性能问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8d/54/8dff554130dcd56ba4649eb6cfbf0c54.mp3"></audio>
你好,我是庄振运。
前面几讲我们讨论了CPU、内存和存储系统的性能。不过你也清楚各种互联网服务的数据传递终归是需要通过网络来传输的所以网络性能也是至关重要的。所以今天我们就来接着学习网络性能相关的内容。
这一讲我的讲解,依然是会逐步递进,先介绍有关网络的几个性能指标,并从单机的网络性能说起,之后推进到数据中心内部的网络性能,接着再进一步阐释互联网和内容分发网络的性能。最后,再为你介绍几个常用网络性能工具。
## 网络的性能指标
先来说说网络的性能指标。网络性能的五个常用的指标是:
1. 可用性availability
1. 响应时间response time
1. 网络带宽容量network bandwidth capacity
1. 网络吞吐量network throughput
1. 网络利用率network utilization
第一个指标是**可用性**,理解起来比较简单。你肯定有过网络不通(也就是断网)的糟糕体验。没有网络,你几乎啥都干不了。所以,对于网络来讲,最重要的就是,网络是否可以正常联通。
如何测试网络可用性呢?
最简单的方法,就是**使用ping命令**。这个命令其实就是向远端的机器发送ICMP的请求数据包并等待接收对方的回复。通过请求和应答返回的对比来判断远端的机器是否连通也就是网络是否正常工作。
第二个指标是**响应时间**。端到端的数据一次往返所花费时间,就是响应时间。响应时间受很多因素的影响,比如端到端的物理距离、所经过网络以及负荷、两端主机的负荷等。
第三个指标是**网络带宽容量**。它指的是在网络的两个节点之间的最大可用带宽这一指标一般是由设备和网络协议决定的比如网卡、局域网和TCP/IP的特性。如果是向网络提供商购买的带宽那么购买的数量就是网络的带宽容量。
第四个指标**网络吞吐量**是指在某个时刻,在网络中的两个节点之间,端到端的实际传输速度。网络吞吐量取决于当前的网络负载情况,而且是随着时间不同而不断变化的。
第五个指标**网络利用率**是指网络被使用的时间占总时间的比例,一般以百分比来表示。因为数据传输的突发性,所以实际中的网络利用率一般不会太高。否则的话,那么响应时间就不能保证。
## 单机的网络性能
了解了网络方面性能的指标后,我们接着讨论具体的网络性能。虽然网络传输需要两端进行,但是我们必须从单机开始,来清楚地了解网络的协议栈。
网络协议其实相当复杂而且分很多层级。操作系统内核中最大的一个子系统或许就是网络。网络子系统由多个协议组成其中每个协议都在更原始的协议之上工作。大学时你应该学过OSI模型或TCP / IP协议栈里面的分层定义对网络协议非常适用。当用户数据通过网络协议栈传递时数据会被封装在该协议的数据包中。
在考虑多层协议的交互时,我们尽量把思路简化。其实它们之间的关系很简单,就是网络协议栈的每一层都有其职责,与其他高层和低层协议无关。
举个例子来说我们看IP层的协议。IP是第三层协议它是通过路由器和网络发送端到端的数据报它的主要目的就是在网络中的每一段找到路由路径从而最终能够到达数据报的接收地。但它不保证数据的有序性也就是转发过程中几个数据报会重新排序同时也不能保证整个数据的完整性和可靠性比如丢失的数据报那就是丢失了IP层不会重传。
那么数据的可靠性是谁保证的呢这就需要更上层的TCP协议实现。TCP层协议通过检测数据丢失并且重传来保证数据的可靠性也通过序列号来保证数据的有序性。
但是这两层协议TCP和IP两层协议都只能传输原始的数据而对于数据本身是否被压缩过这些数据表示什么是在更高层的协议如HTTP和应用层上实现的。
我们接着具体到网络协议的程序实现在Unix系统中网络协议栈可以大体分为三层。从上到下第一层是通过一系列系统调用实现BSD套接字的套接字层比如sendmsg()函数第二层是中间协议的程序例如TCP/IP/UDP第三层是底部的媒体访问控制层提供对网络接口卡NIC本身的访问。
如下图所示(图片来自于[https://myaut.github.io/](https://myaut.github.io/) )。
<img src="https://static001.geekbang.org/resource/image/13/72/13ac1f15762144348da5c170c2b95f72.png" alt="">
注意这个图右边是发送端左边是接收端。先从右边看起最上层就是BSD套接字。它通过一系列API调用比如connect()等然后是TCP的发送再到IP的发送最后到网卡的发送缓冲区并最终通过网卡发出。 接收端,也就是图的左边,则会经过相反的顺序,逐层到达应用层。
## 数据中心的网络性能
谈完单机的网络性能,我们接着讨论端到端的互联网数据传输。
一台服务器和互联网的远端服务器进行数据传输的时候,需要经过好几层交换器和不同的网络。数据从一台服务器的网卡出来之后,下一步就是经过机柜上面的交换器,然后是数据中心内部的网络,再进入互联网骨干网络。接收端的情况正好相反。
对于这些中间的网络构件,我们一个一个地讨论一下。
**机柜交换器**TOR, Top Of Rack; or RSW, Rack Switch
数据中心里面的服务器不是单独放置的,一般是几十台服务器组成了一个机柜。机柜上面会有机柜交换器。这个机柜交换器的作用,一方面让机柜内部的服务器直接互通;另一方面,机柜交换器会有外联线路,连接到数据中心的骨干网络。
**数据中心网络**
数据中心网络里面也分了好几层从TOR到集群交换器Cluter Switch再到集合交换器Aggregation Switch最后到数据中心路由器。
下面的图示简单展示了机柜内部的网络和POD内部网络。TOR1是机柜交换器负责机柜内部几十台服务器之间的数据交换。POD1内部网络包含很多机柜。
<img src="https://static001.geekbang.org/resource/image/7e/a7/7e8c8b6513c0456d5d71a6f9a8da5fa7.png" alt="">
这里有一个值得注意的地方是,就是这个多层次结构,一般是越往上层,总的带宽越少。
比如服务器的网卡带宽是25Gbps即使这个机柜内有30台服务器总带宽就是750Gbps机柜交换器TOR的外联带宽有多少呢可能只有100Gbps。这个差距就叫**带宽超订**over-subscription。同样的POD交换器之间的带宽会继续变小。之所以允许带宽超订是因为多数的数据交换是在内部进行的不会全部都和外部进行交换。
知道这一点是必要的因为我们做网络方面和服务部署优化的时候需要考虑这点不要让高层的网络带宽成为性能瓶颈。比如假设两个服务分别用不同的服务器它们之间的数据交换如果很多的话就尽量让它们运行在同一个机柜的服务器里面如果不能保证同一个机柜就尽量是同一个POD内部。
## 互联网的网络性能
讲完了数据中心内部的网络我们继续往外扩展讨论互联网的网络性能。互联网上运行的是TCP/IP协议。这两个协议本身比较复杂有很多和性能相关的特性值得仔细学习。
一个常见的性能问题就是**丢包**。TCP对丢包非常敏感因为每次丢包TCP都认为是网络发生了拥塞因此就会降低传输速度并且采取重传来恢复这就影响网络性能。
实际情况中,造成丢包的原因有很多,不一定就是网络拥塞。因此我们需要进行各种测试观测来做根因分析。
通常的丢包原因,是**端到端的网络传输中的某一段发生了问题**,或许是拥塞,或许是硬件问题,也或许是其他软件原因。我们需要一步步地逐段逐层地排除。
对于网络的每段可以用工具比如Traceroute来发现每一段路由然后逐段测试。
对于协议的每层你都可以用相关工具进行分析。比如你可以分为TCP层、操作系统、网卡驱动层分别分析。具体来讲对TCP层可以用比如netstat来观察是不是套接字缓存不够对操作系统可以观察softnet_stat来判断CPU的查询队列对网卡驱动层可以用ethtool等来进行分析。
## 内容分发网络CDN的性能
当今互联网几乎普遍采用内容分发网络来提高网络性能。内容分发网络也叫CDNContent Delivery Network或Content Distribution NetworkCDN是一种分布式网络它可以有效地将Web内容交付给用户。
内容分发网络的基本原理是,利用最靠近每位用户的服务器,更快、更可靠地将(音乐、图片及其他)文件发送给终端用户,而不是每次都依赖于中心服务器。靠近用户的服务器,一般叫边缘服务器,会把请求的内容最大限度地缓存,以尽量地减少延迟。
我们用下图讲述它的基本工作方式图里有两个用户和一个边缘服务器Edge Server以及一个源头服务器Origin Server
<img src="https://static001.geekbang.org/resource/image/8c/75/8c882c90c0f6bdfb19ce255a76ad8575.png" alt="">
第1步用户A通过使用具有特殊域名的URL来请求文件。DNS将请求重定向到性能最佳的边缘服务器该位置通常是地理位置上最接近用户的服务器。
第2步如果重定向的边缘服务器A中没有文件则边缘服务器会向源头服务器请求该文件。源头服务器可以是任何可公开或非公开访问的Web服务器。
第3步源头服务器将文件返回到边缘服务器。
第4步边缘服务器先缓存文件并将文件返回给原始请求者。该文件将保留在边缘服务器上这样下次这个文件就可以迅速返回请求的客户。这个文件会保存到什么时候呢时间由其HTTP标头指定的生存时间TTL到期为止。
第5步如果用户B用相同URL请求相同的文件也可能定向到相同的边缘服务器。
第6步如果文件的TTL尚未过期则边缘服务器直接从缓存中返回文件。这样的用户体验就更快。
内容分发网络的边缘服务器节点会在多个地点,多个不同的网络上摆放。这些节点之间通常会互相传输内容,对用户的下载行为最优化,并借此改善用户的下载速度,提高系统的稳定性。同时,将边缘服务器放到不同地点,也可以减少网络互连的流量,进而降低带宽成本。
内容分发网络提供商往往有很大的规模,比如几十万台服务器。对服务的客户提供所需要的节点数量会随着需求而不同。
## 工具
我们最后来看看网络性能有关的工具。这方面的性能测试和观测工具也不少,这里简单介绍几个。
**Netperf**是一个很有用的网络性能的测量工具主要针对基于TCP或UDP的传输。Netperf有两种操作模式批量数据传输和请求/应答模式根据应用的不同可以进行不同模式的网络性能测试Netperf测试结果所反映的是一个端到端的系统能够以多快的速度发送数据和接收数据。
**Iperf**这个工具也以测试TCP和UDP带宽质量比如最大TCP带宽延迟抖动和数据包丢失等性能参数。
**Netstat**这一命令可以显示与IP、TCP、UDP和ICMP协议相关的统计数据提供TCP连接列表TCP和UDP监听进程内存管理的相关报告一般用于检验本机各端口的网络连接情况。
**Traceroute**这一命令可以帮我们知道,数据包从我们的计算机到互联网远端的主机,是走的什么网络路径。
## 总结
不管计算机技术如何发展,网络永远是极其重要的一部分;而且随着互联网业务的多样化和复杂化,对网络性能的要求只会越来越高。不管应用如何变化,高性能网络必不可少,而且会变得更复杂。这让我想起了宋朝大词人张先的《千秋岁》里面的几句词,“天不老,情难绝。心似双丝网,中有千千结。”
<img src="https://static001.geekbang.org/resource/image/06/ad/06ac9fe057487675c8c8e44f0b9622ad.png" alt="">
网络是整个互联网服务的一部分各种因素之间互相影响。和网络性能演化密切相关的因素包括上层服务需求的越来越多样化计算机硬件和操作系统的持续进化各层网络协议的不断优化以及底层介质的演化比如5G
复杂的网络性能问题会受所有这些因素的影响,似有“千千结”。所以,网络性能分析和优化,需要仔细分析和考虑这些方面。
我们这一讲讨论了网络性能的几个性能指标,也讨论了单机网络协议,数据中心的网络,互联网和内容分发网络。
对于网络性能要求简单来说就是能联通、响应快、带宽高并且在部署大流量服务时可以优化服务器的部署来尽量减少外部网络流量也可以使用CDN来加速数据传输。
## 思考题
你们公司的互联网服务使用内容分发网络也就是CDN吗是哪个CDN公司提供的
除了你们使用的这家CDN公司市场上还有哪些CDN提供商选择CDN提供商的时候你们会考虑哪些方面吗比如规模、带宽、价格、服务质量等等。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,165 @@
<audio id="audio" title="04 | 性能工程三定律IT业和性能优化工作的“法律法规”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/38/e6e42842d89b3f64f681779d8cb0a238.mp3"></audio>
你好,我是庄振运。
在开篇的几讲里,我谈了性能工程的重要性以及所需要的知识面,接下来我们就正式地进入相关的学习。
不过不要着急,第一个模块我们并不会直接进入性能问题的现场,一上来就去解决问题,而是要先耐下心来,学习一些必备的基础知识。为什么呢?因为学习任何事情,打好坚实的基础是至关重要的。
古人云:“合抱之木,生于毫末;九层之台,起于累土。”
所以接下来的几讲,我们需要先学习一些基础知识,包括和性能工程相关的几个重要定律法则和数理基础。这一讲我先和你探讨三个定律法则:帕累托法则、阿姆达尔定律和利特尔法则。
## 帕累托法则
我想你可能知道帕累托法则,它也被称为 80/20 法则、关键少数法则,或者八二法则。
这个法则是基于我们生活中的认识产生的人们在生活中发现很多变量的分布是不均匀的——在很多场景下大约20%的因素操控着80%的局面。也就是说所有的变量中比较重要的只有20%,是所谓的“关键少数”。剩下的多数,却没有那么重要。
举例来讲在企业销售中根据帕累托法则大约“80的销售额来自20的客户”。认识到这一点对企业管理至关重要比如需要重视大客户的关系。
虽然帕累托法则在生活中很多方面都适用,但我们今天的重点是来看看**帕累托法则是怎么应用到我们IT界**的,尤其是怎么指导我们的代码开发和性能优化相关的领域的。
我总结了一下,这个法则可以有以下几个领域的指导。
<img src="https://static001.geekbang.org/resource/image/9d/1d/9d99b547cbf2074144203f2ec2806c1d.png" alt="">
**1.应用程序的使用**
一个应用程序往往可以提供很多功能。但是如果我们统计用户的使用情况会经常发现大约80%的用户使用集中在20%的程序功能。
所以,对我们开发程序的指导意义是,要花足够心思在最常用的少数功能模块上,对它的设计和实现都要充分地优化。
**2.程序代码开发时间的分配**
一个程序的开发过程中大约80%的代码开发只占用了20%的总体开发时间。一般来讲,一个应用程序开发时,一开始往往花不了多少时间就可以快速地搭建一个大体可以工作的原型。反而是后面的剩余代码和代码改进需要更多的时间。
也就是说80%的开发时间往往用在最精髓的20%代码上。帕累托法则对我们的指导意义是,对代码开发工作量的安排上,需要合理有序地规划好开发时间。
**3.程序代码的维护**
如果观察代码的维护和改进历史你经常会发现少数代码被不断地改动甚至经常被改得面目全非而多数代码几乎从第一次写完后就一成不变了。按照帕累托法则80%的代码演进和改动发生在大约20%的代码上。
对程序维护而言我们需要对这20%的代码尽量熟悉,这样才能对其他程序员的代码改动了如指掌,一目了然。
**4.程序代码的修正和纠错**
统计数字也表明所有的代码错误中差不多有80%的错误发生在大约20%的代码上。
所以根据帕累托法则代码修复和纠错时要重点修复最容易产生Bug的代码这样才能把保证整个应用程序的合理质量。
**5.客户流量的时间分布**
估计80的流量将在总时间段的特定20内发生。比如一个商业软件客户的流量峰值往往是上班时间开始的那几个小时比如上午9点到12点
所以,我们在应用程序设计和部署时,要充分考虑帕累托法则带来的影响,尤其是客户访问的峰值时段和空闲时段。
**6.程序代码的优化**
一个程序完成后必然会去运行如果我们统计代码的运行时间往往会发现程序的80%的时间是在运行大约20%的代码。也就是说,只有少数代码非常频繁地被调用。
所以,如果我们想提高程序的性能,最好找出这些少数代码,并做重点优化,这样就可以用很少的改动大幅度地提升整个程序和系统的性能。
那么现在,我们就从性能优化的角度,来看看如何参照帕累托法则,来规划我们性能优化工作的投入和产出。
假设我们的性能优化投入永远是按照代码的优先级来投入的也就是说总是要先优化最值得优化的代码。那么我们看到只要投入差不多20%的努力就能产出80%的性能优化产出,获得最大的投入产出比。
下图是一个根据帕累托法则来投入努力和产出效果的过程。
<img src="https://static001.geekbang.org/resource/image/bc/11/bc49ec2e4107878d6069c84252e92311.png" alt="">
假如先解决20%的最重要的问题就可以达到总体效果的80%。以后再花费80%的努力也只能解决剩下的20%的问题。
在应用帕累托法则的时候需要注意的是里面的80%或者20%都是大约数字,实际的场景千差万别,不可能是恰好这两个数字。这个法则的精髓是,我们的生活和自然界万物的分布不是均匀的,总有些因素比其他因素更重要。
## 阿姆达尔定律
接下来我们来看第二个定律。这个定律你可能会感觉有点陌生。
**阿姆达尔定律**Amdahls law / Amdahls argument是计算机科学界非常重要的一个定律和法则。它本来用于衡量处理器进行并行处理时总体性能的提升度。但其实阿姆达尔定律可以用在很多方面为了方便你理解我们就从一个简单的生活例子开始。
我们用洗衣服和晾衣服来举例。这里假设我们不用洗衣机而是用传统的方式先洗再晾。再假设洗衣服和晾衣服各需要10分钟那么整个过程进行完需要20分钟。
<img src="https://static001.geekbang.org/resource/image/21/f2/21936de65dad5d3821ae6312799529f2.png" alt="">
如果我们对晾衣服的过程进行优化从10分钟缩短到5分钟相当于进行了两倍优化。现在整个过程需要多长时间呢需要15分钟因为洗衣服的模块还是需要10分钟。
<img src="https://static001.geekbang.org/resource/image/42/7a/420a4b5cecbf15d13d4c7fc83211947a.png" alt="">
在这个基础上我们继续对晾衣服模块进行优化速度提升5倍从10分钟缩短到2分钟。整个过程现在需要12分钟完成。
<img src="https://static001.geekbang.org/resource/image/73/18/73e4fa8f29685a1dc120cf66d1a95218.png" alt="">
在这个基础上继续进行类推我们就会发现无论对晾衣服模块进行多大的优化整个洗衣服、晾衣服的过程所需的时间不会小于10分钟也就是整体加速比不会超过2。
根据阿姆达尔定律描述科学计算中用多处理器进行并行加速时总体程序受限于程序所需的串行时间百分比。譬如说一个程序50%是串行的其他一半可以并行那么最大的加速比就是2。无论用多少处理器并行这个加速比不可能提高到大于2。
所以在这种情况下,改进程序本身的串行算法可能比用多核处理器并行更有效。
用公式来讲假设一个系统的整体运行时间是1其中要进行优化加速的模块运行用时是P。如果对这个模块的加速比是N那么新系统的处理时间可以用下面的公式来表示。
<img src="https://static001.geekbang.org/resource/image/5e/af/5e654721a7dc896068afe5dc653b94af.png" alt="">
这里面1-P是未被加速的其他模块运行时间而N分之P是优化后的模块运行时间。它们的和就是新系统的总体运行时间。
相对于旧系统,运行时间的加速比就是:
<img src="https://static001.geekbang.org/resource/image/50/ed/50adf0c81433aa34ed91f08319504fed.png" alt="">
阿姆达尔定律对我们进行性能优化的指导意义有以下2点。
1. 优先加速占用时间最多的模块,因为这样可以最大限度地提升加速比。
1. 对一个性能优化的计划可以做出准确的效果预估和整个系统的性能预测。
下面这张图描述了不同的并行百分比场景下分别进行并行优化的曲线。不同的曲线对应不同的并行模块百分比。横轴是并行程度,也就是多少个并行处理器。纵轴是速度提升度。
<img src="https://static001.geekbang.org/resource/image/c4/30/c4fee8386932e98bcf2d20cb67de2130.png" alt="">
对每一条曲线我们都可以看到,超过一定的并行度后,就很难进行进一步的速度提升了。
另外说明一点阿姆达尔定律其实是另外一个定律的简化版本。这个更复杂的定律叫通用扩展定律USL, Universal Scalability Law你有兴趣的话可以去学习一下。
## 利特尔法则
接下来我们来看利特尔法则Littles Law。这个法则描述的是在一个稳定的系统中长期的平均客户人数N等于客户抵达速度X乘以客户在这个系统中平均处理时间W也就是说N=XW。
这个法则看起来有点不直观,但从整个系统的宏观角度仔细想想的话就容易理解了。
如下图所示客户按照一定的速度不断地进入我们的系统假设这个速度是每分钟X个客户。每个客户在我们系统里的平均处理时间是W分钟。一旦处理完毕客户就不会滞留在我们的系统里。
<img src="https://static001.geekbang.org/resource/image/40/89/408c046dc1722db99058ce0bbcf94389.png" alt="">
所以如果这个状态稳定也就是说我们的系统处理速度恰恰好赶上客户到达速度的话一方面系统没有空闲另外一方面客户也不需要排队在系统外等待。那么在这个稳定状态下我们的系统的总容量就恰好等于系统里面正在处理的客户数目。也就是说N就等于X和W的乘积。
我举一个服务器性能提升的例子来解释吧。
假定我们所开发的服务器程序可以进行并发处理,同时处理多个客户请求。并发的客户访问速度是**每分钟到来1000个客户**,每个客户在我们的服务器上花费的**平均时间为2分钟**。根据利特尔法则在任何时刻我们服务器系统里面将容纳1000×22000个客户。这2000个客户都在被服务。
过了一段时间由于客户群的增大并发的访问速度从每分钟1000客户增大到**每分钟2000个客户**。在这样的情况下,我们该如何改进我们系统的性能来应对呢?
根据利特尔法则,我们可以有两种方案来解决这一需求。
第一种方案是把客户的处理时间减半从2分钟减到1分钟。这样我们的系统容量可以不变客户滞留在我们系统的时间减半刚刚好可以适应访问速率加倍的要求。系统容量就等于2000客户每分钟乘以1分钟还是2000个客户。
第二种方案是扩大系统容量维持处理时间不变。因为客户访问速度加倍了所以系统容量也需要加倍变成4000。假如原来的系统需要500台服务器那么新系统就需要1000台服务器。
从这里可以引申出利特尔法则在性能优化工作中的两种用处:
1. **帮助我们设计性能测试的环境**。性能测试的内容我们后面会详细讲到,这里简单提一下。比如当我们需要模拟一个固定容量的系统,那么性能测试的客户请求流量速度和每个请求的延时都需要仔细考虑。
1. **帮助我们验证测试结果的正确性**。有时候,如果性能测试的工作没有仔细地规划,得出的测试结果会出奇得好,或者出奇得差,从而让我们抓脑壳。这时如果采用利特尔法则,就可以很快地发现问题所在之处。
## 总结
我们今天讨论的性能工程相关的三大法则,分别是帕累托法则、阿姆达尔定律和利特尔法则。
可以说这些法则就是IT业和性能优化工作的“法律法规”有了它们我们在实际工作中才能做到“有法可依有法必依”。熟悉并熟练应用这几个法则对我们的工作是会有很大的帮助的。
<img src="https://static001.geekbang.org/resource/image/74/dc/74b119d052157540473f3d75870c64dc.png" alt="">
孟子说:“不以规矩,不能成方圆。”熟悉并熟练应用这几个“规律法则”,对我们的工作是会有很大的帮助的。
## 思考题
帕累托法则可以用到很多场景下,除了本讲中讨论的场景,你还能想到什么场景可以使用帕累托法则呢?使用这个法则会帮助你对问题的把握和找寻解决思路吗?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,143 @@
<audio id="audio" title="05 | 概率统计和排队论:做性能工作必须懂的数理基础" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/58/02/58e283997d7eedf36b1e1c031adc7b02.mp3"></audio>
你好,我是庄振运。
上一讲我们讲了和性能优化有关的三大基础定律法则。今天我们继续打基础,讲一点统计方面的数理知识,包括重要的**概率统计**和**排队论**。
或许你对概率统计和排队论有点发怵,但这些内容是必须学会的,因为它们很重要。因为它们是性能测试和优化这座高楼大厦的地基。地基打不好,性能测试和优化也不会做得很好。
而且我想强调的是你完全没有必要惧怕因为你只需要学习一部分最基础的知识这些知识对多数人和多数场合大体就够了。还记得上一讲的帕累托法则吗根据帕累托法则这一讲的内容或许占不到平时教科书内容的20%但却可以覆盖80%以上的应用场合。
## 概率和置信区间
今天的内容我们先从概率和置信区间讲起。
**概率**Probability也称几率和机率是一个在0到1之间的实数是对随机事件发生之可能性的度量。这个你应该都懂不需要我多说。
但概率论中有一个很重要的定理,叫贝叶斯定理,我们做性能测试和分析中经常需要用到,所以我们稍微讲讲。
**贝叶斯定理**Bayes theorem描述的是在已知一些条件下某事件的发生概率。比如如果已知某癌症与寿命有关合理运用贝叶斯定理就可以通过得知某人年龄来更加准确地计算出他患上该癌症的概率。
具体来讲对两个事件A和B而言“发生事件A”在“事件B发生”的条件下的概率与“发生事件B”在“事件A发生”的条件下的概率是不一样的。
然而这两者的发生概率却是有确定的关系的。就是A事件发生的概率乘以A事件下B事件发生的概率这个乘积等于B事件发生概率乘以B事件下A发生的概率。听起来有点拗口可如果用公式来表示的话其实很简单。
<img src="https://static001.geekbang.org/resource/image/0e/01/0e2e128d5a44743f1b20edb58b2da201.png" alt="">
贝叶斯定理的一个用途在于**通过已知的任意三个概率函数推出第四个**。
另外一个重要的概念是置信区间。**置信区间**Confidence intervalCI是对产生样本的总体**参数分布**Parametric Distribution中的某一个未知参数值以区间形式给出的估计。相对于后面我们要讲到的点估计指标比如均值中位数等置信区间蕴含了**估计精确度**的信息。
置信区间是对分布(尤其是正态分布)的一种深入研究。通过对样本的计算,得到对某个总体参数的区间估计,展现为总体参数的真实值有多少概率落在所计算的区间里。比如下图是一个标准正态分布的图,阴影部分显示的是置信区间[-1.7,1.7]占了91%的概率。
<img src="https://static001.geekbang.org/resource/image/e3/b0/e3500b3104e42f0c906eb5587f02d9b0.png" alt="">
不难理解置信水平越高置信区间就会越宽。一般来说如果需要涵盖绝大多数的情况置信区间一般会选择90%或者95%。
了解了概率和置信区间,我们下面去看看如何分析大量数据。
## 数理统计的点估计指标
做性能测试和优化的过程中会产生大量的数据,比如客户请求的吞吐率,请求的延迟等等。获得这些大量数据后,如何分析和理解这些数据就是一门学问了。通常我们需要处理一下这些数据来求得另外的指标,以方便描述和理解。
描述性统计分析是传统数据分析的基础,这个分析过程可以产生一些描述性指标,比如平均值、中位数、最大值、最小值、百分位数等。
这些描述性指标通常也被称为“**点估计**”,相对于前面讲到的置信区间,是用一个样本统计量来估计参数值,比较容易理解。这些点估计指标分别有不同的优点和缺点。
**平均值**Mean或称均值平均数是最常用测度值它的目的是确定一组数据的均衡点。但不足之处是它容易受极端值影响。比如公司的平均收入如果有一两个员工有特别高的收入会把大家的平均收入拉高就是平时我们经常调侃的“被平均”。
需要注意的是我们有好几种不同的平均值算法。我们平时比较常用的是算术平均值就是把N个数据相加后的和除以N。但是还有几种其他计算方法分别适用不同的情况。比如几何平均数就是把N个数据相乘后的乘积开 N次方。
**中位数**Median又称中值将数值集合划分为相等的上下两部分一般是把数据以升序或降序排列后处于最中间的数。它的优点是不受极端值的影响但是如果数据呈现一些特殊的分布比如二向分布中位数的表达会受很大的负面影响。
**四分位数**Quartile是把所有数值由小到大排列并分成四等份处于三个分割点位置的数值就是四分位数。 从小到大分别叫做第一四分位数,第二四分位数等等。四分位数的优点是简单,固定了三个分割点位置。缺点也正是这几个位置太固定,因此不能更普遍地描述其他位置。
**百分位数**Percentile可以看作是四分位数的扩展是将一组数据从小到大排序某一百分位所对应数据的值就称为这一百分位的百分位数以Pk表示第k个百分位数。比如常用的百分位数是P90P95等等。百分位数不容易受极端值影响因为有100个位置可以选取相对四分位数适用范围更广。
几个特殊的百分位数也很有意思比如P50其实就是中位数P0其实就是最小值P100其实就是最大值。
还要注意的是,面对同一组数据,平均值和中位数以及百分位数这些点估计指标,谁大谁小是不一定的,这取决于这组数据的具体离散程度。
比如我在面试的时候我经常问来面试的人一个问题就是平均值和P99哪个比较大答案就是不确定。
**方差/标准差**VarianceStandard Variance描述的是变量的离散程度也就是该变量离其期望值的距离。
## 重要的分布模型
以上的几个描述性的**点估计统计指标**很简单,但是描述数据的功能很有限。如果需要更加直观并准确的描述,就需要了解分布模型了。
举例来讲,假设我们有一个系统,观察对客户请求的响应时间。如果面对一万个这样的数据,如何对这个数据集合进行描述呢?这时候用分布模型来描述就很合适。
我们简单提一下几个最重要的分布模型,包括泊松分布、二项式分布和正态分布。
**泊松分布**Poisson distribution适合于描述单位时间内随机事件发生的次数的概率分布。如某一服务设施在一定时间内收到的服务请求的次数等。
具体讲如果随机变量X取0和一切正整数值在n次独立试验中出现的次数x恰为k次的概率PX=k就是
<img src="https://static001.geekbang.org/resource/image/0a/f9/0a7765813ce1a4fdd02079c47e6873f9.png" alt="">
公式中λ是单位时间内随机事件的平均发生次数。像下面这个图,表示的就是λ=5的分布。红色部分是P(X=4)的概率约为0.17。
<img src="https://static001.geekbang.org/resource/image/19/42/195f8c16c7462871a0705461841d9542.png" alt="">
当n很大且在一次试验中出现的概率P很小时泊松分布近似二项式分布。
**二项分布**Binomial distribution是n个独立的是/非试验中成功的次数的离散概率分布。
这里通常重复n次独立的伯努利试验Bernoulli trial。在每次试验中只有两种可能的结果而且两种结果发生与否互相对立并且相互独立。也就是说事件发生与否的概率在每一次独立试验中都保持不变与其它各次试验结果无关。
当试验次数为1时二项分布服从比较简单的0-1分布。
在n重伯努利试验中假设一个事件A成功的概率是p, 那么恰好发生 k 次的概率为:
<img src="https://static001.geekbang.org/resource/image/37/f1/37d156e7e163181ff25d4a14716119f1.png" alt="">
比如下图就是一个二项分布的图图中红色的是P(X=5)的概率约为0.18。
<img src="https://static001.geekbang.org/resource/image/8b/ec/8b0d08564e9868f17fb9c69f62446cec.png" alt="">
**正态分布**Normal distribution也叫高斯分布Gaussian distribution。经常用来代表一个不明的随机变量。
正态分布的曲线呈钟型,两头低,中间高,左右对称,因此经常被称之为钟形曲线。比如下图:
<img src="https://static001.geekbang.org/resource/image/37/d8/37ecaee54e21b987cda50c0cb0e510d8.png" alt="">
一个正态分布往往记为N(μ,σ^2)。其中的期望值μ决定了其位置,其标准差σ决定了分布的幅度。概率密度函数如下:
<img src="https://static001.geekbang.org/resource/image/61/d1/61b9b73ec360fc63a1ee5a22d3aa32d1.png" alt="">
当μ = 0σ = 1时的正态分布是标准正态分布。上图就是一个标准正态分布线段的值代表了置信区间。比如在期望值附近左右各一个标准差的范围内差不多可以囊括68.2%的概率各两个标准差的范围内囊括95.4%的概率各三个标准差的范围内囊括99.7%的概率。
正态分布的重要性在于,大多数我们碰到的未知数据都呈正态分布状。这意味着**我们在不清楚总体分布情况时,可以用正态分布来模拟**。
好了,我们这里学习了三个重要分布。如果你看不懂或者记不住这三个分布的公式也没有关系,你只要知道每个分布的大概适应场景就可以了。实际的工作中,很多工具都能帮助我们分析,很少需要我们去具体推导。
## 排队的理论
上面谈到的三个分布经常被应用到排队理论中而排队理论在性能工程方面是非常重要的。计算机系统中的很多模块比如网络数据发送和接收、CPU的调度、存储IO、数据库查询处理等等都是用队列来缓冲请求的因此排队理论经常被用来做各种性能的建模分析。
**排队论**Queuing Theory也被称为随机服务系统理论。这个理论能帮助我们正确地设计和有效运行各个服务系统使之发挥最佳效益。
排队论的系统里面有几个重要模块,比如顾客输入过程、队列、排队规则、服务机构等。几个模块之间的关系大体上可以用下面这张图来表示。
<img src="https://static001.geekbang.org/resource/image/f2/89/f2088a63dfc4756ecc12d765c0e30389.png" alt="">
主要的输入参数是到达速度、顾客到达分布、排队的规则、服务机构处理速度和处理模型等。
排队系统的输出也有很多的参数,比较重要的是排队长度、等待时间、系统负载水平和空闲率等。所有这些输入、输出参数和我们进行的性能测试和优化都息息相关。
排队的模型有很多,平时我们用得多的有**单队列单服务台**和**多队列多服务台**。系统里面各个模块的模型都可以变化,排队论里面还有很多延伸理论。
## 总结
要想精通任何一门学问和工作牢固的基础是必须的。对IT工作包括设计系统、编写程序、系统维护和性能优化而言牢固的数学基础会使我们的工作如虎添翼。这正如古人赞赏梅花时所说得“不经一番寒彻骨怎得梅花扑鼻香“。
我们也常说“根深才能叶茂”,今天讲的内容,包括概率统计和分布模型的知识,都是这样的基础和根基,希望你能牢牢掌握。
## 思考题
- 你们公司的系统和提供的服务中,有没有性能方面的指标要求?
- 这种指标要求是怎么表述的?比如是平均值,还是某些百分位数?
- 为什么要这样规定指标要求?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,114 @@
<audio id="audio" title="06 | 性能数据的分析:如何从大量数据中看出想要的信号?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ca/35/caaaaeb5a525e71830cbe2d1b38e1035.mp3"></audio>
你好,我是庄振运。
我们这一讲来谈谈如何分析我们所得到的性能数据。现代的应用程序和互联网服务系统都比较复杂,要关心的性能参数也很多,所以你从各种渠道得到的性能相关的数据量往往很大。那么要如何从大量的数据中找出我们所关心的特征和规律呢?这就需要你对数据做各种分析和对比了。
这一讲要解决的核心问题就是:**如何从大量数据中看出想看的信号?**
当人沉浸在大量数据中时,是很容易迷失的。而“不识庐山真面目”的原因,当然是“只缘身在此山中”了。但这不能作为借口,我们需要练就“慧眼识珠”的本领,做到对各种性能数据一目了然,才能够做出一针见血的分析。
为了帮助你练就这样的本领,今天我们首先讲一下常见的算法复杂度和性能分析的目的,然后针对一个性能指标来分析,再延伸到对多个性能指标进行对比分析,最后谈谈进行数据分析的几个教训和注意点。
## 算法的时间复杂度
先简单地聊一下算法的时间复杂度Time Complexity。复杂度一般表示为一个函数来定性描述该算法的期待运行时间常用大O符号表述。
考虑程序和算法的时间复杂度时,大家通常关注的是某个解决方案属于哪个时间复杂度。具体来讲,有六种复杂度是比较普遍的,这里按照从快到慢的次序依次介绍:
1. 常数时间O(1):判断一个数字是奇数还是偶数。
1. 对数时间O(Log(N)):你很熟悉的对排序数组的二分查找。
1. 线性时间O(N):对一个无序数组的搜索来查找某个值。
1. 线性对数时间O(N Log(N)):最快的排序算法,比如希尔排序,就是这个复杂度。
1. 二次时间O(N^2):最直观也最慢的排序算法,比如冒泡,就是这个复杂度。
1. 指数时间O(2^N) 比如使用动态规划解决旅行推销员问题。这种复杂度的解决方案一般不好。
把这几个算法复杂度放在一张图中表示出来,你可以清楚地看出它们的增长速度。
<img src="https://static001.geekbang.org/resource/image/1e/00/1e07480a265a7f017463bffba2293b00.png" alt="">
大体上来讲前四种算法复杂度比较合理而后面两种也就是N平方和指数时间就不太能接受了因为在数据量大的时候运行时间很快就超标了。
## 性能数据分析的目的
说完常见的算法复杂度,我们再来看看做性能数据分析的目的是什么。
性能相关的数据有很多种比如系统和模块的运行时间、客户访问延迟、客户滞留时间、服务吞吐率、程序的CPU占用时间等等。最常见的性能数据就是客户的访问延迟、吞吐量和运行时间。比如我们如果去分析某个应用程序或者代码模块的运行时间往往就会立刻暴露其相应的性能。
当收集到性能数据以后我们首先需要判断它们的值到底是正常还是不正常这就需要经验和知识才能判断。比如一个简单的数据库服务查询一般端到端的延迟也就几百毫秒。如果这个值是10秒钟那么就不太正常。发现不正常的数据一般就需要做更多的分析和测试来发现原因比如是网络延迟数据库的服务延迟抑或是其他问题。
再举一个例子如果我们是在测量硬盘的访问时间如果发现这个指标超过1秒那么很可能磁盘这边出异常了。
如果观测到针对某个指标的一系列性能数据那就需要判断这个指标有没有随着时间或者其他变量的变化而变差Regression和变好Improvement然后需要根据这个判断进行进一步的分析及采取措施。
我们还经常需要对某个性能指标做预测。这种情况就需要研究数据的趋势Trend。根据情况做些预测分析比如根据这个指标的历史数据来进行曲线拟合。
有时候某个性能指标会出现问题,比如如果应用程序有内存消耗和内存泄漏,那么我们就需要测量更多的相关指标来做深层的分析,来发现到底是哪个模块,哪行代码导致了内存泄漏。
我们也经常需要把几个性能指标的数据联系起来一起分析。比如发现系统的性能瓶颈在哪里是在CPU的使用上、网络上还是存储的IO读写上等等。如果应用程序和系统性能出了问题那么考察多个性能指标的数据和它们的关系可以帮我们做根因分析发现真正的问题所在。
## 对一个时间序列的分析
一般来讲,一个性能指标,按照时间顺序得到的观测值,可以看作是一个时间序列。很多分析就是针对这个时间序列进行的。
首先需要指出的是性能数据的时间序列往往不是均匀平滑的反而会有各种有规律的峰值。比如客户对一个网站的访问量是随着时间变化而变化的。每天24小时都不同对多数互联网系统来讲白天上班时间的访问量比较大每周7天里面工作日流量比较大而节假日比如新年的流量又和其他时间不同。所以需要你根据具体的情况来决定要不要做特殊的考虑。
再有一点就是对网站而言客户响应时间往往需要考虑百分位的数字比如P90、P95、P99甚至P99.9的用户响应时间。因为这些数字可以保证一个系统的响应时间是不是满足了绝大多数用户的要求。
那么,针对一个性能数据的时间序列,我们要如何看数据的规律和趋势呢?
经常使用的方法是进行**线性回归分析**Linear Regression。线性回归是通过拟合自变量与因变量之间最佳线性关系来预测目标变量的方法。线性回归往往可以预测未来的数据点。比如根据过去几年的每月消费支出数据来预测明年的每月支出是多少。
注意所谓的“最佳”线性关系是指在给定形状的情况下没有其他位置会产生更少的误差。如下图所示以平面点为例如果有N个样本点线性回归算法就是求一条直线Y=f(X)。使得各点到这个曲线的距离的绝对值之和最小。
<img src="https://static001.geekbang.org/resource/image/02/c1/0276bc0587f50bee356b49bee5b369c1.png" alt="">
除了研究数据的趋势和未来预测,还有几种重要的分析,比如**分类**Classification、**聚类**Clustering以及**决策树**Decision Tree。分类是将类别分配给数据集合帮助更准确地预测和分析。聚类是把相似的东西分到一组。决策树也叫分类树或回归树每个叶节点存放一个类别****每个非叶节点表示一个特征属性上的测试。
## 对不同时间序列的分析
在一大堆性能数据面前,经常需要比较各个性能指标的时间序列来确定一个系统和服务的瓶颈,也就是最制约系统性能扩展的资源。
在多数情况下瓶颈资源是常用的几种比如CPU、网络、内存和存储。但是有些情况下其他不太常见的资源也可能成为瓶颈比如转换检测缓冲区TLBTranslation Lookaside Buffer这个我们以后会讲到
如果几个时间序列在时间上是一致的但是对应不同的性能指标比如一个是CPU的使用率另一个是吞吐率。我们有时候需要研究时间序列的相关性Correlation of time series从中可以得出很多有用的观察推断。
数据的相关性是指数据之间存在某种关系,可以是正相关,也可以是负相关。两个数据之间有很多种不同的相关关系。比如,我们经常需要计算两个随机矢量 X 和 Y 之间的协方差covX, YCovariance)来衡量它们之间是正相关还是负相关,以及它的具体相关度。
## 数据分析的教训和陷阱
性能数据的分析并不容易,一不小心就会落入各种陷阱或者踩到坑。下面举几个需要特别注意的方面。
**第一是数据的相关和因果关系。**
有时候几个时间序列之间可以很清楚地看出有很强的相关性质,但是对它们之间的因果关系却不能判定。换句话说,通过单纯的数据分析可以证实数据的相关性,但是还需要其他知识才能更准确地判断谁是因、谁是果。这一点我们必须非常清楚,因为在很多性能问题讨论和根因分析的场合,我们非常容易武断地犯这样的错误,而导致走弯路。
更复杂的情况是有时候系统性能变坏,是因为几个指标互为因果,或者构成**环形因果**,也就是互相推波助澜。实际分析起来非常有挑战性,这就需要我们对整个系统和各个性能指标了如指掌。
**第二是数据的大小和趋势。**
面对性能相关的数据并判断它们是“好”还是“坏”是很难的。经常听到有人问一个问题:
“客户平均访问逗留时间多长比较好?”
这个问题没有一个简单的答案。这取决于每个网站的特性,以及我们想要实现的目标。对一个网站而言很正常的逗留时间,可能对另一个网站而言非常糟糕。在很多情况下,比单纯数字大小更重要的是数据的趋势,比如某个时期是上升还是下降,变化的幅度有多大等等。
**第三是数据干净与否。**
如果数据集合来自多个数据源,或者来自复杂的测试环境,我们需要特别注意这些数据里面有没有无效数据。如果不能剔除无效数据,那么整体数据就“不干净”,由此而得出的结论经常会“失之毫厘,谬以千里”。
**第四是对性能数据内在关系的理解。**
性能数据分析的核心就是要理解各个性能指标的关系并且根据数据的变化来推断得出各种结论比如故障判别、根因分析。如果简单地把性能数据当作普通的时间序列来分析那就往往没有抓住精髓。举个简单例子Linux系统的空闲内存其实就是一条时间序列它或许显示快到0了看起来性能问题出在这里。但是稍微了解Linux系统内存管理知识的人就知道这个指标非常不可靠。
## 总结
性能工程和优化离不开对大量性能数据的研究和分析,那么如何“拨开云雾见天日”,看出里面的端倪和问题呢?我们这一讲就讨论了几种情况,包括对一个和多个时间序列的分析;也讨论进行数据分析的时候需要注意的地方。
古人讲“格物致知”;对我们来讲,性能数据就是我们要“格”的物。只有合理而系统地分析这些数据,才能收获“守得云开见月明”的恍然大悟之感。
## 思考题
对数据的统计分析和处理需要遵循科学的方法,否则,如果处理不当,根据数据得出的结论会严重误导你。回想一下过去的工作中,有没有这样的例子?从这些例子中有没有学到教训?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,157 @@
<audio id="audio" title="07 | 性能数据的展示:一图胜千言,说出你的数据故事" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/53/bf/536102f14909ec2f72310c8ad0b3c1bf.mp3"></audio>
你好,我是庄振运。
这一讲我们来探讨一下如何把性能数据合情合理地展示出来,让别人一目了然。如果你想有效地说服别人,那么你展示的过程就要像讲一个有趣的故事,娓娓道来,让别人爱听才行。
数据的展示根据场景有不同的目的,所以不能千篇一律,需要量体裁衣。每一种场景下,数据展示要根据你的**具体目的**、**听众的特点**和**内容的特点**而采用合适的图表。然后用这些图表做支持,把一个精美的数据分析的“故事”讲出来。
## 性能数据展示的目标和目的
我们先理清一下性能数据展示的目的是什么。
我们前面讲过,性能工作相对复杂,牵扯的模块和构件较多,而且有时候也要牵扯几个不同的部门。数据分析有时是为了性能优化,有时是为了根因分析等等。所以性能数据展示的目的也就有很多种,主要有如下三种:
1. 向上级报告性能趋势和流量预测的结果;
1. 向运维部门描述性能问题的根因分析;
1. 向开发部门建议性能提升和代码优化。
目标不同,听众不同,如何展示数据自然也就不能相同。但不管如何,都要做到有根有据,条理清楚,层次分明。这样大家才会被你和你展示的数据所说服。具体来讲,你希望你的听众:
1. 同意你的理论方法和过程;
1. 信服你的推理分析;
1. 理解问题的核心;
1. 看出问题的根因;
1. 同意你的建议和方案;
## 性能数据展示的挑战和难点
目的清楚了,下面就是怎么做了。但是我们必须认识到,相对于一般数据的展示,性能数据的展示有其独到的特点、挑战和难点:
- **数据量大** :做性能测量时往往性能指标很多,每个指标一般都是一个时间序列。一个复杂系统/性能问题有几十甚至几百个性能指标是经常遇到的,所以总的数据样本经常以百万以上计。
- **数据复杂**:几十或几百的性能指标互相交互和影响,需要考虑它们之间的复杂关系。
- **性能问题复杂**:性能分析不是简单地死扣数据,还需要考虑互联网系统的协议,计算机的设计,和软硬知识的结合。
- **牵扯的模块多**:很多性能问题都不是孤立的,都和其他模块和子系统,甚至客户请求有关系。
## 数据展示的经验
在考虑用什么方式来展示数据时,的确有些讲究,这就需要多借鉴别人的经验。在这方面,我总结了下面六条经验。
1. **按客人来备菜**:既然展示的听众不同,所以千篇一律的用同一种展示方法绝不可行。比如向领导和管理层汇报时,就要注重宏观层次,技术不要钻地太深。
1. **有啥菜吃啥饭**:要根据手头数据的特点来决定展示方法。比如你有好几组相关的数据,有很有趣的趋势,那就要用线图来展示趋势和相关性。
1. **给足上下文**:很多工程人员容易犯的错误,就是想当然地以为别人也都了解问题的背景,然后一上来就展示很多细节。其实除了你自己,没有第二个人更了解你要解决的问题和要展示的数据,所以一定要给足背景介绍和上下文信息。
1. **用图讲故事**:人人喜欢听故事,而且是有趣的故事。如果你能把整个分析和推理的过程,变成一个引人入胜和图文并茂的故事来讲,那我保证你的展示会非常地成功。
1. **和听众交互**:尽量鼓励听众参与讲故事的过程。有两种类型的数据叙事:叙述型和探索型。叙述型是通过描述告诉观众具体结论;而探索型是鼓励观众探索数据以得出结论。虽然探索型叙事稍微多花一点时间,但若能成功运用,听众更容易被说服;因为结论是他们自己得出的,而不是你告诉他们的。
1. **总结重要点**:在数据展示的最后,一定要简洁明了地总结你的展示。比如你希望听众最后只记住三句话,是哪三句呢?根据你的展示目的,这三句或是相关数字,或是趋势,或是问题本质,或是解决方案。
## 数据图表的种类
数据往往是枯燥的,所以我们要想办法变枯燥未有趣。用图表来展示数据就是一个好方法,但关键是要用合适的图表来展示不同的数据。
虽然数据图表的种类很多但常用的就10种左右。我来简单地给你总结一下这几种常用的图表分别适合什么情况和表示什么样的数据。
### 表格Table
表格你一定经常用,它的优点是可以结构化的方式显示大量信息;不幸的是,这个优点恰恰也是缺点。很多用户其实对数据的趋势比具体的数值更有兴趣,这种情况下用表格就不是那么直观。
### 线图Line Chart)
如果我们希望针对一个变量,显示一段时间内这个变量的变化或趋势,线图就最合适了。比如我们经常表示的时间序列图就是最直白的线图。
另外,它也适用多个变量的情况,可以很直观地显示两个或多个变量之间的关系,比如趋势和相关性。
### PDF和CDF图
对于一个变量的概率数据,我们经常会显示这两个互相关联的图。二者可以看作是稍微特殊的线图。
**PDF**概率密度函数Probability Density Function是连续型随机变量的概率密度函数或简称为密度函数。就是一个描述这个随机变量的输出值在某个确定的取值点附近的可能性的函数。
**CDF**: 累积分布函数 (Cumulative Distribution Function)又叫分布函数是概率密度函数PDF的积分。换句话说PDF就是CDF的导数。CDF能完整描述一个实随机变量X的概率分布。
我们举一个前面讲过的例子比如标准正态分布。它的PDF和对应的CDF是这个样子。
<img src="https://static001.geekbang.org/resource/image/53/e3/533c87181e922b207e52fc8d323521e3.png" alt="">
### 面积图Area Charts
面积图类似于线图,但两者之间有细微的差别。面积图的重点是阴影线下方的区域。
如果想用面积图表示几组数据,那么具体也有两种选择:可以选择采用叠加面积图或非叠加面积图。
比如下图就是一个叠加面积图,它显示了几个产品在不同季度的单季产量和总产量。
<img src="https://static001.geekbang.org/resource/image/6b/bb/6bad8b4e214f7d9d91170646119cf0bb.png" alt="">
### 柱状图和条形图Bar Charts
用于比较不同类别的数量,比较容易理解。如果你想表示好几个变量,那么也可以选择叠加方式来显示。
比如下面这个柱状叠加图,显示的是和上面的面积图同样的数据,只是表示的方法不同。
<img src="https://static001.geekbang.org/resource/image/81/5f/81f1a8237cdd53cb631d08693e978a5f.png" alt="">
### 散点图Scatter Plots和气泡图Bubble Charts
**散点图**显示沿两个轴绘制的两个变量的值用点的模式揭示它们之间存在的任何相关性。比如两个变量分别是CPU使用量和客户吞吐率那么我们可以期望散点图会显示比较强的同一趋势。
**气泡图**类似于散点图,但它可以显示三个数据项之间的变化。除了两个变量分别为是横轴和纵轴外,气泡的大小代表第三个变量。
<img src="https://static001.geekbang.org/resource/image/26/31/26eb07e8905372a40c41c49b64dfc131.png" alt="">
我们举一个例子。比如一个公司里面有十个互联网产品,每个产品运行在不同数目的服务器上面。这些产品分别有不同的内存使用率和网络使用率。假设我们想分析一下产品的内存和网络的使用情况,然后决定下一个季度购买什么样的服务器。我们就可以用气泡图来表示,网络和内存的使用率来作为横轴和竖轴,气泡的大小代表每个产品的服务器数目。
通过这个气泡图,我们非常直观地发现,某个产品的气泡比其他产品都大,而且它的两种资源使用率都很低。整体来言,下个季度新采购的服务器可以不需要那么多内存和网络资源。
### 饼图和圆环图Pie Charts Donut Charts
当需要显示比例数据或者百分比时,饼图最佳。由于饼图表示零件与整个实体之间的大小关系,因此零件需要总和必须为有意义的整体。
一个适合饼图表示的性能数据例子是客户请求的来源分布。但你在使用时需要注意的是,饼图最好用来显示六个或更少的类别。如果太多类别的话反而描述不清楚。
举一个前面讲面积图时候用过的例子,公司的四种产品在春季的销售分布,就可以用下面这个饼图表示。
<img src="https://static001.geekbang.org/resource/image/54/02/54e07c61e57f08f398769c2d10499702.png" alt="">
### 树形图Treemaps
树形图对于显示类别和子类别之间的层次结构和比较值非常有用,同时也能保留较多细节。树形图可以帮助我们很直觉地感知哪些区域最重要。另外,还可以通过将颜色编码的矩形嵌套在彼此内部,来更好地实现目的,并用加权以反映它们在整体中的份额。
比如下面这个树形图,它描绘了一个国外的产品,采用不同营销渠道的价值,然后按国家/地区细分。
<img src="https://static001.geekbang.org/resource/image/39/bc/39ccc1ee89312d7d7d78009ad66311bc.png" alt="">
这里的营销渠道包括Email、Social Media、AdWords等。从这个图中你一眼就能看出从营销渠道上来看AdWords是最成功的营销渠道因为它对应的面积最大。但是从国家层面来讲美国United States是所有渠道中最有价值的目的地同样是因为对应的面积最大。
### 热图Heatmaps
热表以表格格式来表示数据,其中每个格子是用颜色,而不仅仅是用数字来展示。这些颜色分别对应包含定义的范围,如绿色代表小的值,黄色代表一般的值和红色代表大的值。
比如分析几台服务器的在一天里面CPU的使用率。我们可以把每台服务器在每个时段的CPU使用率分为小、较小、中等、较大和大等几个范围。然后相应地着色。
我们看下图横坐标代表时段纵坐标是服务器每个值就是CPU使用率。
<img src="https://static001.geekbang.org/resource/image/0e/f4/0ea22fae8afe53bad9763d75a811b6f4.png" alt="">
虽然每个使用率其实是数字但是把数字编码为颜色的好处是颜色编码格式使数据更容易理解。比如我们马上就可以看出早上10点的时候服务器1和5的CPU使用率很高因为它们的格子是红色。
## 总结
<img src="https://static001.geekbang.org/resource/image/b6/ab/b65ebe300caf5f7abd6bd79ac70abcab.png" alt="">
我们都学过宋朝的词人柳永的词,他有两句词是这么写的:
“便纵有千种风情,更与何人说?”
这里他其实是在感叹自己虽有满腹经纶和深厚情感,但无人可以诉说。我们虽然不是词人,但在做过性能测试或者根因分析后,可能经常会有很多新奇的发现,希望向同事和领导讲解。
但是性能问题往往牵涉很广,描述起来并不容易,别人可能很难理解。这时候我们的感觉就是“我有千种风情”,可是如何说啊?
希望通过这一讲,能让你对在图文并茂地来“诉说”这方面能有些提升。
## 思考题
- 对本讲介绍的几种图形,你对哪些比较熟悉并经常使用?
- 哪些是不熟悉的但觉得可能会很有用的?
- 对不熟悉的图形,你能不能花十几分钟时间去仔细学习一下并尝试使用呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,136 @@
<audio id="audio" title="08 | 经验总结:必须熟记的一组常用性能数字" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c0/b2/c0e5d19ef81c83d09ee25e15955262b2.mp3"></audio>
你好,我是庄振运。
今天这一讲是”数理基础“这一部分的最后一讲,我在这一讲会给你总结一组性能相关的常用的数字。这些数字在做性能和设计方面的工作时经常用到。它们就像九九乘法表一样,我希望你能熟记在心。
记住这些数字的好处是,每次看到一个性能相关的数据的时候,我们立刻就能知道这个性能数据有没有问题。
举个简单例子如果我们看到一个硬盘的IO读写延迟经常在500毫秒左右我们立刻就知道这里面有性能问题。反之如果硬盘IO读写延迟小于1毫秒我们可以马上推断——这些IO读写并没有到达硬盘那里是被操作系统缓存挡住了。这就是大家常说的“对数字有感觉”。
人们常说“腹有诗书气自华”。同理,如果我们能对系统和程序运行中常见的性能指标了如指掌,就有可能达到那种“一眼就看出问题”的大师级别。
为了方便理解和记忆我把这些数字分成几大类分别是存储、CPU、操作系统、内存和网络等并且会给出具体的单个数值。
## 有言在先
但是我必须强调说明的是,我之所以给出具体的单个数值,是为了方便你记忆,并让你对性能指标“有感觉”。因为**单个值**比给出**数值范围**更直观。
比如传统硬盘的IO延迟如果我冠冕堂皇地说“IO延迟的大小取决于很多因素比如硬盘型号、IO大小、随机还是连续、磁头离数据的远近等从小于1毫米到几秒钟不等。“这样的说法当然对但是并不能帮助你找到数字的感觉所以直观指导意义不是很大。
所以我想强调,我给出的数字仅供参考,帮助你记忆和理解,更重要的目的是**让你对不同环境下的性能数据有所感觉**。你要更加注重它们之间的量级比较比如SSD的随机IOPS的性能可以轻松地达到普通硬盘HDD的1000倍以上。
至于具体的性能数字的值大小,却也可能对一个非常具体的场景不那么匹配。这有几个原因:
1. 因为市场上有很多种类和很多厂家的产品具体的值都不一样比如SSD的一些性能指标就是如此。
1. 因为每个具体场景都不同比如IO的读写大小。还有就是我们的技术不断进步就如同CPU的频率具体的值是一直在变的。
这些性能数据多半和**延迟**有关所以要弄清楚这些延迟的单位。你应该都知道一秒钟是1000毫秒ms一毫秒是1000微秒us一微秒是1000纳秒ns
## 存储相关
我们先看存储相关的性能数据。存储有很多种常用的是传统硬盘HDD, Hard Drive Disk和固态硬盘SSD, Solid State Drive。硬盘的厂家和产品多种多样而且具体的配置也有很多种比如大家熟悉的磁盘阵列RAID)。我们这里仅仅选取最普遍的硬盘和最简单的配置。
值得一说的是SSD。最近几年SSD的技术发展和市场演化非常迅速。随着市场规模的增大和技术的进步SSD的价格已经极大地降低了。在很多大规模的在线后台系统中SSD几乎已经成了标准配置。
SSD的种类很多按照技术来说有单层SLC和多层MLCTLC等。按照质量和性能来分有企业级和普通级。根据安装的接口和协议来分有SAS、SATA、PCIe和NVMe等。
对所有的存储来说,有三个基本的性能指标。
1. IO读写延迟。一般是用4KB大小的IO做基准来测试
1. IO带宽一般是针对比较大的IO而言
1. IOPS就是每秒钟可以读写多少个小的随机IO。
下面这个表格列出了几种存储介质和它们的性能数值。
<img src="https://static001.geekbang.org/resource/image/46/65/4601c4eb6ecc8e48643d189e7af72565.jpg" alt="">
我们这里考虑三种情况: 传统硬盘SATA SSD和NVMe SSD。你可以看到一般传统硬盘的随机IO读写延迟是8毫秒的样子IO带宽大约100MB每秒而随机IO读写一般就是每秒100出头。
SSD的随机IO延迟比传统硬盘快百倍以上IO带宽也高很多倍随机IOPS更是快了上千倍。
## CPU和内存相关
再来看看CPU。说起CPU相关的性能数字就必须先说CPU的时钟频率也就是主频。主频反映了CPU工作节拍也就直接决定了CPU周期大小。
**主频和周期大小。<strong>比如基于英特尔Skylake微处理器架构的i7的一款其主频为4GHz那么每一个时钟周期Cycle大约**0.25纳秒</strong>ns
CPU运行程序时最基本的执行单位是指令。而每一条指令的执行都需要经过四步指令获取、指令解码、指令执行、数据存入。这些操作都是按照CPU周期来进行的一般需要好几个周期。
**CPI和IPC**
每个指令周期数CPI和每个周期指令数IPC其实是孪生兄弟衡量的是同一个东西。
CPIcycles per instruction衡量平均每条指令的平均时钟周期个数。它的反面是 IPCinstructions per cycle。虽然一个指令的执行过程需要多个周期但IPC是可以大于1的因为现代CPU都采用流水线结构。一般来讲测量应用程序运行时的IPC如果**低于1**这个运行的系统性能就不是太好需要做些优化来提高IPC。
**MIPS**
MIPS就是每秒执行的百万指令数。
我们经常会需要比较不同CPU硬件的性能MIPS就是一个很好的指标一般来讲MIPS越高CPU性能越高。MIPS可以通过主频和IPC相乘得到也就是说MIPS=主频×IPC。这个很容易理解比如一个CPU频率再高IPC是0的话性能就是0。假设一个CPU的主频是4GHzIPC是1那么这个CPU的**MIPS就是4000**。注意的是MIPS是理论值实际运行环境数量一般小于这个值。
**CPU缓存**
一般CPU都有几级缓存分别称为L1、L2、L3按这个顺序越来越慢也越来越大当然成本也越来越低。L3有时候也称为LLCLast Level Cache因为L3经常是最后一级缓存。多核CPU的情况下一般L1和L2在核上而L3是各个核共享的。
我用下面的表格来表示一款2GHz主频的CPU进行寄存器和缓存访问的一般延迟分别用时钟周期数和绝对时间来表示同时也给出在每个CPU核上面的字节大小。重复一下数字仅供参考因为每款CPU都不同。
<img src="https://static001.geekbang.org/resource/image/7d/3d/7d5c3088fbd1463637ffb0641fc68b3d.jpg" alt="">
比如一般L3的访问需要40个时钟周期2GHz主频的话就是20纳秒大小一般是每个核平均下来2MB的样子。
为了方便对比,我们把内存的性能也放在同一个表格里。
值得一提的是现在的NUMA非统一内存访问Non-Uniform Memory Access处理器会有**本地**和**远端内存**的区别,当访问本地节点的内存是会快一些。
## 操作系统和应用程序相关
我们刚刚谈了硬件方面,下面看看软件,也就是操作系统和应用程序。
首先,你需要弄清楚如下的几个重要概念和指标。
**1.指令分支延迟**
CPU需要先获取指令然后才能执行。获取下一条指令时需要知道指令地址如果这个地址需要根据现有的指令计算结果才能决定那么就构成了指令分支。CPU通常会采取提前提取指令这项优化来提高性能但是如果是指令分支那么就可能预测错误预先提取的指令分支并没有被执行。
指令分支判断错误Branch Mispredict的时间代价是很昂贵的。如果判断预测正确可能只需要一个时钟周期如果判断错误就需要十几个时钟周期来重新提取指令这个延迟一般在**10纳秒**左右。
**2.互斥加锁和解锁**
互斥锁Mutex也叫Lock是在多线程中用来同步的可以保证没有两个线程同时运行在受保护的关键区域。使用互斥锁的时候需要加锁和解锁都是时间很昂贵的操作每个操作一般需要几十个时钟周期**10纳秒**以上。
**3.上下文切换**
多个进程或线程共享CPU的时候就需要经常做上下文切换Context switch。这种切换在CPU时间和缓存上都很大代价尤其是进程切换。在时间上上下文切换可能需要几千个时钟周期**1微秒1us级别**。在缓存代价上多级CPU缓存和TLB缓存都需要恢复所以可能极大地降低程序线程和进程性能。
## 网络相关
互联网服务最终是要面向终端客户的,客户和服务器的延迟对用户的服务体验至关重要。
网络的传输延迟是和地理距离相关的。网络信号传递速度不可能超过光速一般光纤中速度是每毫秒200公里左右。如果考虑往返时间RTTRound Trip Time那么可以大致说每100公里就需要一毫秒。北京到深圳约2,000公里RTT就是20毫秒上海到乌鲁木齐或者美国的东西海岸之间距离差不多4,000公里所以RTT是40毫秒左右中国到美国比如北京到美国西海岸旧金山差不多10,000公里RTT就是100毫秒。
在数据中心里面一般的传输RTT不超过半毫秒。如果是同一个机柜里面的两台主机之间那么延迟就更小了小于0.1毫秒。
仔细想想的话你就会发现直线距离本身还不够因为数据是通过骨干网光纤网络传播的。如果光纤网络绕路的话那么实际的RTT会超过以上估算数值。
另外要注意的是传输延迟也取决于传输数据的大小因为各种网络协议都是按照数据包来传输的包的大小会有影响。比如一个20KB大小的数据用1Gbps的网络传输仅仅网卡发送延迟就是0.2毫秒。
下面这个表格就总结了几种环境下的端到端的距离和RTT。
<img src="https://static001.geekbang.org/resource/image/d0/cd/d0c2d6ebd3efa2dd8666cb26fb8002cd.jpg" alt="">
## 总结
今天讲了几十个平时经常用到的性能数字,希望起到抛砖引玉的效果。你可以在此基础上,在广度和深度上继续扩展记忆。
<img src="https://static001.geekbang.org/resource/image/82/d8/82ee900f120d3717e67da7ac54a6ffd8.png" alt="">
宋代诗人苏轼曾经作诗夸奖朋友:“前身子美只君是,信手拈来俱天成”,这里的“子美”是唐朝大诗人杜甫的字。这两句是夸朋友写文章写得好,能自由纯熟的选用词语或应用典故,用不着怎么思考,不必费心寻找,如同杜甫转世。
我们如果对各种性能数据足够熟悉,如掌上观纹,自然也就能达到那种对性能问题的分析信手拈来的境界。
## 思考题
假设你们公司有个互联网服务要上线服务的要求是用户端到端响应时间不能超过40毫秒。假设服务器在武汉那么对上海的用户可以达到响应时间的要求吗
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,175 @@
<audio id="audio" title="25 | 如何在生产环境中进行真实的容量测试?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cf/28/cfd9d042814405dbc6c5693f2833c728.mp3"></audio>
你好,我是庄振运。
从今天开始,我们进入新的模块:性能工程实践。在这一模块中,我会讲述在实际生产环境中应用性能工程的场景、案例。这些场景和案例都是针对大规模互联网服务,是在解决实际性能问题后总结的经验。
今天我要讲的主题是“在生产环境中进行真实场景的压力测试”。这来源于我对LinkedIn公司生产实践的总结。
LinkedIn为超过5.9亿用户提供服务在性能优化的过程中经常会遇到这类问题一个服务可以承受的最大QPS是多少要满足100K QPS的服务需求我需要多少服务器……
怎么解决这些问题呢,有一个大招就是在生产环境中进行真实的容量测试。
关于这个实践的详细方案和技术细节,我们曾经发表过一篇研究论文([IEEE ICWS](https://ieeexplore.ieee.org/document/8029816)并且很荣幸地获得了IEEE最佳论文奖推荐你去读一读。
今天我就带你从这个场景的源头出发,一步步探索到最后的具体落地方案。
## 为什么需要在生产环境中进行容量测试?
既然我们要解决一个具体的问题,那我们一定要先问个“为什么”。
为什么要在生产环境中进行容量测试呢?要回答这个问题,我们还得再往前追问,为什么说真实的容量测试很重要呢?
我想这个问题不难回答,你应该已经有自己的答案了。
我们都知道,一个在线互联网公司的存活和发展,靠的是它提供的互联网在线服务,自然也就依赖于这些服务的性能和稳定性。要保证每个服务都能够稳定地运行,我们必须为之提供足够的服务容量,比如适当数量的服务器。
那么要如何保证服务容量足够呢?首先就必须做好**服务容量的预测**。
要预测服务容量,我们就需要做容量的性能测试。一般是先确定每台单独的服务器可以支撑多少服务流量;然后用这个单台服务器的数据,来决定这个服务整体需要多少台服务器。这种测试其实就是我们前面讲过的容量测试。
举个具体例子如果测试结果表明一台服务器最多可以支撑100个QPS那么要满足100K QPS的服务需求总共就需要部署一千台服务器。
讲到这里,我想再补充几句。我们讲了这么多服务容量的事儿,那“服务”到底都指的是什么呢?
一般来说公司提供的服务大致上分为两种前端服务和后端服务。前端服务是什么样呢包括各种服务的登陆页面和移动App这些服务会直接影响用户的体验。后端服务呢一般是为前端服务和其他后端服务提供数据和结果。后端服务可以有多种比如键值数据存储服务例如Apache Cassandra和公司内部的各种微服务。
服务容量预测的重要性我们了解了,那测试为什么需要在生产环境中进行呢?
你是不是想到了,在非生产环境中的容量测试,执行起来肯定更简单啊!没错,的确会更简单,但是,在实验环境或者其他非生产环境中做这样的测试,比如采用人工合成的流量负载,会非常不准确。
这是因为实际的生产环境里面,有多个特殊因素会导致和非生产环境中不同的结果。比如:
1. 客户需求会随着时间而变化,例如高峰时段与非高峰时段的流量就很不一样;
1. 用户请求的多样性,例如不同国家的查询类型不同;
1. 负载流量的规模,基础架构设施的变化,例如服务的软件版本更新,微服务互相调用的变化等等。
所以,对一个重要而复杂的互联网在线服务,由于难以在非生产环境中进行准确的容量测试,我们经常需要转向真正的生产环境,使用实时而真实的客户流量负载来测试。
所以,想要在非生产环境中进行准确的容量测试基本上是做不到的。而对一个重要而复杂的互联网在线服务,能够做到准确的容量测试又太重要了。因为准确的容量数据,是保证线上服务的可靠运行和控制公司成本的基础。
那怎么办呢?这时候我们就需要转向真正的生产环境,使用实时而真实的客户流量负载来测试。
## 如何在真实生产环境中进行容量测试?
那在真实生产环境中进行容量测试,要如何做呢?
一般来说我们需要把生产环境的流量进行重定向让这些重定向的流量实时地驱动运行SUT的单个或者几个服务器。根据重定向的流量大小会产生不同级别的流量负载。通过仔细地操作重定向的多少并且把握测试的时间我们可以获得非常准确的运行SUT服务器的容量值。
但是,生产环境中的容量测量也有诸多挑战,你需要特别小心。
第一个挑战是需要**设计一个控制重定向流量的机制**。这个机制要能够根据其他一些参数,来调整重定向的流量多少。
第二个挑战(也是最大的挑战)是这种**重定向生产流量可能会影响真正的客户**。因为我们会把客户请求重定向到某个服务器,并且会不断给服务器加压,直到这个服务器接近超载,那么这个服务器上所有的客户请求的延迟都会受影响,也就是可能会变大,用户性能也就可能会受到损害。
为了尽量减少对客户的影响我们的容量测试需要设计合适的机制来将这种可能的损害降到最低点。系统里面必须有一个模块来不断地监测客户的性能一旦到达临界点就停止继续加压的操作甚至适当减压。这就是所谓的“非侵入性”Non-Intrusive
还有一个挑战是**对于测试时间的控制**。既然重定向生产流量可能影响客户性能,当然是测试的时间越短越好。可是测试时间太短的话,又可能会影响数据的稳定性。
为了应对这些挑战准确地确定服务容量的极限并精确定位容量瓶颈LinkedIn采用了一个解决方案我们将它命名为RedLiner。
宏观来讲Redliner是固定一个生产环境中的SUT这个SUT包括服务器和上面运行的被测服务然后不断地把其他服务器上的流量重定向到这个SUT服务器上面。随着流量的不断增大这个SUT服务器的资源使用也就越来越多所服务的客户请求的性能比如端到端延迟就会越来越差。
直到客户请求的性能差到一个定好的阈值比如端到端延迟是200毫秒流量重定向才会停止。这个时候基本就可以确定SUT服务器不能再处理任何额外的负载。此时获得的容量结果就是SUT服务器的最大容量。
想要实现上述的测试需要好几个模块一起合作。不过在讲RedLiner具体的各个模块之前我们要先梳理一下合理方案的设计原则一共是五条
1. 要使用**实时流量**以确保准确性。
1. 尽量不影响生产流量,这就需要**实时的监测和反馈**模块。
1. 可以**定制重定向**等行为规则;对不同的服务和不同的场景的测试,各种性能指标和阈值都会不同。
1. 能**自动终止测试**,并把**测试环境复位**,尽量减少人工干预。
1. 支持基于日历和事件的**自动触发和调度**。
那这个方案具体是怎么实现的呢?
现在我们来看看解决方案的高层架构,如下图所示,这个方案主要包括四个组件:
- 核心控制器LiveRedliner
- 重定向流量的TrafficRedirector
- 收集性能数据的PerfCollector
- 分析性能数据的PerfAnalyzer
<img src="https://static001.geekbang.org/resource/image/60/a3/60eb47fd1b2749af61715eaa7dda93a3.png" alt="">
我先来一一给你介绍下这四个关键组件。
1.**核心控制组件LiveRedliner**
核心控制组件是整个系统的神经中枢,负责总体调度容量测试的过程。比如何时发起测试,何时终止测试,何时需要增加更大的流量等等。
用户可以自己定义一些特殊的规则来更好地控制整个测试。用户可以自定义性能指标的阈值。这些性能指标可以是用户的端到端延迟包括各种统计指标比如P99。也可以把几个不同的性能指标组合起来实现复杂的逻辑比如“端到端延迟不超过200毫秒并且错误率不超过0.1%等”。
2.**重定向流量组件TrafficRedirector**
重定向流量组件负责对生产流量进行重定向。具体的实现机制,根据被测试的服务类型分为两种:前端服务和后端服务。
用于**前端服务**时Redliner是通过客户请求的属性例如用户ID、语言或帐户创建日期来决定是否对一个客户请求来进行重定向的。这个转换也很简单可以是取模机制。
举个例子来说如果Redliner需要重定向1的流量它可以把用户的一个属性比如userid转换成整数然后执行用100来取模的操作并且和一个固定整数值作比较。
用于**后端服务**时重定向流量可以通过另外一个叫做“资源动态发现和负载均衡”的模块来实现。在LinkedIn我们很多的服务负载均衡机制一般会采用一个服务器列表URI集群以相对应的权重值来决定一个请求发送到哪个服务器。
假设这样一个机制有10个可用的URI集群并且最初所有这些集群都接收等量的流量即每个URI的权重为10。如果Redliner决定将20的流量重定向到特定的URI即SUT那么它可以为SUT的URI分配20的权重。
3.**性能数据收集组件PerfCollector**
容量测试必须采集各种类型的性能指标例如CPU内存和QPS等。这些性能指标的作用就是确定SUT何时达到其容量最大值以及容量值是多少。
PerfCollector组件负责收集各种性能指标包括系统级和服务级的指标。这个组件运行在所有受监视的节点。组件传递的数据量通常很大因为一般要监测较多的性能指标。所以最好采用扩展性好的实时消息传递系统来把这些性能数据及时传到其他组件尤其是下面要介绍的性能分析组件PerfAnalyer。
我们采用的消息传递系统是[Kafka](https://time.geekbang.org/column/intro/100029201)。Kafka也是由LinkedIn设计并开源的扩展性和性能都很好建议你也尝试采用。
4.**性能分析组件PerfAnalyzer**
收集性能指标是第一步下一步就是分析性能并采取相应的措施。具体来说可以根据性能指标的值来确定SUT是否饱和。
根据性能数据和用户定义的规则如果发现当前的SUT仍有空间来承担更多的负载我们可以将更大比例的实时流量重定向到该SUT。否则如果SUT显示饱和迹象那么重定向的流量百分比就应该降低。PerfAnalyzer组件能分析收集的数据并确定是否有特定指标是否违反用户定义的规则。
Redliner作为一个完整的解决方案通过几个模块互相配合来实现真实的线上容量测试。通过动态地调整线上的流量直到让被测系统临近超载状态从而获得准确的单位服务容量。
现在我们再来换个角度看一下RedLiner的具体操作流程。流程图如下所示。
<img src="https://static001.geekbang.org/resource/image/f1/51/f1da037ebb3a7e3a98fcdf31b7a37851.png" alt="">
首先是**跟据以前的测试历史数据决定一个初始重定向数量也就决定了SUT的初始负载**。理论上讲,每个测试都可以从0开始;但是如果起点流量太低,整个测试需要花很长时间,所以最好能利用历史数据,从一个比较高的起点开始。
在使用历史数据的基础上,我们还需要**决定总体测试时间**。决定了时间后每次调整重定向流量的百分比也就确定了。这个调整的数值大小也就是所谓的“步距”如同人迈步走路每一步都有大小。对每个百分比我们一般固定测试3分钟让数据稳定下来然后调用PerfAnalyzer分析并决定下一步。
举个例子假如我们决定总体测试60分钟并且是从0%开始。因为每一个百分点需要测试3分钟那么我们就会决定调整的“步距”大小是5%因为最大就是100%。如果PerfAnalyzer决定需要继续增加或者减少重定向百分比那么就按照前面决定的步距进行相应的调整。
## 一起来看两个生产环境的数据
刚才讲了一大堆如何实现这个解决方案的内容现在我们来看看两个生产环境中的实际数据来直观地感受一下这个系统的特点。下图显示了典型Redliner运行的特征。<br>
<img src="https://static001.geekbang.org/resource/image/b5/0f/b532a67de294378cd7ee9bf4ede3b00f.png" alt="">
这是一次完整的容量测试,持续了一个小时。
第一幅图是QPS也就是系统吞吐量。蓝色线表示SUT有望实现的目的QPS。红色线是SUT实现的实际QPS。我们可以看到实际的QPS值持续变化这个变化表明了Redliner的控制和探测过程就是一直在动态增加和减少重定向流量百分比。
<img src="https://static001.geekbang.org/resource/image/53/45/53a13bbb560a6c03ac89507e5e42c845.png" alt="">
第二幅图,标识出了所测量的客户查询等待时间的中值。
比较这两幅图你可以看到开始测试的阶段RedLiner不断提高重定向百分比实际的流量持续增加同时客户感受的查询等待时间也慢慢加大。
等到SUT不堪重负时查询等待时间也就太大了超出了客户能接受的阈值40毫秒。所以RedLiner决定逐步降低重定向百分比最后重置到初始状态。
## 总结
我们这一讲介绍了一个在生产环境中,进行真实场景压力和容量测试的方案,这里面的关键点,是**逐步而智能地把一部分流量重定向到被测试的系统上面**。
这个案例是我们在领英的生产实践,但我觉得在你的公司里实现这么一个类似的系统一点也不难。希望这些分享能帮助你设计和实现。
<img src="https://static001.geekbang.org/resource/image/2d/94/2d5aac1c39d5c8ce8467d5752f080e94.png" alt="">
唐代诗人白居易的《长相思》最后的几句是:“愿作远方兽,步步比肩行。愿作深山木,枝枝连理生。”说的是主人公愿意与心爱的人相守到老,哪怕是做山林中的野兽和树木,一起步步连心,亦步亦趋,比肩而行,并肩而居。
我们用实际的生产负载做容量测试时,要小心控制重定向的流量“步距”大小,尽量“小步勤挪”,并且实时地观测,才不会影响客户的体验,并且得到比较准确的结果。
## 思考题
你们公司有没有类似的解决方案,来准确地测量一个服务需要的容量呢?如果有,和我讲的具体方案有何异同?不同的地方是基于什么考虑呢?
如果没有,你可以考虑实现一个,我相信一定会让老板对你另眼相看的。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,182 @@
<audio id="audio" title="26 | 怎么规划和控制数据库的复制延迟大小?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/38/0c/38196cd5ac5de8b76e29e825c142a60c.mp3"></audio>
你好,我是庄振运。
在正文开始前首先要祝你新年快乐今天我们要通过我在LinkedIn领英公司做过的一个项目来学习如何控制数据库复制延迟。
我在LinkedIn工作的时候就遇到过因为数据复制延迟太大而导致的生产事故。当时出现了这样的情况一个用户刚刚更新了自己的照片可是他的朋友们却迟迟看不到更新。
你是不是觉得LinkedIn又不是微博看个照片而已早点晚点也没什么大不了的
但换个内容这性质就严重了。LinkedIn网站上面会播放广告在一定时间内广告商往往会设置一定的广告投放预算限额比如一天一万元。如果广告投放的收入统计数据被延迟就会导致很严重的统计错误实际支出严重地超过预算。一切有关钱的事可都是大事。
所以,从用户数据的有效性、时间性和一致性来考虑,数据的传输复制延迟当然是越小越好。
## 什么是数据库的复制延迟?
那到底什么是数据库的复制延迟呢?如果让我用一句话来说,数据库的复制延迟其实就是当线上服务需要多个数据库时(比如为了分散流量),一条信息从源头数据库传递复制到下游数据库时经过的延迟。
还是用LinkedIn的例子来说明。LinkedIn的全球用户已经超过5.9亿用户流量异常庞大。这些用户流量是由用户的各种活动而生成的事件数据。LinkedIn的系统要求是实时地捕获这些用户数据并不断存储在后台数据库中。这些存放在数据库中的用户数据很重要会被各种应用程序和其他服务读取比如广告投放就需要消费这些数据。
在消费这些数据的时候,理论上来说,数据的消费者可以直接连接到后台数据库,去读取这些数据。但是,这些用户数据流具有大数据的几个特征,包括大规模和高可变性,并不适合直接连接后台数据库。因为,这会对后台数据库造成很大压力,而且不能保证读取的延迟。
为了解决这一挑战,当今主要的互联网公司,往往部署一整套的数据处理系统。这一整套系统通常由几个模块构成,包括数据事件的捕获、数据的存储、数据的复制传输、数据的读取。
在这个系统中,**数据的复制传输**的作用很特别,是用来隔离数据库和数据使用者的。
采用数据复制传输模块的好处非常大,它可以减轻源头数据库的压力。因为复制传输可以进行级联,分散了用户流量,能够让系统的可扩展性更好。但这里有个前提,那就是,要保证数据的复制延迟不能太大,否则会造成很糟糕的业务影响,比如影响广告业务的收入。
我们今天就针对这个数据复制传输系统,来探讨如何通过合适的容量规划和分析,来控制数据的复制延迟。
要想理解后面要讲到的容量规划,你得先去**了解生产环境中的用户流量特点**才行。
下面的图显示了一个互联网服务的在线用户流量。
<img src="https://static001.geekbang.org/resource/image/18/97/18468a1e9d01b03525183ee05ec67597.png" alt="">
时间范围是连续的六周也就是42天的流量。你可以看到用户流量呈现出了非常强的重复模式基本上是以一周为一个周期每周内五个工作日流量比较大而周末的两天流量比较小。而且无论哪天每天内部都有一个峰值。
我们接着研究每一天内流量的周期性模式。对于每个工作日和周末流量的形状也是非常有规律的曲线。下图显示了一个工作日中也就是24小时内的流量变化。你可以清楚地看到每天都会有一段时间的高峰期约为8小时。
<img src="https://static001.geekbang.org/resource/image/2b/e7/2bbe9265fdb91c6364ead53533c46ae7.png" alt="">
我们也对比了工作日和周末的流量变化结果也是毫不奇怪的工作日峰值一般远高于周末的峰值。二者的峰值之间差不多有4倍的差距。
你只有了解了用户流量特征,才能在实际操作中选对模型。
了解了生产环境中的用户流量特征后你还要了解LinkedIn采用的数据传输复制系统才行。如下图所示LinkedIn的系统有如下几个模块事件生成、数据库存储、数据复制/传输和数据的消费/读取。
<img src="https://static001.geekbang.org/resource/image/8e/6c/8e71357e44827c3c35075cebb041896c.png" alt="">
具体来讲,当用户与公司的网页互动时,相应的用户更新事件就被发送到了数据库。这些事件包括:用户点击了其他链接、阅读了其他用户的动态、向其他用户发信息等等。每个用户事件都由数据复制模块传输,并提供给下游消费者服务。
同一个用户数据,可能被很多应用程序和服务模块读取,所以这样上下游级联的系统设计,有比较好的扩展性,可以轻松应对规模的扩展。比如,如果数据复制模块不堪重负,而成为性能瓶颈,那么可以采用发散式级联的方式来扩展,从而分散读取的流量。
需要注意的是,**数据传输复制模块除了可以提供高扩展性,也可以提供数据的一致性**。像LinkedIn这样的公司服务的用户遍布全球在全球也就有很多数据中心。因为互联网流量分布在多个数据库或多个数据中心所以就需要一个整合而一致的数据视图。这样一个目的是可以通过传输复制实时数据库事件来得到的。
这个系统的另外一个特性是,**虽然用户流量可能随着时间变化很大,但是数据复制和传输的能力相对稳定**。我们通过测量和观察,发现复制传输模块的吞吐量能力非常稳定,如下图所示。
<img src="https://static001.geekbang.org/resource/image/26/57/2693bcbbf13ac5659cae8013d4d5c757.png" alt="">
那么面对不断变化的客户流量(也就是事件流量),我们要如何规划整个系统,来让稳定的数据复制能力去适应用户流量的不稳定性呢?
## 怎么解决数据库复制延迟问题?
要达成目的,我们必须做好容量规划和分析。
要减少数据库复制延迟,我们在进行容量规划的时候需要考虑几个因素,包括**用户流量**、**复制模块的吞吐量**、**复制的延迟**以及**复制延迟的SLA**Service Level Agreements服务水平协议从而确保所需的复制延迟不要超过SLA的规定。
另外,通过充分考虑用户流入的流量和复制容量,我们还可以做出一些预测,比如未来一定时间内的预期数据复制延迟。而且,大多数互联网公司的流量往往有不断增长的趋势,我们需要持续地提高复制处理能力,来应对数据流量的增加。
数据库复制的容量规划和分析,其实就是在三个基本变量中辗转腾挪。哪三个变量呢?就是**用户流量大小**、**传输复制容量的能力**和**传输复制的延迟**。给定任何两个变量,都可以确定第三个变量。
具体来说,数据库复制的容量规划和分析可以帮助回答以下几个问题:
1. 未来的流量预测
1. 数据复制延迟的预测
1. 确定数据复制的容量
1. 确定用户流量的增长空间
1. 帮助确定延迟SLA
未来的流量预测,也就是根据历史流量的数据,预期未来的流量(这个问题也能帮助回答后面的问题)。
数据复制延迟的预测也就是给定传入流量和复制处理能力预期的复制延迟是多少这些数据可以帮助我们确定复制延迟的SLA。
确定数据复制的容量就是在假设给定传入流量和最大允许的复制延迟SLA的情况下确定我们需要部署多少数据复制容量这将有助于定义复制容量需求。
确定用户流量的增长空间就是在给定复制的容量和延迟的SLA的情况下确定最大可以支持多少用户流量这有助于计划将来的容量要求。
帮助确定延迟SLA就是在给定输入流量现在或将来的复制处理能力的情况下如何确定适当的SLA显然作为一个公司或者部门我们不想过度承诺或低估SLA。
## 解决方案如何落地?
那么这具体是如何操作的呢?现在我来为你介绍一下我们采用的规划和分析模型(这是基于统计模型的)。
你要知道,数据的传输复制模块,其实是一个排队系统,是一个**有无限缓冲的先进先出队列**。
从输入角度讲,所有的用户流量数据都进入这个先进先出队列,然后传输复制模块会一个一个地处理。如果在任何时候,用户流量大小超过传输能力,那么队列就会加长。反之,队列就会变短。不过与典型的排队论问题不同,在这里,我们会更加**关注所有事件的“最长”等待时间并根据这个值来决定SLA**。
你还记得我们在最开始对用户流量特征的观察吗?基于前面对流量的观察和测量,你认为该用什么模型来预测未来数据呢?
没错,最好把用户流量看作是一个时间序列模型。并通过这个模型来预测未来的数据。
对于这类时间序列数据,通常选择**ARIMA**Autoregressive Integrated Moving Average自回归积分移动平均线模型来进行建模和预测。什么是ARIMA呢 ARIMA是一个常见的统计模型是用来对时间序列进行预测的模型。它的全称是自回归移动平均模型ARIMA, Autoregressive Integrated Moving Average Model)。
ARIMA的工作机制有较大的计算开销所以ARIMA不太适合大规模的建模比如超过几百个数据点就不太合适了。一般来讲对于一年内的预测ARIMA只能预测到每天的尺度因为一年只有365天。对这个模型来讲比天更细的粒度不太适合。但是我们这里的容量规划又偏偏需要获取更加精细粒度的数据比如每小时的预测数据而不仅仅是每天的数据。
考虑到这几个特点,对于较长期的预测,比如半年期间的话,该怎么办呢?我们提出了**两步预测模型**来获取未来的每小时流量。
简而言之,这个两步预测模型采取两步走的办法。
第一步是工作在**星期**这个粒度上获取每周的聚合流量第二步再将聚合数字“分布”或“转换”到一天内的每个小时。这样做的好处是一方面减少了数据的点数比如一年也就52周另一方面仍然可以得到每小时的数据。
你可能想问,这个第二步的转换,也就是流量从一周到小时的“转换”,是怎么做的呢?事实上,我们采用了一个**季节性指数**,该指数大致代表一周内每小时的流量部分。你也可以直观地把这个指数看作是一个分配函数。
下图就是一周到小时的季节指数展示。一周内有168小时也就是有168个数据点。这些数据点的值大小其实就是分布概率。所有的数据点的值加在一起正好等于1。
<img src="https://static001.geekbang.org/resource/image/1d/91/1ddcfe432bf88d0729dbeb9def89c591.png" alt="">
具体来说,该模型包括两个步骤:
1. 使用ARIMA模型预测未来几周的每周总体流量
1. 使用季节指数,预测一周内每个小时的流量。
采用这个模型就可以回答我们前面提出的问题了,比如可以轻松地预测未来每小时的流量。
那么具体的算法怎么实现呢?比如回答与容量规划相关的其他几种类型的问题,我们采用了**数值计算**和**二进制搜索**的类似机制。
这里的二进制搜索原理不难理解其实就是不断地尝试直到找到一个最合适的值为止。比如我们需要一个确定总的容量一方面希望容量越小越好因为省钱另一方面又不能违反传输复制延迟的SLA。
怎么用二进制搜索呢?
就是随便假定一个容量值然后带入模型去推导出传输复制的延迟然后判断这个延迟是否违反了SLA。如果违反了说明我们一开始的容量值太小应该增加一点。怎么寻找呢就是每次取中间的值重新推导。这个过程如同二进制搜索。
为了便于理解,我们假设时间粒度为每小时。一旦获得流量,则将使用数值计算方法,来判断任何时间点的复制延迟。基于数值计算的结果,也就可以使用二进制搜索,来获得特定流量所需的数据复制容量。
同样的道理,还可以用此模型,来确定任何固定场景可以支持的最大用户流量,以及相应的将来日期。也就是根据预测的增长,未来什么时候用户的流量就会超越这个最大用户流量。
最后检查每种场景下的预期复制延迟我们还可以确定适当的复制延迟SLA。
任何针对流量的预测模型,都不可避免地会出现误差。产生预测误差的主要原因是网络流量的高变化性。
当实际流量小于预测流量时实际的复制延迟也将低于预期延迟。注意这种低估误差不会违反基于预测值定义的延迟性能SLA。唯一付出的成本是部署了一些额外的资源。但是当预测的流量小于实际流量时就可能会违反延迟SLA。
为了解决此问题在确定各种指标例如SLA有必要**预留一定的空间来避免预测误差**。预留的量取决于一系列因素,包括:
1. 预测模型的历史表现,也就是预测的精准度;
1. 违反延迟SLA的后果包括业务付出的成本
1. 过度部署容量资源的成本。
好了,到这里这个规划和分析模型就讲完了,内容比较多,我们来复习一下。对于数据库复制系统来说,可以把它当作一个排队系统来对待。不断到来的输入流量,是一个有季节性的不断变化的时间序列。通过对这个时间序列来适当建模,我们可以比较准确地预测未来的输入流量。有了预测的输入流量,就可以根据服务处理的速度和复制延迟的关系,来计算出其他结果:比如预期的复制延迟,所需要的复制容量等等。
## 实际生产实践的验证
我们在LinkedIn的生产实践中采用了这个规划下面我们来看看一些结果。
我们还是使用前面介绍的42天数据来做未来的流量预测。使用ARIMA模型的实践建议是预测数据的长度最好不要超过历史数据长度的一半。所以既然我们有42天的历史数据通常可以在未来的21天之内使用ARIMA模型进行预测。
下图展示了这21天的两个数据一个是生产环境中的实际数据另外一个是根据我们模型的预测值。蓝颜色的线条是实际观察到的用户流量绿颜色的线条是我们的预测值。
<img src="https://static001.geekbang.org/resource/image/04/b3/04f88352b68339474a30f983631672b3.png" alt="">
从图中可以观察到虽然有些地方还是有误差但是从解决实际问题的角度我们认为已经足够了。我们也把ARIMA方式和其他预测方式做了比较比较预测的时间序列结果我们发现ARIMA模型给出了更好的精度但差异不大仅仅有6左右的差异。
## 总结
唐代诗人杜甫有两句诗说:“迟日江山丽,春风花草香。”说的是春天越来越长,沐浴在春光下的江山格外秀丽,春风也送来花草的芳香。数据传输复制的延迟,如果不仔细控制,也会变得越来越长,最终会导致严重问题。到时候就不是春风送暖和花草飘香,而是秋风萧瑟,老板发威了。
<img src="https://static001.geekbang.org/resource/image/c3/8a/c376d8c69669920a04ea41bd60d7128a.png" alt="">
我们今天探讨了如何通过合理的规划和分析来控制这个数据库复制延迟。具体就是使用ARIMA统计模型并且用两步走的策略来回答一系列的各种相关问题。
我想特意说明的是,这一整套时间序列的预测原理和方法,其实也可以用在很多其他的很多种预测场景中。比如一个在线广告显示系统(输入的流量是线上用户活动量,要进行的处理是决定合适的广告来显示)。
通过今天的分享,我希望你能掌握的要点是对于一个线上的数据处理系统,我们可以进行基于排队理论和时间序列的建模,通过这个建模来回答各式各样的相关问题。这个方案其实还牵涉到一些稍微复杂的统计理论,如果你还有精力,我建议你去读一下我的一篇论文,发表在[ACM ICPE](https://dl.acm.org/citation.cfm?id=2688054)上面,里面阐述了所有的细节,你在阅读的过程中有什么问题或者思考,也可以留言和我讨论。
## 思考题
如果数据的传输复制延迟过大,会造成很多种不同场合的业务影响,都会有什么样的业务影响呢?
提示:假如你们公司的网站上面帮助别人投放广告,如果广告的数据有延迟,会造成广告商和你们公司之间的什么样的纠纷呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,185 @@
<audio id="audio" title="27 | 多任务环境中的Java性能问题怎样才能不让程序互相干扰" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/82/92/8257fec023b6d2a472ef2989495f1792.mp3"></audio>
你好,我是庄振运。
我们来继续学习生产实践中的案例。在生产实践中,为了降低公司运营成本,更好地利用系统容量,并提高资源使用率,我们经常会让多个应用程序,同时运行在同一台服务器上。
但是,万事有利就有弊。这几个共存的应用程序,有可能会互相影响;有时还会导致严重的性能问题。我就遇到过,几个程序同时运行,最后导致吞吐量急剧下降的情况。
所以今天我们就来探讨当多个Java应用程序共存在一个Linux系统上的时候会产生哪些性能问题我们又该怎么解决这些问题
## 怎样理解多程序互相干扰?
为了更好地理解后面的性能问题你需要先了解一下应用程序内存管理机制的背景知识。我们运行的是Java程序所以先快速复习一下**Java的JVM内存管理机制**。
Java程序在Java虚拟机JVM中运行JVM使用的内存区域称为**堆**。JVM堆用于支持动态Java对象的分配并且分为几个区域称为“代”例如新生代和老年代。Java对象首先在新生代中分配当这些对象不再被需要时它们会被称为GCGarbage Collection的垃圾回收机制收集。发生GC时JVM会从根对象开始一个个地检查所有对象的引用计数。如果对象的引用计数降为零那就删除这个对象并回收使用这个对象相应的存储空间。
GC运行的某些阶段会导致应用程序停止响应其他请求这种行为通常称为STWStop The Word暂停。 JVM调优的重要目标之一就是最大程度地减少GC暂停的持续时间。
复习完JVM内存管理机制我们还要看一下与它相关的**Linux的内存管理机制**。
在Linux操作系统上虚拟内存空间基本上是固定大小例如4KB的页面。Linux近年来有很多内存管理的优化来提高内存使用效率和运行进程的性能。
Linux内存管理有一个**页面回收**的机制。它在内部维护一个空闲页面Free Page列表来满足未来应用程序的内存请求。当空闲页面的数量下降到一定水平时操作系统就开始回收页面并将新回收的页面添加到空闲列表中。
执行页面回收时操作系统需要进行页面扫描Page Scanning以检查已经分配页面的活动性。Linux有两个策略来进行页面扫描**后台扫描**由kswapd守护程序执行和**前台扫描**(由进程自己执行)。
通常情况下后台扫描就够了应用程序的性能一般不会受到影响。但是当操作系统的内存使用非常大空闲页面严重不足时Linux就会启动前台页面回收也被称为**直接回收或同步回收**。在前台页面回收过程中,应用程序会停止运行,因此对应用程序影响很大。
Linux还有一个**页面交换**Page Swapping的机制。是当可用内存不足时Linux会将某些内存页面换出到外部存储以回收内存空间来运行新进程。当对应于换出页面的内存空间再次处于活动状态时系统会把这些页面重新从外部存储换入内存。
内存管理方面THPTransparent Huge Pages透明大页面是另外一个机制也是为了提高进程的性能。我们在[第22讲](https://time.geekbang.org/column/article/189200)讨论过如果系统用较大的页面比如2MB而不是传统的4KB那么会带来一些好处尤其是所需的地址转换条目数会减少。
尽管使用大页面的好处很早就为人所理解但在THP引入之前程序想使用大型页面并不容易。例如操作系统启动时需要保留大页面并且进程必须显式调用才能分配大页面。而THP就是为了避免这两个问题而设计的因此操作系统默认情况下就启用THP。
了解了背景知识,你再看多个应用程序共存时的两个场景就不会有障碍了。第一个场景是应用程序启动时,第二个场景是应用程序稳定运行时。
## 应用程序启动时为什么会被其他程序干扰?
我们先看应用程序启动时的场景。当几个共存的应用程序共享有限的计算资源包括内存和cpu它们之间会相互影响。如果各自独立地运行导致对系统计算资源的消耗无法协调一致那么某些应用程序会出现问题。
我们要做个实验来暴露这些性能问题,看看这个问题的表象是什么,然后一起分析产生问题的原因。
这个实验采用了两个相同的Java程序。我们首先启动第一个程序来占用一些内存系统剩下约20GB的未使用内存。然后我们开始启动另外一个Java程序这个程序需要20GB的堆。
<img src="https://static001.geekbang.org/resource/image/25/87/250f92b847f23868da8910e7134e2687.png" alt="">
在图中你可以看到启动第二个程序之后它的吞吐量是12K/秒持续时间约30秒。然后吞吐量开始急剧下降。最坏的情况在大约20秒的时间内吞吐量几乎为零。有趣的是过了一会儿吞吐量又再次回到了稳定状态12KB/秒。
下图显示了同一时间段的GC暂停信息。
<img src="https://static001.geekbang.org/resource/image/2e/d5/2e76dec3ff4a1928dd1162da13dabed5.png" alt="">
最初的GC暂停很低都低于50毫秒然后暂停就跳到数百毫秒之大。你甚至可以看到两次大于1秒的超大的暂停大约1分钟后GC暂停再次下降至低于50毫秒并变得稳定。
我们看到在启动期间Java程序的性能很差。因为问题是在启动JVM时发生的我们有理由怀疑这与JVM的启动方式有关。我们检查了程序的的内存驻留大小RESResident Size也就是进程使用的未交换的物理内存图示如下。
<img src="https://static001.geekbang.org/resource/image/d9/7b/d96aaba8c8ef2119901a914985170d7b.png" alt="">
从图中你可以看到尽管我们在启动JVM时用参数将JVM的堆大小指定为20GB-Xmx20g和-Xms20g但是JVM并不会从内存中一次全部拿到20GB的堆空间。相反操作系统会在JVM的运行过程中不断地分配。也就是说随着JVM实例化越来越多的对象JVM会从操作系统逐渐拿到更多的内存页面来容纳它们。
在分配过程中操作系统将不断地检查空闲页面列表。如果发现可用内存量低于一定水平操作系统就会开始回收页面这个过程会花费CPU的时间。根据可用内存短缺的严重程度回收过程可能会严重阻塞应用程序。在下图中我们看到可用内存明显地下降到了非常低的水平。
<img src="https://static001.geekbang.org/resource/image/db/ec/db352ff5d0cf5ac1cb0a65e1d14d67ec.png" alt="">
下面这张图显示了CPU的空闲百分比CPU空闲百分比和繁忙百分比的和是100%。对比时间线我们可以清楚地看到页面回收过程会导致CPU开销也就是空闲百分比下降了。
<img src="https://static001.geekbang.org/resource/image/b1/12/b1daec744f3ec004c0b48260b1d06d12.png" alt="">
那么怎么进行内存回收呢?
在Linux上当可用内存不足时操作系统会唤醒**kswapd守护程序**,开始在后台回收空闲页面。如果内存压力很大,操作系统就会被迫采取另外一种措施,就是**直接地同步释放内存的前台**。具体来讲,当可用空闲页面降到一个阈值之下,就会触发这种直接前台回收。
当发生直接前台回收时Linux会冻结正在申请内存的执行代码的应用程序从而间接地导致大量的GC暂停。
此外直接回收通常会扫描大量内存页面以释放未使用的页面。那么我们就来看看Linux直接回收内存页面的繁忙程度。下图就画出了Linux通过直接回收路径扫描的页面数。
<img src="https://static001.geekbang.org/resource/image/58/95/58acbaa1b20764b08a0cc7725cfff795.png" alt="">
我们看到在峰值时通过直接回收每秒扫描约48K个页面即200 MB这个回收工作量是很大的CPU会不堪重负。
## 运行中的应用程序为什么会被别的程序干扰?
了解过程序启动时互相干扰的场景,我们再来考虑第二个场景:应用程序在持续运行中。
我们的实验是这样进行的。第一个Java程序以20GB的堆启动并进入稳定状态。然后另外一个程序启动并开始分配50GB的内存。
下图中体现了第一个程序的吞吐量。
<img src="https://static001.geekbang.org/resource/image/a9/45/a9b05fb6c7d9cec5085b86f06196f645.png" alt="">
从图中我们看到第一个程序从一开始就实现了稳定的12K/秒的吞吐量。然后吞吐量急剧下降到零这个零吞吐量的过程持续了约2分钟。从那时起吞吐量一直在发生相当大的变化有时吞吐量是12K/秒,其他时候又降为零。
我们也观察了JVM的暂停用下图所示。
<img src="https://static001.geekbang.org/resource/image/ed/5d/eded62489c1d07997998bd460d8d8e5d.png" alt="">
从图中我们看到在稳定状态下GC暂停几乎为零然后居然有一个超级大的暂停多大呢55秒从那时起GC暂停持续变化但很少恢复为零。大多数暂停时间为几秒钟。
我们观察到,其他应用程序的运行会严重影响本程序的性能。各种观察的结论是,系统处于内存压力之下,操作系统内存会有很多和外部存储的页面交换活动。在下图中,我们看到操作系统交换出了很多内存页面到外部存储空间。
<img src="https://static001.geekbang.org/resource/image/3f/57/3f9408097dcdb03ec54c5f49a71f5657.png" alt="">
这些换出的内存页面很多属于Java程序也就是堆空间。如果JVM需要进行堆上的垃圾回收也就是GC那么GC需要扫描JVM对象以收集失效的对象。如果扫描的对象恰好是分配在换出的页面上那么JVM需要先将它们从外部存储交换空间重新载入到内存中。从外部存储载入内存需要一些时间因为交换空间通常位于磁盘驱动器上。
所有这些时间都会算在GC暂停之中。因此程序会看到较大的GC暂停。下图就显示了大量的从外部存储载入页面的活动。
<img src="https://static001.geekbang.org/resource/image/e4/62/e4207f1105b7b8ef720f1eb1cdc19a62.png" alt="">
尽管页面交换活动会增加GC暂停时间似乎可以解释刚刚看到的JVM暂停。但是我怀疑仅是这个原因根本无法解释生产中看到的很大暂停比如超过55秒的暂停。你可能会问我为什么有这样的怀疑因为我在许多GC暂停的过程中观察到了较高的系统CPU使用。
比如在下图中我们观察到系统也处于严重的CPU压力下。
<img src="https://static001.geekbang.org/resource/image/f9/4c/f946efa1b98a07302d7fd2b52daf9b4c.png" alt="">
CPU的高使用率不能完全归因于页面交换活动因为页面交换通常不会占用大量CPU。所以其中“必有隐情”一定是有其他活动在大量使用CPU。我们通过检查了各种系统性能指标最终确定了根因是由于THP的机制该机制严重加剧了程序性能和系统性能的下降。
具体来说Linux启用THP后当应用程序分配内存时会优先选择2MB大小的透明大页面而不是4KB的常规页面。这一点我们可以轻易验证比如下图中显示了透明大页面的瞬时数量。在峰值时我们看到约34,000个THP即约68GB的内存量。
<img src="https://static001.geekbang.org/resource/image/0b/aa/0b948557e64c10edf711532d3d6647aa.png" alt="">
我们还观察到THP的数量一开始很高一段时间后开始下降。这是因为某些THP被拆分成小的常规页面以补充可用内存的不足。
为什么需要拆分大页面呢是因为当Linux在有内存压力时它会将THP分为常规的、要准备交换的页面。为什么必需拆分大页面这是因为当前的Linux仅支持常规大小页面的交换。
拆分活动的数量我们也用下图画出来了。你可以看到在五分钟内大约有5K个THP页面被拆分对应于10GB的内存。
<img src="https://static001.geekbang.org/resource/image/c0/3a/c07ba59fbb545d1e9cb9f11e650e1c3a.png" alt="">
除了大页面拆分同时Linux也会尝试将常规页面重新聚合为THP大页面这就需要额外的页面扫描并消耗CPU。如果你在实践中注意观察的话可以发现这种活动会占用大量CPU。
使用THP可能遇到的更糟糕的情况是**聚合**和**拆分**这两个相互矛盾的活动是来回执行的。也就是说当系统承受内存压力时THP被拆分成常规页面而不久之后常规页面则又被聚合成THP依此类推。我们已经观察到这种行为会严重损害我们生产系统中的应用程序性能。
## 如何解决多程序互相干扰?
那么程序在启动和运行时互相干扰的性能问题,到底该怎么解决呢?我们现在就来看解决方案。
我们的解决方案由三个设计元素组成,每个设计元素都针对问题的特定方面。部署任何单独的元素都将在一定程度上对问题有所帮助。但是,所有设计元素协同工作,才能获得最好的效果。
第一个设计元素是**预分配JVM的堆空间**。
我们知道对JVM而言只有在实际使用堆空间之时就是当需要增大堆空间来容纳新对象分配请求时Linux才会为之分配新的内存页面这时就可能会触发大量页面回收并损害程序和系统性能。
这个设计元素就是预分配所有堆空间从而避免了Linux实时分配页面的不利场景。要预先执行堆预分配需要使用一个特殊的JVM参数“ -XX+ AlwaysPreTouch”来启动Java应用程序。
但这个设计元素也有副作用就是增加了JVM启动所需的时间在部署时你需要考虑这一点。我们也做过一些实际测量这个额外启动时间并不大一般在几秒钟内通常是可以接受的。
第二个设计元素,是关于如何**保护JVM的堆空间不被唤出到外部存储**。
我们知道当发生GC时JVM需要扫描相应的内存页。如果这些页面被操作系统换出到外部存储则需要先换入它们到内存这就会导致延迟会增加JVM的暂停时间。
这个设计元素就可以防止JVM的堆页面被换出。 我们知道Linux操作系统上是可以关闭内存页面交换的但是这个设置如果是在系统级别进行就会影响所有应用程序和所有内存空间。我推荐你一个更好的实现就是采用**微调**你来选择哪个应用程序和哪个存储区域可以页面交换。例如你可以使用cgroup来精确控制要交换的应用程序。
公司中的大多数平台一般都用来运行同类Java应用程序这些程序往往配置差不多。在这些情况下在系统级别关闭应用程序交换倒也是非常合理的。
第三个设计元素是**动态调整THP**。
我们已经看到启用THP功能可能会在某些场景下导致严重的性能损失但是THP在其他场景的确提高了性能所以到底是否要启用THP呢我们需要仔细考虑。
当THP影响性能时系统的可用内存往往也恰好严重不足。发生这种情况时现有的THP需要拆分成常规页面以进行页面换出。所以我建议你用一个可用内存大小的阈值来决定THP的开关。
具体来说,就是建议你使用**应用程序的堆大小**作为内存阈值来决定是否打开或关闭THP。当可用内存远远大于应用程序的内存可能占用量大小时就启用THP因为系统不太可能在启动特定应用程序后出现内存压力。否则的话就关闭THP。
由于许多后端服务器都是运行同类应用程序,通常情况下,你都很容易知道,部署的应用程序预期会占用多少内存空间。
此外常规页面需要聚合成THP才能将大页面分配给应用程序。因此这个元素的另外一部分机制是进行微调是决定何时允许THP聚合。我建议你根据**操作系统的直接页面扫描率**和**聚合进程的CPU使用率**来决定。
## 总结
今天我们讲述了,将多个应用程序放置在同一台服务器上时,由于应用程序和操作系统机制的互相作用,引发的一系列性能问题。这些问题的根本原因,就是程序之间的互相影响。
<img src="https://static001.geekbang.org/resource/image/df/ef/dfe49c970e5a67b8f6a3993b9fc39aef.png" alt="">
应用程序之间的关系和人际关系一样,有时和谐,有时不和谐。唐代诗人刘禹锡有几句诗说:“常恨言语浅,不如人意深。今朝两相视,脉脉万重心。”说的是,语言的表达能力通常很有限,所以两人只能用眼神传达更复杂的情感。应用程序之间的关系,甚至程序和操作系统及硬件之间的关系,也会很复杂,也需要做足够的性能分析,才能理清它们之间的关系。
今天的讲述,主要集中在多任务共存环境中的两个问题,重点在分析问题产生的复杂根因。 如果你对这方面的具体算法和生产验证有兴趣,可以参考我的一篇论文。这篇论文发表在[International Journal of Cloud Computing](https://www.researchgate.net/publication/282348773)上面。
## 思考题
Linux操作系统的THP机制的设计初衷本是为了提升系统性能。可是在有些情况下反而导致了系统性能下降。想一想操作系统的其他机制有没有类似的情况发生
>
Tips文件系统的预先读取等。
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,148 @@
<audio id="audio" title="28 | 网络数据传输慢,问题到底出在哪了?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7c/e6/7cba2646633fd34d98e8c6d87c0fb3e6.mp3"></audio>
你好,我是庄振运。
你一定有过在网页或者手机上下载照片的体验,如果数据传输太慢,那你的体验一定十分糟糕。你看,互联网实体之间的数据快速传输对用户体验至关重要。这里涉及到的其实就是网络传输问题。所以,今天我们就通过生产实践中的案例,来探讨一下互联网服务中的数据传输性能。
说到底,网络传输问题其实就分两种:
1. 数据根本没有传递;
1. 数据传送速度较慢。
“数据没有传递”虽然看起来更严重,但是相对“数据传送缓慢”来说,更容易判断和解决。所以,这一讲,我们就重点解决第二种问题。我们一起来看看,为什么网络传送速度会慢,在众多原因中怎么快速诊断出关键问题来,又该如何去解决。
造成网络传输缓慢的原因很多,我们这一讲,就是帮助你快速诊断问题出在哪里:是客户端,是服务器端,还是网络本身?在此基础上,你才能专门针对具体的领域继续分析。
## 为什么数据传输慢?
我们先看一下,都有哪些可能的原因会导致数据传输缓慢呢?在宏观上,这种问题的可能原因可以分为三种场景:
1. 客户端应用程序的原因;
1. 网络的原因;
1. 服务器应用程序的原因。
也就是说,可能是由于数据发送方过载,而没有向接收方发送数据;也可能是网络通道很慢;又或者是数据接收方的服务器太忙,从而无法从网络缓冲区读取数据。
为了描述方便,我们根据平时客户浏览网页的场景,假设客户端是数据接收方,而服务器端是数据发送方。
进行此类分析诊断时,负责的工程师通常需要快速隔离出上述不同场景,以便他们可以专注于特定场景里面的可疑组件,并对本质原因进行更深入的分析。但这一快速诊断过程会遇到很多难点。
首先,**数据传输涉及多个网络实体**,包括两台机器(也就是发送者和接收者)和网络路由,这与仅涉及一台机器的常见性能问题形成鲜明对比。
其次,这种**诊断涉及多层信息**包括应用程序层和网络传输层。为了找出原因工程师必须检查各种数据包括客户端日志、服务器日志、网络统计信息、CPU使用情况等。这些检查需要花费很多时间和精力并且通常需要性能工程师的经验和专业知识。
更加让人郁闷的是,这些日志往往分散在不同的地方,比如客户端和服务器。为了节省时间和精力,性能工程师迫切需要更智能的工具,以帮助他们快速找出根本原因。
所以今天,我专注于解决这样的一个问题,就是:快速确定应归咎的组件范围和场景(无论是发送方、接收方还是网络本身)。我提出了一种**当发生数据传输缓慢的问题时,可以自动隔离原因的解决方案**。毕竟,你只有找出了要对“数据传输慢”负责任的那一部分,才可以进行后续分析工作,最终确定真正的问题。
## 如何判断问题所在位置?
要想快速诊断,我们需要先看看三种问题场景的不同特征。
这个解决方案本质上依靠的是**客户端和服务器端的TCP层面的特征**。TCP是传输层协议之一可提供有序且可靠的流字节传输是当今使用最广泛的传输协议。TCP具有**流控制**功能,可避免接收方过载。接收方设置专用的接收缓冲区,发送方设置相应的发送缓冲区。数据发送方(服务器)的发送缓冲区和数据接收方(客户端)的接收缓冲区,都可以通过操作系统来监测当前队列大小。
为了能够识别瓶颈你需要在发送方和接收方的传输层上收集有关队列大小的信息。有很多收集此类信息的方法。你有两种工具可以使用分别是Netstat和ss。
Netstat是一个命令行工具可以显示网络连接和网络协议统计信息。我们主要是用它来观察TCP / IP套接字的发送队列和接收队列的大小。而ss命令可以显示套接字统计信息包括显示TCP以及其他类型套接字的统计信息。类似于Netstatss还可以显示发送和接收队列大小。
除了相应的工具的介绍为了帮助你理解我们还需要先重温一下传输数据时候应用层和TCP层的交互。
<img src="https://static001.geekbang.org/resource/image/db/89/dbe011441395739fbef5869848224789.png" alt="">
上图显示了任何基于TCP的数据传输中的典型流程。关于系统调用和网络传输的五个步骤如下
1. 在步骤A服务器应用程序发出write()系统调用,并将应用程序数据复制到套接字发送缓冲区。
1. 在步骤B服务器的TCP层发出send()调用并将一些数据发送到网络数据量受TCP的拥塞控制和流控制。
1. 在步骤C网络将数据逐跳路由到接收方IP路由协议在这部分中发挥作用
1. 在步骤D客户端的TCP层将通过recv()系统调用接收数据,数据放入接收缓冲区。
1. 在步骤E客户端应用程序发出read()调用,以接收数据并将其复制到用户空间。
接下来,我们就来看看三种不同场景下的问题特征是什么样的。
我们先看第一个场景,**客户端接收数据缓慢**的情况。为了重现这一场景我们做一个实验让发送端发送一段固定大小的数据给接收方。我们强制接收方也就是客户端减慢数据的接收速度。具体做法就是在应用程序代码的read()调用之前,注入了一定的延迟,这种场景代表了客户端数据接收成为瓶颈的情况。
<img src="https://static001.geekbang.org/resource/image/1e/d8/1e28e9d5065f653f0cdc5ee1ddd639d8.png" alt="">
上图显示的是数据发送方的发送缓冲区SendQSend Queue的大小变化。开始时候数据发送调用send()立刻注满SendQ。随着数据的传输慢慢变为0。
<img src="https://static001.geekbang.org/resource/image/07/13/0757f0a16dbf81ebed5c9f092c260913.png" alt="">
第二张图是客户端的接收缓冲区RecvQReceive Queue的大小变化客户端因为应用程序运行缓慢所以RecvQ具有一定的积累这可以由非零值来看出。这些非零值持续了一段时间随着应用程序不断地读取最终RecvQ减为0。
对于第二种场景,也就是**数据发送方是瓶颈**的情况我们强制发送方即服务器端放慢数据的发送速度。具体来说我们在应用程序代码中对write()的调用之前注入了一定的延迟,模拟了发送者是瓶颈的情况。
<img src="https://static001.geekbang.org/resource/image/8d/1a/8dc79b587e12571b62b7725256bdb11a.png" alt="">
上图显示了服务器端的SendQ的值你可以看到SendQ几乎全部是零。这是因为发送端是瓶颈其他地方不是瓶颈所以任何SendQ的数据会被很快发送出去。
你可以在图片中看到一个持续时间很短的峰值这是因为SendQ取样的时候恰好取到数据还没有被传输到网络中的时候。但因为这个峰值持续时间很短简单的过滤就可以去掉。
接收端的RecvQ显示在下图你可以看到因为接收端不是瓶颈RecvQ是零。
<img src="https://static001.geekbang.org/resource/image/d8/3c/d8283edde9ac0494f4f80029b2436a3c.png" alt="">
第三个场景,是**网络本身是瓶颈**造成的数据传输缓慢。我们通过向网络路径注入延迟来创造这一场景以使TCP仅能以非常低的吞吐量进行传输。
<img src="https://static001.geekbang.org/resource/image/6d/1d/6d0ed6a9c6cf1e51085e366a7a84061d.png" alt="">
图片中显示了发送端的SendQ值你可以看到它的值不为零因为那些数据不能很快地被传送出去。
再来看接收端的RecvQ如下图。RecvQ全为零这些零值就代表了快速的数据传递。
<img src="https://static001.geekbang.org/resource/image/36/05/36e98d68b61e115e6b5a5ede39dc6205.png" alt="">
通过上面三种场景的分析尤其是对发送端SendQ和接收端RecvQ的观察我们不难总结出规律来。正常的数据传输情况下客户端的接收队列和服务器端的发送队列都应该是零。
反之,如果数据传输缓慢,则有如下几种情况:
1. 如果客户端上的接收队列RecvQ不为零则客户端应用程序是性能瓶颈
1. 如果服务器上的发送队列SendQ为零则服务器应用程序是性能瓶颈
1. 如果客户端的接收队列RecvQ为零而服务器的发送队列SendQ为非零则网络本身是性能瓶颈。
为了帮助你加深记忆,我用表格来做了个归纳。
<img src="https://static001.geekbang.org/resource/image/31/f3/312fe519d3db2f20f9b44c547f0e00f3.png" alt="">
你可以通过这个表格,快速判断问题出现的位置。
## 解决方案如何落地?
根据前面的分析和总结,我们现在提出解决方案。这是一个基于**状态转移**的方案,需要从客户端和服务器端收集几个关键点的信息。
为了帮助你理解我们需要先来看看数据请求和传输流程图。就用常见的HTTP协议的Request和Response方式来描述如下图所示。
<img src="https://static001.geekbang.org/resource/image/b0/24/b0b9e458e45019a7cdae9bf6b9b2da24.png" alt="">
当客户端需要下载服务器的数据时首先在T0发出数据请求网络将请求发送到服务器后服务器在T1收到数据。
然后服务器开始准备数据数据准备好后服务器将开始在T2时发回数据。通过一系列write()调用。发送在T4完成。网络传输后客户端在T3开始接收数据并在T5完成接收。请注意尽管其他时间戳是按照严格的顺序T3和T4的顺序可能会因实际情况而异。具体来说对于小数据传输时T4可以先于T3因为单个write()调用就足够了。对于大数据传输通常使用T3在T4之前。
接下来我们来看看基于状态机的解决方案它是一个针对HTTP数据传输问题的完整而具体的解决方案。
从上面的过程中,我们可以看到,如果服务器无法接收到数据请求,则数据传递将不会发生,因此不会完成。
我用下图来表示整个状态机。这个状态机展示了整个HTTP数据传输的过程包括Request和Response。如果数据传递成功状态机最后会到达状态F。
<img src="https://static001.geekbang.org/resource/image/46/ca/466e92b6401de7e5c476812c800b55ca.png" alt="">
如上图数据传递的初始状态为State-S。客户端发出请求后它将移至状态A当网络通道完成其工作并将请求传递到服务器OS时状态变为B。当服务器准备好数据并开始发出数据的第一个字节时状态变为C。
当客户端收到第一个字节后状态变为D最后当服务器发出最后一个字节时状态变为E。或者这两个次序交换成功进行数据传递的最终状态是State-F。
在整个过程中,如果发生其他的转移,那么就是网络传输有问题了。我们就可以根据发送端的发送队列和接收端的接收队列长度的变化,轻松判断是谁的问题,比如是客户端,服务器或是网路。
## 总结
今天我们讲述了,互联网服务在传输数据时,如果发生传输速度太慢的问题,怎样才能快速地诊断到底是客户端、服务器端,还是网络的问题。
<img src="https://static001.geekbang.org/resource/image/4a/9d/4a785ec504c96d6bfbc079f61fe9539d.png" alt="">
唐代诗人高适的《燕歌行》有几句诗:“山川萧条极边土,胡骑凭陵杂风雨。战士军前半死生,美人帐下犹歌舞。”说的是前方的战士,在前线出生入死;后方却有人逍遥自在的观赏美人歌舞,醉生梦死。这种冰火两重天的讽刺,部分原因,就是责任没有分清,以至于滥竽充数者可以逍遥自在。
对待数据传输缓慢问题我们也很希望能快速地搞清责任分清是哪里的问题然后才能有针对性地继续分析。我们的解决方案就是根据TCP的Send和Receive队列大小变化来快速诊断的方案。它能智能而快速地分清问题的大致范围就是数据发送方、数据接收方还是网络。
## 思考题
根据你平时的观察,公司业务有没有发生数据传输太慢的问题?如果发生了,你们一般怎么根因呢?如果采用本讲的思路,会不会更加快速地诊断?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,194 @@
<audio id="audio" title="29 | 如何彻底发挥SSD的潜力" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/20/07/20933d0dfc588089273516d23593fb07.mp3"></audio>
你好,我是庄振运。
今天是“性能工程实践”这个模块的最后一讲我们来讨论一种“软硬件结合”的性能工程优化实践与SSD硬件有关。现在SSD用的越来越普遍的情况你一定非常清楚但是你设计的应用程序软件真的充分利用了SSD的特点并发挥SSD的潜力了吗
要知道SSD可不仅仅是“更快的HDD”。
SSD的好处显而易见它作为存储时应用程序可以获得更好的I/O性能。但是这些收益主要归因于SSD提供的更高的IOPS和带宽。如果你因此只将SSD视为一种“更快的HDD”那就真是浪费了SSD的潜力。
如果你在设计软件时能够充分考虑SSD的工作特点把应用程序和文件系统设计为“对SSD友好”会使服务性能有个质的飞跃。
今天我们就来看看,**如何在软件层进行一系列SSD友好的设计更改**。
## 为什么要设计SSD友好的软件
设计对SSD友好的软件有什么好处呢简单来说你可以获得三种好处
1. 提升应用程序等软件的性能;
1. 提高SSD的 I/O效率
1. 延长SSD的寿命。
先看第一种好处——**更好的应用程序性能**。在不更改应用程序设计的情况下简单地采用SSD可以获得性能提升但无法获得最佳性能。
我为你举个例子来说明。我们曾经有一个应用程序它需要不断写入文件以保存数据主要性能瓶颈就是硬盘I/O。使用HDD时最大应用程序吞吐量为142个查询/秒QPS。无论我们对应用程序设计进行什么样的更改或调优这就是使用HDD可以获得的最好性能了。
而当迁移到具有相同应用程序的SSD时吞吐量提高到了20,000 QPS速度提高了140倍。这种提高主要来自SSD提供的更高的IOPS。你是不是觉得与HDD相比应用程序吞吐量有了显着提高性能已经很好了
但这并不是SSD能实现的最佳性能。
我们对应用程序设计进行了优化使其对SSD友好之后应用程序吞吐量提高到100,000QPS与简单设计相比提高了4倍。
你可能会问,这是如何做到的?
这其中的秘密,就是**使用多个并发线程来执行I/O**。如下图所示,这利用了**SSD的内部并行性**。
<img src="https://static001.geekbang.org/resource/image/d2/a0/d2b82a4a2b47c03aea570f183c49b8a0.png" alt="">
这里你需要注意的是在这个系统中多个I/O线程对HDD是毫无益处的。因为HDD只有一个磁头所以用多个I/O线程并不能提高旧系统的吞吐量。
第二种好处是**更高效的存储I/O**。
我在[第17讲](https://time.geekbang.org/column/article/185154)中提到过SSD上的最小内部I/O单元是一页比如4KB大小。因此对SSD的单字节读/写必须在页面级进行。应用程序对SSD的写操作可能会导致对SSD上的物理写操作变大这就是“写入放大参见[第23讲](https://time.geekbang.org/column/article/190151))”。
因为有这个特性如果应用程序的数据结构或I/O对SSD不友好就会让写放大效果更大导致SSD的I/O不能被充分利用。
设计SSD友好软件的最后一个好处是**延长SSD的寿命**。
SSD会磨损是因为每个存储单元只能维持有限数量的写入擦除周期。实际上SSD的寿命取决于四个因素
1. SSD大小
1. 最大擦除周期数
1. 写入放大系数
1. 应用程序写入速率
例如假设有一个1TB大小的SSD一个写入速度为100MB/秒的应用程序和一个擦除周期数为10,000的SSD。当写入放大倍数为4时SSD仅可持续10个月。具有3,000个擦除周期和写入放大系数为10的SSD只能使用一个月。
你也知道相对于HDD而言SSD的成本比较高我们自然希望自己设计的应用程序对SSD友好从而延长SSD的使用寿命。
## 怎么设计对SSD友好的软件
说了这么多设计SSD友好软件的好处那我们具体该从哪里入手呢
其实在软件领域有很多地方都可以做对SSD友好的设计比如文件系统、数据库系统、数据基础架构层和应用程序。接下来我会一一为你介绍。尤其是应用程序这个领域它是这一讲的重点。
第一个可以对SSD友好的领域是**文件系统**。文件系统层直接处理存储因此我们需要在此级别进行优化设计更改来更有效地发挥SSD的特长。一般而言这些设计更改集中在**SSD和HDD迥异的三个关键差异特征**上:
- SSD的随机访问与顺序访问具有相同的性能
- 需要在块级别进行擦除后才能重写;
- 内部损耗均衡的机制会导致写入放大。
现在比较流行的有两种对SSD友好的文件系统。第一种是适用于SSD的通用文件系统主要支持Trim的新功能比如Ext4和Btrfs。第二种是专门为SSD设计的文件系统。基本思想是采用日志结构的数据布局相对于B树或Htree来容纳SSD的“复制-修改-写入”属性比如NVFS非易失性文件系统、FFS / JFFS2和F2FS。
除了文件系统,**数据库系统**也可以设计成对SSD友好。
SSD和HDD之间的差异在数据库设计中尤其重要。近几十年来数据库的各个组件比如查询处理、查询优化和查询评估等都已经在考虑HDD特性的情况下进行了诸多优化。
举个例子因为普通硬盘的随机访问比顺序访问要慢得多所以数据库组件会尽量减少随机访问。而使用SSD时类似这样的假设就不成立了。
因此业界设计了新的、对SSD友好的数据库。对SSD友好的数据库主要有两种第一种是专门针对SSD的数据库例如AreoSpike这种数据库主要采用对SSD友好的Join算法第二种是HDD和SSD混合的数据库一般是使用SSD来缓存数据。
第三个领域是**数据基础架构层**。
对于分布式数据系统的设计而言,数据来源大体上有两个地方:计算机上的本地磁盘或另一台计算机上的内存。这两个来源哪个更快更高效呢?还真不一定。随着技术的演化,业界也一直在争论。
过去很多年这样的争论比较倾向于远程计算机的内存因为速度更快Memcached就是一个例子。传统的本地HDD访问延迟大约是好几个毫秒而远程内存访问的延迟包括了RAM访问延迟和网络传输延迟也仅处于微秒级。同时远程内存的I/O带宽与本地HDD大致相同甚至更多。因此远端的内存反而比本地的硬盘的访问时间更短。
而SSD的出现正在改变这个趋势和业界的决定。使用SSD作为存储设备后本地SSD变得比远程内存访问更为高效。
首先SSD的I/O延迟降低到了微秒级而I/O带宽可是比HDD高一个数量级。这些结果就导致了数据基础架构层的新设计。比如新设计更希望尽可能与应用程序**共同分配数据**,以避免额外的节点和网络的限制,从而降低系统的复杂性和成本。
这么说可能你的感受不太明显我来用Netflix为你举例说明一下。Netflix就是采用这种设计的公司之一。Netflix公司曾经采用memcached来缓存Cassandra层数据。假设Netflix需要缓存10TB的数据如果每个内存缓存节点在RAM中保存100GB数据则需要部署100个内存缓存节点。后来Netflix只使用10个Cassandra节点并为每个Cassandra节点配备1TB SSD来完全取代memcached层。
你看采用这种设计就不需要100个Memcached节点只需10台配备SSD的节点节省了大量成本。
在**应用程序层**我们也可以对SSD进行友好的设计以获得前面提到的三种好处更好的应用程序性能、更高效的I/O和更长的SSD寿命
在这一领域我总结了七大SSD友好的设计原则大体上分为三类数据结构、I/O处理和线程使用。
<img src="https://static001.geekbang.org/resource/image/a0/2d/a087788f0d74a2863366ca456f8c362d.png" alt="">
### 1.数据结构:避免就地更新优化
传统的HDD的寻址延迟很大因此使用HDD的应用程序通常会进行各种优化以执行不需要寻址的就地更新比如只在一个文件后面写入。
比如下图所示在执行随机更新时吞吐量一般只能达到约170 QPS而对于同一个HDD就地更新可以达到280QPS远高于随机更新。
<img src="https://static001.geekbang.org/resource/image/ff/3b/fff29bfb9e14f2ff93e0637e8636333b.png" alt="">
不过在设计与SSD配合使用的应用程序时这些考虑就没什么意义了。对SSD而言随机读写和顺序读写性能类似就地更新不会获得任何IOPS优势。
此外就地更新实际上是会导致SSD性能下降的。包含数据的SSD页面无法直接重写因此在更新存储的数据时必须先将相应的SSD页面读入SSD缓冲区然后将数据写入干净的页面。 SSD中的“读取-修改-写入”过程与HDD上的直接“仅写入”行为形成了鲜明的对比。
相比之下SSD上的随机更新就不会引起读取和修改步骤即仅仅“写入”因此速度更快。
使用SSD以上相同的应用程序可以通过**随机更新**或**就地更新**来达到大约2万QPS而且随机更新和就地更新的吞吐量大体相似随机更新其实还稍微好些。如下图所示。
<img src="https://static001.geekbang.org/resource/image/7a/0b/7aa33a3abeef4b908f7560b23db6c20b.png" alt="">
### 2.数据结构:区分热、冷数据
几乎所有处理存储的应用程序,磁盘上存储的数据的访问概率均不相同。
用一个需要跟踪活动用户活动的社交网络应用程序来给你举个例子。对于用户数据存储简单的解决方案是基于用户属性例如注册时间将所有用户压缩在同一位置例如某个SSD上的文件。以后需要更新热门用户的活动时SSD需要在页面级别进行访问即读取/修改/写入)。
因此如果用户的数据大小是小于一页那么每次读取这个用户的数据附近的用户数据也将一起被访问。如果应用程序其实并不需要附近用户的数据那么额外的数据访问不仅会浪费I/O带宽而且会不必要地磨损SSD。
为了缓解这种性能问题在将SSD用作存储设备时应将热数据与冷数据分开。以不同级别或不同方式来进行分隔。例如把它们存在不同的文件、文件的不同部分或不同的表格里。
### 3.数据结构:采用紧凑的数据结构
在SSD的世界中最小的更新单位是页面4KB因此即使是一个字节的更新也将导致至少4KB SSD写入。由于写入放大的效果实际写入SSD的字节可能远大于4KB。读取操作也是类似因为OS具有预读机制会预先主动地读入文件数据以期改善读取文件时的缓存命中率。
所以当将数据保留在SSD上时你最好使用紧凑的数据结构避免分散的更新以获得更快的应用程序性能、更有效的存储I/O以及节省SSD的寿命。
### 4. I/O处理避免长而繁重的持续写入
SSD通常具有GC机制不断地回收存储块以供以后使用。GC可以用后台或前台的方式工作。 SSD控制器通常保持一个空闲块的阈值。每当可用块数下降到阈值以下时后台GC就会启动。由于后台GC是异步发生的即非阻塞因此它不会影响应用程序的I/O延迟。但是如果块的请求速率超过了GC速率并且后台GC无法跟上则将触发前台GC。
在前台GC期间必须即时擦除即阻塞每个块以供应用程序使用这时发出写操作的应用程序所经历的**写延迟**就会受到影响。具体来说释放块的前台GC操作可能会花费数毫秒以上的时间从而导致较大的应用程序I/O延迟。
因此你最好避免进行长时间的大量写入操作这样就可能永远不触发前台GC。
### 5. I/O处理避免SSD存储太满
SSD磁盘存储的满存程度会影响写入放大系数和GC导致的写入性能。在GC期间需要擦除块以创建空闲块。擦除块前需要移动并保留有效数据才能获得空闲块。有时为了获得一个空闲块需要压缩好几个存储块。而每个空闲块的生产需要压缩的块数取决于磁盘的空间使用率。
假设磁盘满百分比平均为A要释放一个块则需要压缩1 /1-A块。显然SSD的空间使用率越高将需要移动更多的块以释放一个块这将占用更多的资源并导致更长的I/O等待时间。
例如如果A=80则大约移动五个数据块以释放一个块。当A=95将移动约20个块。
### 6.线程使用多个线程执行小的I/O
SSD内部大量使用了并行的设计这种并行表现在多个层面上。一个I/O线程无法充分利用这些并行性会导致访问时间更长。而使用多个线程就可以充分利用SSD内部的并行性了。
SSD可以有效地在可用通道之间分配读写操作从而提供高水平的内部I/O并发性。例如我们使用一个应用程序执行10KB写入I/O10KB算是比较小的IO大小。使用一个I/O线程它可以达到115MB/秒。使用两个线程基本上使吞吐量加倍使用四个线程再次将其加倍使用八个线程可达到约500MB/秒,如下图所示。
<img src="https://static001.geekbang.org/resource/image/89/76/8984620c393a877fb47ecab219a41076.png" alt="">
你可能会很自然地问出一个问题这里的“小”IO到底有多小
答案是只要是不能充分利用内部并行性的任何I/O大小都被视为“小”。例如SSD页面大小为4KB内部并行度为16则阈值应约为64KB。
### 7.线程使用较少的线程来执行大I/O
第七个原则同样关于线程,它与第六条原则相对应(但并不矛盾)。
对于大型I/OSSD内部已经充分优化使用了SSD的内部并行性因此应使用更少的线程即小于四个以实现最大的I/O吞吐量。从吞吐量的角度来看用太多线程不会有太大益处。更重要的是使用太多线程可能导致线程之间的资源竞争以及诸如OS级的预读和回写之类的后台活动。
例如根据我们的实验当写入大小为10MB时一个线程可以达到414MB/秒两个线程可以达到816MB/秒而四个线程达到912MB/秒八个线程实际上只有520MB/秒。如下图所示。
<img src="https://static001.geekbang.org/resource/image/98/99/98feb65d9b624e057baeb97f0af6c599.png" alt="">
## 总结
这一讲我们讨论了软硬件结合的优化。
与使用HDD的应用程序相比使用SSD的应用程序通常具有更好的性能水平。但是如果不更改应用程序设计则应用程序是无法获得最佳性能的。因为SSD的工作方式不同于HDD。
为了发挥SSD的全部性能潜能应用程序设计必须对SSD友好。
<img src="https://static001.geekbang.org/resource/image/48/d0/485e47b6f1103ba4304e436d066f62d0.png" alt="">
唐代的王维有一首《少年行》,讲几个好哥们一起喝酒:“新丰美酒斗十千,咸阳游侠多少年。相逢意气为君饮,系马高楼垂柳边。” 说的是性情投机的好朋友一起互相帮助,就会互相促进。
硬件和软件也是如此,如果互相对对方友好,互相都受益,总体性能也就更高。
我们的软件系统如果能充分考虑硬件比如SSD的特性做出的设计就会获得更好的性能和稳定性。基于这一点我在这一讲里面提出了七个对SSD友好的软件设计原则分为三类数据结构、I/O处理和线程使用。你在设计使用SSD存储的软件时可以参考采用。
## 思考题
你正在开发、维护、使用的系统有没有使用SSD作为存储的如果有这个系统有没有考虑到SSD的特殊机制和寿命问题
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,153 @@
<audio id="audio" title="09 | 性能测试的种类:如何快准狠地抓住一个测试的本质?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/12/6e/127dadddc70d5cba60e68e074258e16e.mp3"></audio>
你好,我是庄振运。
从这一讲开始我们讨论性能测试。性能测试是一种特殊的软件测试,它的目的是**确保软件应用程序在一定的负载流量下运行良好**。性能测试是性能分析和性能优化的基础,它的目标是**发现和性能相关的各种问题和性能瓶颈,从而进一步去消除错误和性能瓶颈**。
由于性能测试本身就有好多种类;加上各种测试之间的界限其实很模糊,这就造成了很多人理解上的混乱。
比如大家在工作讨论时,经常说做性能测试,但对于做什么样的“性能测试”,每个人有不同的看法,而且又经常表达不清。这就造成来交流不畅,甚至是误解,从而严重地影响了工作的速度。我见过很多次因为对性能测试定义和交代不清,造成了老板和员工之间/员工和员工之间的理解误差。
性能测试的种类颇多,各自有不同的测试目的、测试环境、负载等等;这里面最重要的是测试目的和负载的大小变化。我们这一讲就一起来分一下类。
## 性能测试的分类方式
性能测试如何分类呢?我们需要从几个方面来看,包括测试目的、测试环境、负载流量、测试对象、负载数据、黑盒白盒等。
### 测试目的
测试目的是最重要的方面。大体上有几种目的:
1. 测量服务速度Speed确定程序是否能够快速地响应用户的请求这个服务速度一般包括延迟和吞吐率两个指标。速度通常是应用程序最重要的属性之一因为运行缓慢的应用程序容易丢失用户。
1. 测量可扩展性Scalability确定应用程序是否可以在用户负载和客户流量增大情况下还能正常地运行。
1. 测量稳定性Stability确定在各种极端和恶劣环境下应用程序是否能稳定运行。
1. 测量性能瓶颈Performance Bottleneck性能瓶颈是应用程序和系统中的最影响整体性能的因素。瓶颈是指某个资源不足而导致某些负载下的性能降低。一些常见的性能瓶颈是CPU、内存、网络、存储等。
### 测试环境
性能测试的环境也有几种,主要是开发环境还是生产环境。开发环境里面更多的是简单的测试来发现一些明显问题,而生产环境测试一般是开发环境测试通过后才进行的。
### 负载流量
根据测试的负载大小来分:是小流量,正常流量,还是超大流量。除了大小,负载变化的速度也需要考虑。
### 测试对象
测试的对象可以是只针对一个代码功能,或是整个代码模块,亦或是整个系统。
### 负载数据
测试的负载数据可以是真正的生产环境中的请求和数据,比如终端客户的网站请求和上传数据,也可以是人工模拟出来的请求及数据。
### 黑盒白盒
如果把被测试的对象当作一个整体,不关心它的内部工作机理,也就是把它当作一个黑盒子,那么这种测试就是黑盒测试。反之,如果你也关心它的内部构件和内部设计,就是把它当作白盒子来测试。
## 性能测试的种类
我们现在看看你可能经常会提到的各种测试包括负载测试、容量测试、压力测试、断点测试、瓶颈测试、尖峰测试、耐力测试、基准测试、可扩展性测试和冒烟测试这10种。我分别说说它们是什么特点尽量用刚刚讲过的分类方式归归类并且适当举例说明。
需要说明的是,业界对于不同的测试类别其实也是各说各道,没有特别统一而严格的定义。我也是尽我所能,根据我的经验和实践来帮你理一理。
因为性能测试的种类多,我尽量把它们按照某种方式归类一下,帮助你理解和记忆。大体上可以按照负载流量的大小分成三类:低流量、中等流量和高流量。这里的流量高低是相对于生产环境中的流量而言的。当然,它们的实际界限其实很模糊。
<img src="https://static001.geekbang.org/resource/image/de/0c/dea330b04f4c81c71ab727b579e23b0c.png" alt="">
### 冒烟测试Smoke Testing
冒烟测试是开发人员在开发环境里执行的简单测试,以确定新的程序代码不出故障。冒烟测试目的是确认系统和程序基本功能正常。冒烟测试的执行者往往就是开发人员,但有时也让运维人员参与。
### 耐力测试Endurance Testing和浸泡测试Soak Testing
耐力测试或者耐久测试有时也叫浸泡测试是一种非功能性测试。耐力测试是长时间测试具有预期负载量的系统以验证系统的行为是否正常。举一个例子假设系统设计工作时间是3小时我们可以对这一系统进行超过3小时的测试比如持续6小时的耐力测试以检查系统的耐久性。
执行耐力测试最常见的用例是暴露某些不易重现的问题,如内存问题、系统故障或其他随机问题。
这里的偏重点是**测试时间**,因为有些程序和系统的问题只有在长期运行后才暴露出来。一个最明显的例子就是内存泄漏。一个程序或许短时间内运行正常,但是如果有内存泄漏,只要运行时间足够,就一定会暴露出这个问题。
### 基准测试Benchmark Testing/性能回归测试Performance Regression Testing
基准测试或者性能回归测试是着重“前后”对比的测试。
这种测试往往是开发过程的一部分,一般不需要具体的性能要求。代码的演化过程中经常需要确保新的代码不会对整个模块或系统的性能产生任何不好的影响。最简单的方法是对代码修改前后进行基准测试,并比较前后的性能结果。执行基准测试的重点是**保证前后测试环境的一致**,比如负载流量的特征和大小。
### 负载测试Load Testing
负载测试用于验证被测试系统或者程序是否可以处理预期的负载流量,并验证正常和峰值负载条件下的系统和程序行为。这里的负载可以是真正的客户请求,也可以是仿真的人工产生的负载。
我认为负载测试的定义有时太广泛和模糊,很多其他测试都可以看作是负载测试的一种,比如马上就要讲到的容量测试,其实就是一种负载测试。
### 断点测试Breakpoint Testing
断点测试类似于压力测试或者容量测试。这种测试的过程是随着时间的推移而增大流量负载,同时监视系统的预定故障条件。
断点测试也可以用来确定系统将达到其所需规范或服务水平协议的最大容量,并且自动采取措施来纠正或者缓解。比如云计算环境中,我们可以设置某种性能断点,用它们来驱动某种扩展和伸缩策略。
比如一种性能断点可以是根据用户的访问延迟。如果延迟性能测量的结果是已经超过预定的阈值,就自动进行系统容量调整,比如增加云计算的服务器。反之,系统容量也可以根据断点的规则来减少,以节省成本。如下图所示。
<img src="https://static001.geekbang.org/resource/image/01/d6/019067e2d02744e7ad7d4ff754523fd6.png" alt="">
### 尖峰测试Spike Testing
尖峰测试用于确定系统在负载(比如用户请求数)突然变化时的系统行为。这种测试是通过突然增加或减少由用户产生的负载来观察系统的行为。
测试的目标是确定性能在这样的场景下是否会受损,系统是否会失败,或者是否能够处理负载的显著变化。尖峰测试的核心是**负载变化的突然性**,所以也算是一种压力测试。
### 可扩展性测试Scalability Testing
可扩展性(或者叫可伸缩性)测试用于确定一个程序和系统的非功能性特征能不能在变化的环境里合理扩展。这里的环境变化包括系统环境的变化、负载量的大小、请求的多样性、数据量的大小等。
在系统环境变化时,同步的测量和观察各种性能指标,并进行数据的分析,从而确定在各种环境下被测试系统的可扩展性。如下图所示。
这个测试的主要目的是了解系统在什么样的环境中,以及什么样的变化会导致系统不能扩展。发现这些环境后,可以进一步有针对性的分析和加强。
<img src="https://static001.geekbang.org/resource/image/ae/12/aed41953ab37b10ffabab3b12c283012.png" alt="">
### 容量测试Capacity Testing
容量测试或者叫体积测试Volume Testing是用于确定一个单位容量能够支持的最大负载。比如一个程序运行在某种服务器上我们有时需要知道每台服务器能够支持的最大负载例如客户数从而决定需要部署多少台服务器才能满足预定的总负载要求。
容量测试一般是会不断增大负载,并且不断地测量各种性能指标。在性能目标变得不可接受之前,系统和程序可以成功处理的负载大小,就是单位容量可以承担的负载。为了尽量让得到的结果匹配实际生产环境,采用的负载流量最好是真正的生产环境的请求和数据。
正常生产环境中的流量和数据或许不够大到让一台服务器超载,因此我们需要解决这个问题。很多公司的解决方案是把其他服务器上的请求重定向到某一台被测试服务器,从而让这台服务器适度超载。这种机制我后面会用一讲专门讨论。
容量测试是确保系统稳定的重要一环。只有进行彻底的容量测试,并有相对应问题的解决方案,才可以使我们能够避免将来出现潜在的超载问题,例如增加的用户数或增加的数据量。
如下图所示,容量测试至少包含三个部分:可调节的流量负载、性能的测量、可以接受的性能指标。这三个部分一起就可以决定单位容量(比如一台服务器)的最大负载容量。这个数据可以帮助我们做各种决策,包括预估系统能负担的总负载;或者根据预期负载来决定部署多少台服务器。
<img src="https://static001.geekbang.org/resource/image/0d/36/0da67807c884dace487cb8f950bed136.png" alt="">
### 瓶颈测试Bottleneck Testing
瓶颈测试其实可以看作一种特殊的压力测试。它的目的是找到被测试系统和程序的最制约的资源类型比如CPU或者存储。瓶颈测试并不局限于只找到最制约的一个瓶颈它也可以同时找多个性能瓶颈。
找多个性能瓶颈的的意义主要有两点:
1. 如果最制约的瓶颈资源解决了,那么其他制约资源类型就自动会成为下一个瓶颈,所以需要未雨绸缪。
1. 系统设计时可以考虑在几个资源之间做些平衡比如用内存空间来换取CPU资源的使用。
### 压力测试Stress Testing
压力测试也是一种负载测试,不过它偏重的是在负载增加到超过系统设计预期后观察和验证系统的行为。当我们通过增加负载,对系统施压到超出设计期望的负载时,就能发现哪个模块或组件首先因超载而失败。这样我们就可以通过提升失败组件的性能来设计出更健壮、性能更优的系统。
相对于容量测试,压力测试的目的是为了暴露系统的问题,因此采用的负载不一定是真正的生产数据和客户请求。
## 总结
这一讲重点讲了几种性能测试。
<img src="https://static001.geekbang.org/resource/image/1c/73/1c5e4102c3f2b31b2f95ce9ee9851673.png" alt="">
在工作中和别人交流时,你一定还会听到各种不同叫法的性能测试。我从业多年的总体感觉就是,性能测试的种类太多,甚至对某一种测试怎么进行也众口纷纭。给人的感觉,就像古诗里面所说的,“乱花渐欲迷人眼”。
虽然各种性能测试叫法不一,但万变不离其宗,你只要主要抓住几点就行,比如分类的方式,流量的大小和测试的目的。
希望通过本讲的讨论,你能对不同的性能测试之间的区别更清楚一些了,以后工作交流和阅读文献时能搞清楚它们是什么种类的测试,从而对症下药,做好测试规划和合理的分析。
## 思考题
- 今天讲了这么多种测试,你平时用过几种?
- 对于没有用过的测试种类,你清楚了解它们的使用场景吗?
- 如果有的测试种类看起来还挺有用但自己没有试过,尝试着设计一个,并在工作中表现一下,把这种测试和其他已经有的测试种类的区别好好分析一下,让同事们和领导知道你才是性能测试专家!
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,119 @@
<audio id="audio" title="10 | 性能测试的规划和步骤:为什么性能测试不容易一蹴而就呢?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bc/6f/bcf012e5227094132d74df187e338f6f.mp3"></audio>
你好,我是庄振运。
上一讲我们讲了不同类型的性能测试。今天我们来讲**如何规划一个性能测试和具体的执行步骤**。在规划任何一种性能测试时最重要的事情是搞清楚被测试的实体也就是SUTSystem Under Test对应的性能指标和度量以及期望的结果。在此基础上根据测试的类型来决定和规划具体的测试步骤然后执行测试最后再合理地分析测试的结果。
为方便描述,我们用下图来表示整个性能测试的过程,总共七个部分。
<img src="https://static001.geekbang.org/resource/image/72/38/72857508b6a54ce2da0467ce9249c138.png" alt="">
大体上分为前后两大部分。前面四个部分分别是决定SUT、决定性能指标、决定指标的度量、决定期望结果。后面三个部分是性能测试的规划、测试的执行和结果分析。这三个部分根据测试的结果或许需要重复多次。
## 搞清楚测试对象
性能测试当然首先要搞清楚测试对象。但说起来有意思,我看到过很多做性能测试的人,对“什么才是他的测试对象”这个问题糊里糊涂的。经常碰到的情况是,有些人做了一大堆测试,但后来发现搞错测试对象了,所以大量的工作白做了。
为什么会导致这样的误会呢?
因为一个被测试的系统往往是复杂的,包含多个子系统和模块。如果对测试的类型和规划没有搞透彻,就很容易搞不清真正的测试对象。
测试的对象一般叫SUTSystem Under Test它可以是一段代码、一个模块、一个子系统或者一个整个的系统。比如要测试一个在线互联网服务的性能那么这整个系统包括软件、硬件和网络都算是SUT。再比如SUT也可以是一个子系统比如运行在某台服务器上的一个进程。
搞清楚SUT的重要之处是让测试做到有的放矢。除了SUT本身其他所有的模块和构件在整个性能测试的过程中都不能有任何性能瓶颈。
比如测试一个在线服务,那么所使用的负载和流量模块就不能成为瓶颈。如果这一点不能得到保证,那么性能测试得出的数据和结论就是不正确的。拿上面的互联网服务来举个例子,如果性能测试中负责产生负载流量的模块成了瓶颈,一秒钟只能发出一千个请求,那么你测出的吞吐量最多也就每秒一千请求。这样的结果和结论显然是不对的。
## 决定测试的性能指标
搞清楚测试对象SUT之后下一步就是决定具体的性能指标。
对一个面向终端客户的SUT而言一般就是和客户直接相关的性能指标比如客户端到端的服务延迟。如果SUT是系统中的某个模块那么测试的指标有可能是资源的使用率比如CPU或者内存使用率。
平时用的最多的性能指标有三个,就是服务响应时间,服务吞吐量和资源利用率。这三个指标各有侧重,分别对应了终端客户、业务平台以及容量系统。通常,响应时间是用户关注的指标,吞吐量是业务关注的指标,资源利用率是系统关注的指标。
## 决定测试指标的度量
决定性能指标后还需要更加具体到统计上的度量。比如你关注的是平均值还是百分位数例如P99也或许是某个置信区间的大小。
举例来说对服务响应时间延迟的指标而言一般需要同时考虑平均值、中位数和几个高端的百分位数比如99百分位。
## 决定性能测试的期望结果
SUT和性能指标都确定了那么下一步就是决定**我们期望从测试中得出什么样的结论**比如是为了确认SUT的性能满足一定的指标呢还是只希望获取一些性能数据做参考。搞清楚了测试的期望结果才能决定什么样的测试结果是可以接受的什么样是不能接受的。
假设SUT是一个互联网服务测试指标是端到端的平均服务延迟。我们或许已经知道可以接受的平均服务延迟的最大值比如500毫秒。如果性能测试测出的结果显示平均服务延迟是600毫秒那么这个测试结果显然是负面的就是被测互联网服务不够好不能接受。如果只是想获取性能数据那么这个600毫秒就是测试结果。
再举几个更复杂一点的测试期望结果的例子:
- 当2000个用户同时访问网站时所有客户的P99响应时间不超过2秒
- 测试应用程序崩溃前可以处理的最大并行用户数;
- 测试同时读取/写入500条记录的数据库执行时间
- 在峰值负载条件下检查应用程序和数据库服务器的CPU和内存使用情况
- 验证应用程序在不同负载条件下,比如较低、正常、中等和重载条件下的响应时间。
## 性能测试的规划
一个成功的性能测试离不开具体的规划,比如如下的几个方面的重要内容,包括负载流量的特征、负载如何注入、测试的数据、黑盒还是白盒测试、测试的工具、测试的环境等,我们逐一说明。
**负载流量的特征**和我们上一讲讲过的测试类型直接相关。首先我们需要决定是用真正的生产环境的负载还是仿真的负载。
那么**负载如何注入**呢?负载流量即使已经确定用真正的生产负载,还需要继续决定几个问题:
- 是用实时的流量呢,还是用过去捕捉的流量来重新注入?
- 流量的大小,是完全模拟生产环境呢,还是加大负载。
- 如果不使用实时生产流量,那么如何注入呢?
很多情况下,直接采用开源的工具就够了,但有些情况下需要自己开发或者对开源工具进行二次开发。
很多负载需要操作数据,比如数据库查询。所以,我们就需要决定**测试的数据**,是用真正的用户数据还是仿真的数据。
测试选**黑盒**还是**白盒**呢黑盒就是不改变SUT完全做被动观察。白盒就是允许改变SUT比如在程序中间输出更多的性能日志信息等。白盒的问题就是改变了SUT的行为可能导致最终得到的数据失真。
市场上有各种各样的性能**测试工具**比如JMeter但是选择什么样的测试工具将取决于许多因素例如支持的协议类型、许可证成本、硬件要求、平台支持等。我们后面会有一讲专门讲各种工具。
配置一个合适的**测试环境**很重要,理想情况下,应该尽量用与生产平台相同的硬件、路由器配置、网络,甚至是网络背景流量等。不过值得说明的是,有时候我们会特意选取和生产环境不同的测试环境,比如当我们希望提高可重复性,降低测试环境的噪音;那么我们就会选取一个单独的不受干扰的环境来测试。
## 性能测试的执行
测试规划完毕后就是执行了,这个过程相对简单。
但是需要强调的是,**测试结果的可重复性非常重要**。性能测试和性能优化很多情况下是一个长期的行为,所以需要固定测试性能指标、测试负载、测试环境,这样才能客观反映性能的实际情况,也能展现出优化的效果。
很多性能测试比较复杂,所以不要期望一次测试就能成功让整个测试环境工作。经常需要实验好几次才能真正让整个测试环境搭配成功。
所以,复杂的性能测试需要多次迭代执行,一般有以下几种方式迭代:
1. 分步进行:把复杂的测试验证过程分成几步,一次验证一步,最后一步是整个完整的测试。这样的好处是每一步的问题都可以及早暴露,快速解决。
1. 先短时间测试,再长时间测试:有些测试需要执行很长时间,比如一周。如果一周后才发现测试过程有错误,那就浪费了一周时间。所以,为了避免浪费时间,会先进行短期测试,比如半小时。然后分析结果,来发现其中的问题。这样可以比较快速地纠正测试中的错误。
1. 模拟测试:在实际使用负载测试之前,先执行简单的负载测试以检查各种工具的正确性。
## 分析测试结果
测试完毕,就需要分析测试结果了。
如果对一次测试的结果我们不满意,我们就需要重新回到以前的步骤上,或者重新执行测试的步骤,或者重新规划测试的方法。
根据我的经验,几乎所有的性能测试,就算是看起来非常简单直白的测试,都需要反复进行多次,才能达到满意的效果。 所以如果发生这样的情况,你千万不要气馁。
为什么性能测试不容易一蹴而就呢?
这是因为任何测试,其实都依赖于很多其他模块,比如流量的产生、数据的注入、环境的搭建、干扰的排除、数据的收集、结果的稳定等,这些模块都不简单。所以寄希望于“毕其功于一役”,一次就完美地规划和执行一个测试,几乎是不可能的。
每一次测试完毕,我们都要认真分析一下结果,如果不满意,就需要看看如何改进。如果是测试方法不对,就需要重新规划。如果是环境不稳定,有干扰,那么就需要考虑如何消除干扰,净化测试环境。如果数据的收集不够多,就需要从测试模块中输出更多的信息。
## 总结
郑板桥在他的七言绝句《竹石》中说:“咬定青山不放松,立根原在破岩中“,赞扬竹子目标明确,基础扎实,而且百折不挠。
<img src="https://static001.geekbang.org/resource/image/a4/8c/a41d275dbe54a1034eb1de44b9cab38c.png" alt="">
我们做性能测试也是如此。只有条理分明,目标清楚,目的明确,规划仔细,执行得力,才能“千磨万击还坚劲,任尔东西南北风“。
这样的测试或许需要执行很多次,因为经常需要调整测试的方法,但不达目的,我们决不罢休。
## 思考题
假设你需要重复做一种性能测试但是你发现每次的测试结果都很不一样你可以想一想会有哪些原因呢举几个例子或许SUT服务器上面还在跑其他程序也或许注入的负载流量不稳定还有其他因素吗
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="11 | 性能测试的工具:七大测试场景如何选择高质量的测试工具?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/da/e6552235d7ffaf75cd1fe2e306cf0ada.mp3"></audio>
你好,我是庄振运。
我们在前面两讲讨论了如何进行性能测试的规划和设计。性能测试离不开合适的工具,那么这一讲,我们来讨论一下测试工具的分类和构成,并根据七个不同的测试场景,分别学习几个常用的高质量测试工具,尤其是开源的。
这七个测试场景分别是Web测试、系统测试、数据库测试、文件IO测试、存储测试、网络测试以及移动App测试。
我们应该尽量借助这些好用的工具,而不要自己去重新开发。
自己开发的话,费时费力不说,开发出来的工具也不见得会比这些工具好用。更重要的是,性能测试工具的开发和使用过程中,有很多需要注意的地方和容易陷入的坑,一不小心就会掉坑。我们下一讲就会讨论常见的坑。
而这些常用的工具已经经过很多人的努力和长时间的改进,在很多方面避免了容易出现的各种问题,所以尽量使用它们吧。
## 测试工具的分类
首先你必须认识到,性能测试工具是繁多的。之所以繁多,是因为每种工具适合的场合不同,所以它们各有特点。比如如下几个方面:
- 测试场景是针对Web环境、移动App、系统、数据库还是模块测试
- 测试类型:是基准测试还是峰值测试?
- 免费还是收费:开源工具一般都是免费的;但是很多收费工具也的确物有所值。
- 支持的协议比如是否支持HTTP协议、FTP协议等等。
- 支持的功能:比如并发性支持度,能否分析测试结果,能否录制性能测试脚本等。
## 测试工具的模块
要评价一个测试工具的优劣好坏,我们就需要知道测试工具的模块和测试的一般过程。
大规模性能测试的一般过程是通过录制、回放定制的脚本模拟多用户同时访问被测试系统SUT来产生负载压力同时监控并记录各种性能指标最后生成性能分析结果和报告从而完成性能测试的基本任务。
比照这个过程,一个稍微健全的测试工具都会包括以下的模块:
- 负载生成模块负责产生足够的流量负载。多少流量算足够这得根据测试类型和具体需求来定。如果测试类型是压力测试那么产生的流量一定要大到SUT不能处理的程度。
- 测试数据收集模块:负责获取测试的数据,包括具体的各种性能数据。这个收集可以是实时的,就是在测试进行之中收集;也可以是后期,等测试完成之后收集的。
- 结果分析和展示:有了大量的测试数据,就需要进行分析并展示。同样的,这个过程可以是实时的,也可以是等全部测试完成后进行。
- 资源监控模块测试过程中离不开对SUT和流量生成模块的实时资源监控目的是确保这两个模块运行正常。具体来说流量生成模块必须不能负载过量否则很可能产生的流量不够大。SUT也要确保运行还是正常的否则整个测试就失去意义了。
- 控制中心模块:测试者需要用这个模块和整个测试系统来交互,比如开始或停止测试,改变测试的各种参数等等。
这几个模块的关系我用下面这张图表示。
<img src="https://static001.geekbang.org/resource/image/1c/4f/1c4e724cff73f9595bb63b49a5cd3a4f.png" alt="">
## Web测试场景
我们先看第一个测试场景Web测试。这一场景的测试工具很多我们介绍几个。
**JMeter**是一款优秀而小巧精致的开源测试工具是用Java写的。JMeter安装简单使用方便所以很流行建议每个性能测试者都掌握它。熟练使用它在绝大多数场合都能大大提高测试效率。JMeter的测试是基于HTTP协议的所以最好对HTTP协议熟悉一些才能快速上手和理解里面的概念。
**LoadRunner**是HP公司的一款测试工具功能和资料都比较全也好用但不是开源的。它的组成模块都很强大比如分析模块中的AutoCorrelation向导。这个向导会自动整理所有的监控和诊断数据并找出导致性能降低的最主要的几个原因。这样就将性能测试结果转化为可处理的精确数据从而使开发团队大大减少了解决问题的时间。
**Locust**是基于Python的开源测试工具支持HTTP、HTTPS等协议。它的一个突出优点是可扩展性很好。
## 系统测试场景
这种场景下的测试工具很多,我主要介绍两个。
**UnixBench**是一个Unix系统比如Unix、BSD、Linux下的性能测试工具是开源的而且被普遍用于测试Linux系统主机的性能。这个工具可以测试很多模块和场景比如系统调用、读写、进程、图形化测试、2D、3D、管道、运算、C库等它的测试结果可以作为基准性能测试数据。
比如你可以用它测试从一个文件向另外一个文件传输数据的速率要求每次测试使用不同大小的缓冲区。再比如测试两个进程通过一个管道Pipe交换一个不断增大的整数的速度类似现实编程中的一些应用这个测试会首先创建一个子进程再和这个子进程进行双向的管道传输。它也可以测试进入和离开操作系统内核的开销即一次系统调用System Call的开销代价。
这是通过反复地调用 getpid 函数的小程序来进行的。
**Perf**是Linux下最普遍使用的性能分析工具功能强大全面俗称性能测试的“瑞士军刀”。比如Perf 可以对程序进行函数级别的采样,从而了解程序的性能瓶颈究竟在哪里。或者计算每个时钟周期内的指令数等等。
Perf的原理是使用特殊的计数器来进行性能统计。它既可以分析指定应用程序的性能问题也可以用来分析内核的性能问题所以可以全面理解应用程序中的性能瓶颈。
我个人建议每个关心性能的人都了解和学习一下Perf的使用。
## 数据库测试场景
数据库的测试工具也是汗牛充栋。
**SysBench**是一个容易使用的的开源多线程测试工具主要用于测试数据库性能比如MySQL Oracle和PostgreSQL但也可以测试CPU内存文件系统等性能。它的强项包括数据分析和展示模块多线程并发性比较好而且开销低。另外我们可以很容易地定制脚本来创建新的测试。
**mysqlslap**是MySQL自带的压力测试工具它可以轻松模拟出大量客户端同时操作数据库的情况。
## 文件IO和存储测试场景
对文件系统和存储系统的性能测试工具也有很多,比如**ioZone**可以测试不同操作系统中的文件系统的读写性能。比如可以测试不同IO读写方式下硬盘的性能。
Bonnie++是一个用来测试UNIX文件系统和磁盘性能的测试工具它可以通过一系列的简单测试来生成硬盘和文件系统的性能参数。这个工具很容易使用输出结果显示方面很不错。
如果你希望在Linux中很快地测试硬盘读写性能**dd**这个很有用的命令经常就够用了。这个工具就是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
## 网络测试场景
网络测试也有各种各样的工具。
**Netperf**是很不错的一个网络性能的测量工具主要针对基于TCP或UDP的传输。它有两种基本模式即批量数据传输bulk data transfer模式和请求/应答request/reponse模式我们可以根据应用的不同来选择不同模式。
测试结果所反映的,是两个系统之间发送和接受数据的速度和效率,即一个系统能够以多快的速度向另一个系统发送数据,以及后者能够以多快的速度接收数据。
**Iperf**可以测试最大TCP和UDP带宽性能具有多种参数和UDP特性可以根据需要调整可以报告带宽、延迟抖动和数据包丢失。这个工具使用起来很简单一条命令行就可以。比如如下的测试“采用UDP协议以100Mbps的数据发送速率从本机到服务器192.168.1.1作上传带宽测试测试时间为120秒”或者“以50Mbps的发送速率同时向服务器端发起100个连接线程”。
## 移动App测试场景
移动App测试的性能指标主要是内存、CPU、电量使用、启动时长、显示帧率、网络流量等。 针对App的性能测试工具和平台可以按照两种方式分类。
第一种分类是根据**线下还是线上**。线下App性能测试主要依靠传统测试手段和方法比如不同的版本框架等等都需要进行App性能测试。 线上测试算是场景化测试主要针对大规模或者动态环境让App在特定场合和特别条件下更精准地衡量App的核心性能。
另外一种分类方式是**平台生态系统**现在主要有安卓Android和iOS。有的测试工具可以兼容多个平台比如**Appium**就是一个可以同时支持安卓和iOS的测试框架的工具功能强大。
对Android的测试工具常用的有**adb**Android Debug Bridge和**Monkey**。Monkey是Android SDK自带的测试工具。Monkey的意思是猴子 顾名思义,就是在电脑面前乱敲键盘在测试。 在测试过程中会向系统发送伪随机的用户事件流如按键输入、触摸屏输入、手势输入等对App进行压力测试也有日志输出。
对iOS App的测试工具也很多比如XCTest、Frank、KIF等等这里就不展开了。
## 总结
古人说:“工欲善其事,必先利其器”。业界已经有很多好用的工具,一般都能满足大多数场合的测试要求,会让我们的测试工作如虎添翼。
<img src="https://static001.geekbang.org/resource/image/fd/47/fdc924cbf4fe1122ff2da78e4cccdd47.png" alt="">
虽然有些环境下我们经常倾向于自己开发某些工具,但是往往会花费很多时间,而且很容易陷入各种各样的坑中,因此尽量避免自己开发。
这一讲介绍的几款工具适用于不同的场合,如果你能合理运用它们,应该会取得事半功倍的效果。
## 思考题
- 你曾经用过什么样的性能测试工具?
- 为什么会选择这些工具,而不是其他的工具?
- 你使用的测试工具有什么优点和缺点?
- 你是怎么克服它的缺点的?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,154 @@
<audio id="audio" title="12 | 九条性能测试的经验和教训:如何保证测试结果可靠且可重复?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4b/57/4b29e641c379c76f56519d06bb404e57.mp3"></audio>
你好,我是庄振运。
上一讲我们介绍了十几种常用的性能测试工具。我们知道,性能测试的一个关键是保证测试结果可靠、可重复,否则就没有意义。所以,我们今天来学习一下进行性能测试时,这方面的经验和教训。
根据以前做过的相关工作,我总结了九条这样的经验和教训。按照逻辑时间顺序,我将它们大体上分成三大类别,就是测试前的规划、测试中的变化和测试后的结果分析;每一类又有三条要点。
<img src="https://static001.geekbang.org/resource/image/ee/a5/eee41a2f022ed7c88ea2a31c5b4157a5.png" alt="">
## 测试规划
三大类别的第一类别是测试规划,我们先来说说测试规划时要注意的三条要点。
### 1.详细记录测试环境和测试过程
做每个性能测试时,测试的环境至关重要。这里的环境包括软件硬件、操作系统、测试的负载、使用的数据等等。
测试的环境不同,性能测试的结果可能会迥异。除了测试环境,其它几个因素比如测试的过程,包括步骤和配置的改变也有相似的重要性。所以,我们每次测试都要把测试环境和测试过程记录下来,为将来分析数据做参考。
这些测试环境信息包括什么呢?大体上是操作系统和程序的版本号,以及各种软件参数的设置等等。
记录测试环境的目的是为了以后的各种分析。比如我们如果发现两次测试结果不匹配,需要找到不匹配的原因,那么这些测试环境就是相当关键的信息。如果两次测试结果的不同是因为软件配置不同导致的,那么根据记录的测试环境信息,我们就很容易根因出来。
至于如何记录,我们可以去手工去记录,但最好是自动记录,因为手工记录既费时费力,又容易出错。如果我们知道常用的环境配置的路径,可以很方便的写个程序来自动记录。或者依靠能自动记录配置的软件也可以。
### 2.快速地复位测试环境
有时候性能测试需要重复进行多次,那么就需要在每一次测试后,能够有快速“复位”到初始测试环境的机制。这个复位机制越简单有效越好,最好能达到所谓“一键复位”的程度,从而最大限度地降低手工复位的工作量。
复位的具体内容和方式要根据性能测试的情况而定,这就要分析一下在每个测试过程中,哪些子系统的配置和参数被改动或者影响了。
虽然理论上说,我们只需要恢复那些会影响测试结果的配置和参数,但是由于系统的复杂性,有些乍看起来不会影响测试结果的参数,其实或多或少也会影响测试结果。所以一般来讲,我们会**把所有的配置和参数都恢复一下**。环境复位的具体方式包括重新拷贝文件、重置配置参数、清空各种缓存等。
举个具体例子,如果是测试一个存储系统的性能,比如硬盘的性能。如果测试过程中写入了大量数据到存储系统,那么测试完毕,恢复环境的时候,就需要删掉这些数据,以便使被测系统回到初始状态。
再举个类似的例子,如果是测试一个数据库的性能,那么测试过程可能发出了很多查询请求。因为数据库经常会缓存各种情况和结果,所以在恢复测试环境时,不要忘记把缓存也清空。
一个现代计算机系统中往往有很多种缓存比如数据库缓存、文件系统缓存、存储缓存比如SAN等。不同种类的缓存自然有不同的清空方式。对于数据库缓存在每个测试之前可以用命令行来刷新数据库缓存如果数据库不提供这样的命令则需要重新启动数据库。对于文件系统缓存一般需要重新启动服务器。对于SAN缓存可以考虑在测试期间减少甚至关闭缓存或者用大量的随机数据来“污染”缓存。
### 3.足够的负载请求和数据
性能测试需要流量负载以及相关的数据,我们需要特别注意保证它们的多样化和代表性。否则测试结果会严重失真。
当使用相同的测试数据进行重复测试时,如果负载请求不够大,那么各种缓存可能会严重影响结果。
如何识别缓存的影响呢?
各级缓存系统都有相应的统计指标和命令比如文件系统缓存和SAN缓存中的缓存命中率就可以通过统计信息中报告的延迟并且结合经验来识别。举例来说如果一个随机8KB或16KB数据的对硬盘的读写测量出的延迟不到1毫秒那就实在是“太快”了快得让人不敢相信可以肯定它是命中某种缓存了。
除了合理清空缓存外,更有效地方式是保证测试时间足够长、测试的负载请求足够多和数据足够多样化,从而最大限度地减少或者掩盖缓存等其他因素的影响。
## 测试进行
测试规划之后,我们就要关注测试中的变化了。
### 1.性能数据日志要适当输出
性能测试过程中也需要实时输出有关的性能数据和日志比如CPU使用率数据。这些数据对于测试完成后的分析至关重要。
输出的数据和日志最好保存起来,以方便后期处理/重新处理。比如执行过很多不同参数配置的测试之后,我们经常需要进行测试之间的比较,这时候就需要仔细检查以前输出的日志了。
输出数据的多少也需要注意,虽然我们希望尽量多地输出,但是也要意识到,太多的输出有时候会起反作用。
- 存储的开销:可以用压缩存储来解决。
- 数据处理时间的开销:注意压缩和解压缩也要花时间。
- 可能影响性能测试的结果:有时候因为某些原因,日志输出会影响被测系统的性能,比如往一个文件写入日志,可能会导致系统的暂停,从而影响测试的结果(这个我们下一讲会详细剖析)。
### 2.测试环境要稳定
性能测试的环境在测试进行过程中,以及重复测试时一定要保持稳定和一致,否则测试结果就不可靠或者不能重复。
比如一个测试进行中,背景流量的负载产生了剧烈变化,导致被测系统的延迟增加。假如这个背景流量变化不是预期产生在测试规划之内的,就会造成测试结果失真。
类似的,如果测试过程中有不可控的因素,造成每次重复测试结果都不同,那这样的测试就不可靠。无论测试结果好坏,都不能用来作出有用的结论。
一般的解决方案,是尽量在一个独立无干扰的环境中进行测试,加上每次测试都准确地恢复测试环境,就能最大限度地保证测试环境的稳定。
### 3.一次调一个参数的利弊
在性能调优测试中,就是通过实验来找出系统的最优配置。
对这种测试,我们经常听到的经验是,“一次只调一个参数,通过对比实验,就能知道这个参数的最佳值”。你觉得这个经验对吗?
我们先看这个经验的出发点。性能调优过程中有很多可调参数和配置,互相之间的影响不清楚,因此不宜对系统的各种参数进行随意的改动。应该以基本参考设置为基础,逐次根据实际测试结果进行优化,一次只对某个领域进行性能调优,并且每次只改动一个设置和参数,避免其他参数和相关因素的干扰。
这个经验有它的道理,但我觉得这样做既对也不对,你不能盲从,否则就会错过最优配置的机会。
我举个生活中的例子来说明。假设有一个装水的木桶,这个木桶由多块可调整高度的木条组成。假设这些木条的初始高度不一,我们的目的是找到一个木条高度组合,从而实现最大的装水量。这个问题看起来很简单,根据木桶定律,就是把每块木条都调到最高嘛。
<img src="https://static001.geekbang.org/resource/image/c9/e7/c91e6cbdd7b63b27a0d1f0bf9bd5ace7.png" alt="">
我们假设这个木桶是我们的被测系统,每块木条就是一个参数。再假设我们对木条之间的关系不清楚。如果一次只调整一个参数,然后实验测试装水量。因为木桶装水量取决于高度最低的木条,我们或许会得出结论——只有那块最低木条值得调,其他木条的高度都不重要。
这种结论的结果就是把最低木条调高,比如调到最高。那么这个所谓的“最优系统”,也就是整个木桶的装水量取决于新的最低木条,也就是原来木桶中高度次低的那个木条。
我们很容易看出,如果一次调整多个木条,那么我们就会很快地找出一个更优的系统,也就是一个装水更多的木桶。
## 结果分析
测试后的结果分析也是需要你关注的重点,三条经验如下:
### 1.根因分析要由易到难
如果在性能测试过程中需要查找性能瓶颈,查找的过程一定要由易到难逐步排查。因为参考我们学过的帕累托法则,从最明显的性能瓶颈来开始,往往可以事半功倍。
首先从最常见的几种资源和几个指标查起比如CPU使用率、存储IO繁忙度、内存大小、网络发送和接收速度等。
进一步的分析就可以针对不太明显的资源比如内存带宽缓存击中率线程加锁解锁等从而过渡到应用程序和系统的一些配置参数。这些配置参数包括应用服务器及中间件操作系统瓶颈数据库、WEB服务器的配置还有应用业务瓶颈比如SQL语句、数据库设计、业务逻辑、算法、数据等。
### 2.几种测试最好互相验证
各种性能测试工具和测试手段都有自己的局限性或缺陷,从而可能会造成测试结果出现偏差。所以,如果条件和时间允许,最好使用几种不同测试工具或手段,分别进行独立的进行测试,并将结果相互比较和验证。
如果几种测试比较后结果相似,那么皆大欢喜。
否则就需要进行深入比较分析,弄明白造成结果不同的原因。分析以后,如果能够清楚地了解根因并作出合理解释,那么很多时候就够了,可以止于此。最后形成结论时,只要稍加有针对性地说明就可以。
### 3.测试结果和生产环境比较
如果性能测试是在非生产环境中进行的,那么得出的测试结果或许会和生产环境大相径庭。如果我们测试的目的是尽量和生产环境一致,就需要仔细审查每个测试的环节,包括测试环境和测试流程。
假如非生产环境的测试结果和生产环境的测量不同,很多情况下是由于测试环境不同导致,尤其要注意的是网络环境的差异。
比如测试的环境是在局域网而真正的生产环境是无线网或者4G那么可以肯定这样的局域网测试没有什么意义。网络差异的影响经常会大大超过很多人的预期因为实际的生产环境还牵扯很多上层协议和背景流量比如HTTP协议。
假设4G用户在网络层或者链路层上相对局域网而言只是多了30毫秒的延迟。在Web服务器中等负载的情况下这几十毫秒的链路层延迟就可能导致应用层响应时间增加惊人的几十秒延迟。
对网络环境这点,最理想的解决方式当然是在生产环境进行测试。如果实现这点有困难,那么可以考虑使用一定的网络仿真,来模拟真实生产环境。
除了网络环境的影响真实客户的操作流程也可能造成测量结果的不同。比如一个真正的Web用户或者APP用户在访问我们的系统时往往会有思考或者阅读时间所以真实世界的用户不太会在1秒钟内发出背对背的好几个页面请求。对于这种情况的差异可以通过人为地添加停顿来模拟思考时间。
现在很多测试工具都支持这种思考时间的引入。比如使用JMeter可以使用高斯随机计时器Gaussian Random Timer来模拟现实世界中的用户以随机方式和页面进行交互。
## 总结
<img src="https://static001.geekbang.org/resource/image/eb/c0/eb79090925a67c93acc09ae2efed0cc0.png" alt="">
当年毛主席给彭德怀写过几句诗:
山高路远坑深,<br>
大军纵横驰奔。<br>
谁敢横刀立马?<br>
唯我彭大将军。
打仗有很多坑,做性能工作也有很多坑。我们要把性能测试的工作做好,本身就不容易。山高路远都不在乎,但一定要注意其中的陷阱。
所以只有不断地学习别人总结的经验,吸取别人的教训,解决测试的各种挑战,才能得出可靠,可重复并且有意义的性能测试结果。
## 思考题
回想一下你做过的性能测试,有没有踩过今天介绍的几个坑?踩过之后,是怎么防止以后不再踩同样的坑的?对没有踩过的坑,你有没有方法避免以后中招呢?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,126 @@
<audio id="audio" title="13 | 性能测试的工程集成:如何与产品开发和运维业务有机集成?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ea/69/ead8315502687b20efd01abfff216969.mp3"></audio>
你好,我是庄振运。
前面几讲,我们讨论了性能测试的几个方面,包括测试的种类、测试的规划、工具以及经验和教训。
今天我们讨论性能测试如何和其他系统进行智能集成,也就是如何让**性能测试**这一工作从单独的、一次性的、手工发起的、传统的人工操作,进化成一个和开发及运维过程相结合的、持续的、自动重复执行的智能操作。
## 性能测试模式的演化
性能测试作为IT公司的一种重要工作它的工作模式正在从传统的手工模式不断进化成智能集成的自动模式。
这样的演化主要归功于这些年互联网技术的进步和业务需求的提高,包括数据量的加大和业务的日益复杂化、客户需求的多元化、公司业务规模的扩大,以及人工智能和机器学习的不断成熟。
那么,性能测试的模式进化表现在哪些方面呢?主要有四个方面,如下图所示:
<img src="https://static001.geekbang.org/resource/image/18/df/189133714d4ed913957d36667dab2bdf.png" alt="">
1.从**独立的操作**演化成和其他系统(比如开发和运维)的**有机集成**。
公司中很多业务都和性能测试有关,尤其是产品开发和系统运维业务。性能测试需要和这些业务紧密结合,从而使相关工作的效率极大提高。
2.从一次次的**单独执行**演化成**持续而重复的执行**。
从开发角度来看,一个产品的程序代码会不断被开发和增强,包括加入新的功能和修补发现的错误。每次代码改变都需要进行性能测试,以确保程序面对客户的端到端性能和资源利用效率没有变低。从运维角度来看,公司的产品系统也需要持续地进行性能测试,以尽早发现可能的问题。
3.从**手工进行**测试演化成**自动化**的测试。
每当需要进行性能测试的时候,系统越来越需要自动触发,并按照既定规则来开始测试。测试完成后也需要自动进行分析,并根据分析的结果继续进行后面的定义好的步骤。整个过程最好不需要测试人员的人工参与,以降低运营成本并减少人为错误。
4.从常规的**人工操作**演化成基于人工智能AI和机器学习ML的**智能操作**。
数据量的变大让有效的机器学习成为可能。同时伴随着人工智能技术的不断成熟,传统常规的性能测试一步步地走向智能的自动性能优化。
## 和产品开发的系统集成
性能测试和产品开发密切相关,我用下面这张图片表示它们之间的关系。我分成两部分讲,先讲和产品开发的系统集成。
<img src="https://static001.geekbang.org/resource/image/b1/c7/b1a78241835c50a5ec0754fabfbfd9c7.png" alt="">
性能测试和产品开发的集成也是所谓“持续集成”Continuous Integration的一部分。
什么是“持续集成”呢?
这个概念已经在软件开发领域存在很多年了本来是为了配合敏捷开发Agile Developement的速度和效率而产生的是把源代码管理、代码检查、程序编译、性能和功能测试、产品部署等一系列操作整合在一起的概念和工具。
怎么才叫“持续”呢?
就是从程序员提交了源码开始,相关工具就会自动进行编译、测试等一系列运作,并将结果反馈给程序员。这样,提交代码的程序员很快就会知道刚刚提交的代码有没有问题。
可能发生的问题有很多种,包括编译错误、整个程序不能运作、能编译运行但是整体性能变差等等。如果发现这样的问题,程序员和团队就可以迅速进行改正,不必等到开发周期后期才寻找和修复缺陷。
什么叫“集成”?
就是上述一套操作都是有相应工具支持的,几个工具又集成在一起,构成一个完整的系统。
持续集成包括两个重要阶段:持续交付和持续部署。我们先说一下持续交付。
**持续交付**Continuous delivery指的是开发人员的每一次代码提交都会自动地编译并且运行已经定义好的测试程序。
测试程序里面就包括性能测试和结果分析。如果分析的结果确认这次代码提交没有性能问题,就成功接受这次提交。否则,就会采取措施通知代码提交者去修正代码;并重复这个过程,直到通过。
**持续部署**Continuous deployment是持续交付的下一步指的是代码通过评审以后自动部署到真正的生产环境。
持续部署的目标是,代码在任何时刻都是可部署的,并且是可以进入生产阶段的。持续部署的前提是能自动化完成测试、构建、部署等步骤。同时,如果一旦部署了的版本发生不能接受的问题,就需要回滚到上一个版本。这个回滚的过程也需要简单方便,否则会造成非常大的混乱。
持续集成的价值何在?
持续集成的价值主要有几点:降低代码开发风险,及早发现集成错误,减少手动测试过程,快速生成测试结果,提高程序员和开发团队的安全感。同时,频繁的提交代码,也会鼓励和促使开发人员创建模块化和低复杂性的代码。
### 工具集
支持持续集成的工具有很多按照领域分可以有下面几大类代码版本管理、代码检查、编译连接、功能测试、性能测试、性能分析、代码覆盖率、结果展示等。有些流行的持续集成服务器可以整合这些工具或功能比如Jenkins、CruiseControl、Travis等。
我举一个完整持续集成的工具例子。比如一个Java开发团队在开发某项目时用了如下的一系列工具
- 用JStyle来分析Java源代码
- 用Ant 构建运行JUnit来进行简单的功能测试
- 用DbUnit 执行长时间的数据库组件测试
- 用JUnitPerf来进行负载和压力测试
- 用JProfiler来进行全功能的代码性能剖析
- 用 Selenium运行基于 Web 的功能测试
- 用 Cobertura测量代码覆盖率
- 用 CruiseControl 作为服务器来管理持续集成
## 和运维业务的系统集成
讲完了性能测试和开发过程的集成,我们接着谈谈和运维业务的系统集成。
广义上来讲,性能工作也是运维的一部分,包括性能测试、性能分析和性能优化。但是,如果把性能工作和其他运维业务分开来看,它就需要和其他运维业务有机而智能地集成。
运维业务的一个趋势是智能化。
智能化的原因是互联网数据量的变大运维业务的多样化和复杂化以及对运维服务质量要求的提高比如低成本、低延迟、高防范。这样一来很多传统的运维技术和解决方案已经不能满足当前运维所需。另一方面机器学习ML和 人工智能AI技术在飞速发展这就推生了智能运维AIOpsArtificial Intelligence for IT Operations这是运维未来发展的必然趋势。
在这样一个趋势里,运维就需要和性能测试的过程紧密集成。
一是通过持续而智能的性能测试,能及时发现已有的和将来可能会发生的性能问题,从而快速修复和及时预防。比如,根据性能测试的结果,可能会发现在不久的将来,整个系统的某项资源会用光,有可能导致系统挂掉。这种情况,我们就可以提前采取相应的措施,来避免这一问题的发生。
二是通过不同种类的性能测试,来找出最佳的解决方案。或许,对有些简单的性能问题,我们能很容易发现和解决,但对很多复杂的性能问题,就比较难找出原因和确定最优方案。要找出最主要的性能问题根因,经常需要进一步的性能测试。即使找到根因,现代互联网服务产品的子模块之间依存度很高,互相的交互多而复杂。那么什么样的解决方案才是效果最好,最容易实现的的,往往需要进行进一步测试来验证。
性能测试和运维的集成必须有两个特点:**有机**和**自动**。
不管是性能衡量、问题预测、根因分析还是性能优化,人工去执行都非常费时费力,从而不可取。唯一可行的就是借助于人工智能来尽量自动化。除了持续自动地进行性能测试,发现性能问题还需要进行自动分析,找出问题后也要执行自动调整优化。
我们的目标是**让性能测试和运维业务进行智能的有机集成**,其中的智能来自于对大数据的分析和机器学习。
无论是本业务系统的历史数据,还是其他业务系统的数据,甚至是业界其他公司的数据和经验,都是机器学习的对象和分析的基础。同时,我们还需要注入适当的知识和规则,来帮助这一套集成的持续优化。
在这一过程中,数据的采集和整理是一切的基础。公司层面需要全方位,实时、多维度、全量地对各种运维数据采集、整理和存储。这里的运维数据包括基础架构的机器监控数据,内网和外网的网络数据,公司业务流量数据,工单系统数据,日志监控数据等。这些数据需要有统一而合理的接口,以方便访问。
## 总结
性能测试虽然有很多讲究和注意的地方,但它本身也是作为整个公司业务的一个子系统而存在的。我们需要把它和其他几个子系统,尤其是产品开发子系统和运维子系统有机地整合集成起来,让这几个子系统之间的操作“浑然天成”,才能获取整个公司业务的最大收益。
<img src="https://static001.geekbang.org/resource/image/45/16/4507f2890c2a8a050b4e2c83f8714216.png" alt="">
白居易的《长恨歌》有两句:“在天愿作比翼鸟,在地愿为连理枝”,期盼的是一种比翼齐飞,一种同心同德。
这种期盼用在这一讲的子系统集成也挺合适。性能测试和产品开发子系统的集成,可以使得开发过程的迭代更快、更高效,并保证代码质量。性能测试和运维子系统的集成,可以让整个程序和业务保持高性能运转,提高公司业务质量和公司营收。
## 思考题
你现在的开发环境中有没有整合这样的自动测试系统?如果没有,你觉得值得考虑一下吗?如果你能从无到有的搭建一个这样的系统,你老板会不会对你刮目相看?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,103 @@
<audio id="audio" title="35 | 职业发展:从“锦上添花”到“不可或缺”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6e/43/6e6095cac7aba01112f3f0068f82b543.mp3"></audio>
你好,我是庄振运。
到今天,我们已经基本讲完了所有的技术方面的内容。通过这些内容,希望你对性能和容量工程这一领域有了更多的认识和收获。
从今天开始,我想和你介绍一下,性能优化和容量工程这个职业在公司里面的定位、工作的形式特点,以及职业的发展前景。
## 在公司里面的定位
性能优化和容量工程这个工作在公司各种业务中的定位是什么样的呢?
其实性能和容量工程这样的职位几乎每个互联网公司都有只是具体的职位名称和工作内容不同。甚至很多非互联网公司也会有这样的职位毕竟现代社会每个公司都有IT部门而IT的性能对每个公司都是很重要的。
根据我这些年的从业经验和观察结果来看,由于性能优化这个领域覆盖面变广、复杂度变高,性能工程师的重要性越发提高,相关职位也越来越多。
其实这种情况的出现非常合理。从广义上来讲,性能工程和容量工程涵盖了很多方面,比如软件测试、软件部署、互联网服务监测、性能的优化、性能问题的根因及处理、容量的管理和分配、容量效率的提升等等。这些领域的工作,都是公司里面必不可少的。
最近几年,互联网正在发生巨大的变化。
第一个变化,是**数据量增大**了。你也知道,今天已经是大数据横行的时代,现在要是说自己不懂大数据,估计都不好意思出门。
第二个变化,是**互联网服务更加多样化**了。层出不穷的新业务(比如共享经济),提出了新的互联网服务需求,这些需求最终都需要部署到公司的基础设施上,需要相应的容量支持。
第三个变化,是**提高了对性能质量要求**。伴随着这些新业务、新需求,互联网服务越来越需要提高效率,降低延迟,增加可靠性。
所以,如果说前几年性能工程在公司里面的定位,还是“锦上添花”“没有无所谓,有了也挺好”的话,那么现在的定位就是“不可或缺”的、“一个也不能少”的工作之一了。
## 工作的形式和性质
你或许对“性能工程师”Performance Engineer这一工作有些了解可能已经发现这一工作的具体形式在不同的公司里面很不一样。有的公司里QAQuality assurance ,质量控制或者测试)的工作,就称为性能工程;有的公司里负责整个软件服务架构的工作叫性能工程。
“性能工程”这一角色的具体工作内容,的确是取决于公司业务和职位定位,大体上有这么七种:
**软件性能测试**是最普遍的就是我们常说的QA。虽然它的工作内容相对简单但对于性能工程领域的新人来说这其实是一个不错的开始。通过软件测试你可以学到很多相关的知识。
**性能问题诊断**这个工作经常和运维工作捆绑在一起。比如系统和平台出了各种性能问题,就需要做诊断和根因分析。
**系统性能优化**就是在性能测试和性能诊断的基础上,做到提升系统性能和效率。这样的工作经常要和很多模块打交道,比如硬件、软件、网络等等,就需要你拥有比较全面的知识。
**网站平台性能监测和优化**也是性能工程的一种。现代互联网业务,对性能要求越来越高,比如端到端低延迟、网站高可靠性等。这就需要实时监测各种指标,并且不断地进行提升。
**互联网服务架构的优化**很容易理解。互联网业务复杂了,公司内部的各种支撑服务和架构,也就需要不断优化。要做好这种工作,需要你对公司的各种服务性质和功能足够了解才行。
同时,不要小看了**性能相关的工具开发**多数的性能工作包括监控、检测、分析等总是需要一些相关的工具和UI界面面板的。这些工具和面板也需要去开发和维护。
最后一种,是**混合型**,就是综合了一部分前面说的六种工作。我举个例子,比如下面这个微软的性能工程师招聘广告,就是一个典型的混合型的例子。
<img src="https://static001.geekbang.org/resource/image/67/a0/670b4a043925fd17125c43c23969cfa0.jpg" alt="">
这个职位需要做性能测试,也需要做性能优化、性能诊断,并且开发相关的工具。
## 职业的发展前景:越老越吃香
了解过性能优化相关的工作后,你最关心的可能是——这种工作的发展前景如何?
说实话,在互联网行业工作,很多人担心的就是**年龄问题**。比如你经常看到的程序员招聘要求说必须多少岁以下比如35岁以下。在网上你是不是也常常看到各种消息说某某公司要清退多少岁以上的员工。对于互联网行业收入方面是风光无限但年龄是一个不能忍受的痛。
对于这种“年龄的痛”,很多人会说,年龄大了可以转岗,比如做管理。但是管理层毕竟人数更少,对多数人来讲,并不可行。而少数不容易遇到这类问题的互联网岗位中,就包括性能和容量工程。这个工作的特点我们也讨论了,它需要多方面的知识、各角度的技能和实际的经验积累,这需要相当长的时间,不太可能一蹴而就。因此,“年龄”并不会成为一个性能和容量工程师的短板。
我在很多硅谷公司工作过都是性能和容量工程的岗位。我的很多同事都是年龄比较大的常有超过40甚至50岁的。这些大龄同事往往是技术的牛人是挑大梁的角色。可以不夸张地说这一行业是“越老越吃香”的。
也许你会怀疑硅谷公司重用大龄的员工是出于怜悯之心,但事实并不是这样。事实上,这些“大龄员工”给公司带来了更大的收益。性能和容量工程师的工作,很大一部分内容,是帮助公司提升业务性能和容量效率,降低运营成本。随着公司业务规模的扩大,他们给公司降低的成本也越来越大,公司也越来越离不开他们。
还记得我开篇词中提到的电机专家斯坦门茨的故事吗?他的一个简单建议就帮助福特公司解决了一个超级大问题。在第一讲里面,我也举了一个同事的真实经历,一个架构师用几行代码优化系统,帮助公司节省了数百万美元。
这些年我自己也做了很多的性能优化和容量效率提升的工作我经常开玩笑说我给公司节省的成本早已经远远超过了公司付给我的工资没有100倍几十倍也是有的。
## 性能和容量工程师的工作:怎么去找?
说了这么多你可能会问怎么进入这一职业呢也就是如何去找这样的工作呢现在是互联网时代我们都知道要去招聘网站。无论你是否打算寻找海外工作我个人都比较建议你用LinkedIn。你也知道我曾经在LinkedIn工作过四年不过我可不是在帮LinkedIn做广告。虽然我对LinkedIn这个老东家印象非常好很喜欢它的文化但是我推荐LinkedIn是有别的原因。
我推荐LinkedIn的原因有三
1. LinkedIn在国外的工作人才市场中拥有垄断地位。不管是员工找工作还是公司找人一般都会用这个网站。
1. LinkedIn的定位就是针对比较专业和高端的人才市场。你找工作当然是想找一份好的工作所以用LinkedIn比较合适。
1. LinkedIn上的猎头多。如果你创建一个账号放上足够的信息很快就会不断地收到猎头的信件希望你去某某公司面试。我经常开玩笑说这种感觉不是“你在找工作”而是“工作在找你”。
当然你也可以主动用LinkedIn去搜寻合适的工作机会也可以适当的过滤比如根据工作地点和公司。比如下面这个截图就是我用“Performance Engineer”搜索出来的工作机会。
<img src="https://static001.geekbang.org/resource/image/ca/26/cab43bf65ec45b6d29df23161e71a026.jpg" alt="">
还有个建议,建好账号后,你一定要放上足够的和你相关的信息,尤其是专业的信息,否则就和没有建账号一样。
为什么呢这是因为猎头们都是用关键词和关键领域来搜索员工的。比如你要找性能工程的工作你就需要把你的相关工作经验和技能列上因为猎头可能会用如CPU Profiling、Performance Optimization等相关的词语去搜索候选人。
## 总结
我今天讲了性能和容量工程工作的特点、重要性,以及如何找这样的工作。
根据多年国内国外的观察和工作经验,我总体的感觉是,对这一方面的工作,真正了解的人非常少。甚至很多业界人员,都不知道有这样的工作。
我记得唐代诗人杨巨源的佳作《城东早春》中说:“诗家清景在新春,绿柳才黄半未匀。若待上林花似锦,出门俱是看花人。”说的是早春的时候,花开的还很少,这样的清新景色,正是诗人的最爱,适合出门赏花。若是等到晚春之际,虽然到处是花团锦簇,但满城也都是赏花的人,会拥挤不堪。 这一点,我们都有体会,国庆中秋出游过的人,对塞车拥挤都有很深的记忆。
一个行业和领域,如果处于发展期,内行不多,知名度不高,这自然是严峻的挑战,但也是巨大的机遇。对我们每个人而言,一个还没有很多人涉足的领域,恰恰是充满各种机会的沃土。我觉得性能工程和容量效率就是这样的一个领域,还有待我们去持续开拓。
## 思考题
你们公司里面有没有从事性能和容量工程相关的部门和人员呢?比如性能测试、性能优化、互联网服务效率提升、数据中心容量管理等。他们是在同一个部门还是分成不同的部门?他们的工作内容有没有明显的区分或者重合?
你觉得做这些工作的哪个部门和哪个工程师比较牛?牛在哪里?你可以向他们学习一下吗?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,118 @@
<audio id="audio" title="36 | 如何成为优秀的性能和容量工程师?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/51/c7/511a9b308b691b07ea9dfda38bcaa2c7.mp3"></audio>
你好,我是庄振运。
上一讲,我们探讨了性能工程师的职业特点和工作性质。今天我们就接着讨论如何才能成为优秀的性能工程师。
性能工程师的工作有些特殊,不同于一般程序员、运维以及测试。这一工作需要比较全面的知识、技能和经验。具体来说,需要软硬两方面的技能。
硬技能方面要有扎实而广泛的理论知识和丰富的实践经验。你不仅要了解计算机软件和硬件知识,还得具备性能测试、性能分析和性能优化的经验。
除了硬技能之外,软技能也同样重要。我们这一讲就重点聊一下这方面。我觉得软技能主要包括四个方面:
1. 英文和技术跟踪能力;
1. 多部门协调能力;
1. 项目驱动和领导能力;
1. 人际交往和沟通能力。
最后,我也会分享一些性能工程师的面试要求和经验供你参考。
## 英文和技术跟踪能力
英文是最流行的国际交流语言。互联网上的绝大多数资料是用英语写的,尤其是和互联网技术相关的资料。
在这个全球信息共享的时代网上有很多知识宝库还有大量的优质学习资源包括文章、视频、问题解答等。全球很多优秀的技术牛人和程序员开发的软件、库、工具、源代码也大都放在上面比如我们熟知的GitHub平台上面就有很多有用的代码和工程。而英语就是打开这些知识宝库的钥匙。
如果英文好的话你可以直接获取第一手的新技术资料并能快速地跟踪技术发展。在开发软件时遇到什么问题在Google上搜索一下一般都能找到答案。你碰到的技术问题大概率是别人早就碰到了的而且很可能已经有现成的解决方案了。于是很多情况下你只要复制粘贴就可以解决问题了。
对性能工程而言也是如此。毕竟很多的硬件比如CPU、软件Linux和Windows操作系统以及大量的开发库和应用程序都来自国外。要想知道这些最新硬件和软件的性能特性你就需要及时、直接地从国外的网站上获取。
所以,熟练的英文和相关的技术跟踪能力,对一个从事性能优化的工程人员而言至关重要。如果你能在这方面胜出一筹,那么你成为同行崇拜的技术牛人的可能性就更大。因为你可能知道别人不知道的,或者你可能会比别人知道得更早、更详细、更准确。
## 多部门协调能力
对于一个正常发展的公司而言,规模一般是会越来越大的,部门也会越来越多。在公司的业务变得复杂之后,几乎任何工作都需要和其他部门打交道,需要对方的配合,所以拥有多部门协调能力非常重要。
做性能工程更是如此。性能优化的工作,总是需要和很多部门打交道。即使是简单的软件测试,也需要和软件开发部门、使用部门以及运维紧密合作。如果是系统优化和容量管理,打交道的部门就更多了。
牵扯的部门多了,就需要你有一定的协调能力。各个部门的利益和需求经常不同,甚至会有冲突和矛盾。这时候就需要我们来统筹考虑,把关系理顺,适当地调和利益,只有这样,才能把性能工程工作往前推进。
多部门协调的能力是你的职位越高就越需要。当你还是一个初级工程师时,你可以只埋头做领导安排的工作,可以不和别人合作。但是当你走到了高级工程师,甚至是架构师这样的高级职位时,你基本不可能靠自己单干完成任务。
## 项目驱动和领导能力
性能优化和容量管理的工作和其他工作一样,也是通过划分成一个一个的项目来推进执行的。很多项目中的问题,一开始并不是那么显而易见,这就需要我们来发现问题、分析问题的性质、思考问题的解决方法,并把这一问题转化为一个合适的工程项目。
做每个工程项目的时候,你还需要和管理层和合作团队充分沟通。在各方认可之后,一步步地推进执行,定期汇报进展,最后才能够完成预设目标,胜利完成项目。
这一整套的步骤体现的其实就是**项目驱动和领导能力**。如果你能在这个过程里面起主导作用,包括发现问题、提出方案、说服别人、实现方案并且验证成功,那么你就是真正的项目带头人。
在实际工作中,每个公司的部门分工不同,每个人的具体工作领域也不同。当项目复杂时,需要很多人参与,每个人可能只需要负责或者执行一个小的步骤或模块。所以,在这整个“端到端”并且互相影响的过程中,充分和其他部门和同事合作是很必要的。
## 人际交往和沟通能力
职场讲究效率,如果你不能进行有效的人际沟通,又怎么会有效率?所以,人际交往能力和沟通能力至关重要。遗憾的是,这些年我遇到了太多在这方面非常欠缺的人(尤其是工程师)。
人际间的沟通有很多种,比如需要和上级、下级和同事沟通,也需要和合作部门甚至客户沟通等等。有效的沟通有四个特点:要及时、要透明、要一致、要清楚。
所谓“及时”,你很容易理解,就是不要太早或太晚。“透明”就是不要私下甚至黑箱操作,尽量通过正式的途径沟通,做到有案可查。“一致”就是说,即使是对不同的人、在不同时间,信息也要尽量一致,不能互相矛盾,尤其是同一部门的人,要“用一个声音讲话”。“清楚”就是信息不要有含糊的地方——允许就是允许,不同意就是不同意,不要让别人误解。
和其他员工之间的人际关系你也要重视。俗话说“三分做事,七分做人”,讲的就是**人的因素**起到的作用。你的人际关系好,就会左右逢源,在处处有人帮忙的情况下,项目自然会进展顺利。反之,如果人见人厌,项目的推进就会处处受阻,很难把事情做成。平时我们所说的“情商”,很大部分就是在说这个人际关系的处理。
当然,你还需要一定的**演讲和写作能力**。我们总是需要把自己的东西讲给别人听或者写给别人看。不管是问题展示、进展汇报,还是项目总结,都需要你把事情讲清楚、写清楚。
## 面试
接着我说说面试,分享一下这个职业的面试要求和经验。我在这一行业学习和工作十几年了,在美国好几家公司工作和实习过。
我被面试过几十次,也面试过别人几百次。
面试的内容和我前面讲过的这一行业需要的知识技能是一致的,大体上就是硬技能和软技能这两类。我要强调这里容易出现的一个误区,那就是很多刚刚踏上职场的朋友经常认为只有硬技能才是所谓的“真本事”,并轻视甚至鄙视软技能,认为那是“虚头八脑的东西”,这样想是完全错误的。
那么我们要如何根据行业需要的知识模块来应对面试呢?虽然实际的面试不会严格地按这八个模块来考,而是会根据你面试公司的情况和具体的职位要求各有侧重,但是“万变不离其宗”,你只要了解这些模块的要求,就可以做到心里有数,无往而不胜了。
### 硬技能面试
性能和容量工程的硬技能面试就比较直白,很多模块的要求和普通程序员的面试并无本质区别,比如写代码和系统设计。但是由于这一工作的特殊性,我还是要给你讲一讲它的侧重点。
**写代码能力**不用我多解释,就是我们平时所说的“刷题”。往往是给你一个问题,让你用程序实现。和普通程序员面试模块的唯一区别是它的难度一般不会太高。
**性能优化**模块算是个特殊模块,一般的程序员和运维面试不会有这方面的要求。这个模块所涉及的知识范围非常广泛,我们[第4讲](https://time.geekbang.org/column/article/174462)到[第29讲](https://time.geekbang.org/column/article/193142)的内容都会涉及到。这些内容都需要你平时积累,是很难临时速成的。
和普通程序员面试类似,**系统设计**模块也会给你一个开放的问题,让你做出自己的设计。我的经验是,性能和容量工程方面的系统设计面试是有侧重点的,最后都会面向**互联网服务性能**和**容量规划**方面。比如给你一个场景,让你预测系统的流量和需要的容量。
**容量规划**模块就更是直接针对容量工程了。面试时会提出场景性问题,让你根据给定的条件,推导出所需要的系统容量。比如,给你一个社交网站场景,让你详细演算出每个数据中心需要多少服务器(参见[第32讲](https://time.geekbang.org/column/article/195572))。在计算时,一般要充分考虑季节性因素(比如春节、双十一)和灾难恢复的要求。
### 软技能面试
软技能方面的面试主要考察四个方面:部门协调能力、人际沟通能力、公司文化的匹配、员工的行为个性。
一般来讲,职位层次越高,对软技能的要求也越高,相应的面试比例也就越大。一个刚刚毕业的职场新人的面试中不会有太多的软技能要求。尤其是部门协调和人际沟通方面的面试,就算有,它的重要性也没有那么大。
但是,对于新人来说,行为个性方面的要求还是有的,毕竟一个员工如果和别人打交道都困难,谁会愿意和他做同事呢?又有谁愿意做他的老板呢?所以,千万别小看软技能,一个人的职场发展越往后走,软技能就越重要。好消息是,这方面的书籍资料的资源很多,并不难准备。
**员工行为个性**模块就是所谓的“Behavior Interview”通过要求面试者描述其过去某个工作或者生活经历的具体情况来了解此人各方面行为素质的特征。比如面试官可能会问能给我讲一个你成功说服他人接受你思路的例子吗又或者面试官会考察你的时间管理能力问你当很多任务同时进行时你如何规划安排自己的时间
**公司文化匹配**是评测一个人的个性特点是否和公司的文化等规定匹配。换句话说,就是这个人能不能在这个公司里面生存下去。如果一个公司的传统是周六加班,而面试者因为种种原因周末完全不能加班,那么这就是不匹配。
**部门协调能力**主要是看看面试者能不能有效地和其他部门合作,这就需要一定的领导能力和沟通表达能力了。比如,你发现前端客户上传图片的延迟突然增加了,诊断发现是后台服务的性能突然下降导致的。你作为负责这个项目的人,你如何去和后台部门协调?如果他们那边不配合,有各种各样的理由,你又要如何处理?
任何一个员工都是要和同事合作的,团队凝聚在一起才有战斗力。要有**人际沟通能力**指的就是需要员工能和别人交往沟通是所谓的“team player”。毕竟一个团队最不想看到的就是有所谓的“猪队友”不能和别人合作甚至会拖后腿。
如果你想升职,这些能力都是你必须具备的。记住一点,老板们决定是否给你升职的时候,更多的是考虑你是不是已经到了那个新岗位的要求,而不是先把你升上去,再让你去学。
## 总结
中国的互联网事业一直在高速前进。性能和容量工程相关的工作,也是这一事业相当重要的一部分,我们的工作会让公司的业务效率和性能不断地提升,也就是对社会做出了贡献。
这种工作需要软硬两方面的知识、技能和经验。若想在这一领域做得出色,你尤其要加强对英文、协调能力和情商方面的训练。古人云:天助自助者,天道酬勤。哪怕你天资有限,机遇不足,现在还做不了“第一名”,但是只要每天进步,不断超越自我,那么你其实就是“成功者”。
古人说:“泰山不让土壤,故能成其大;河海不择细流,故能就其深。”我自己也经常会想,我们每个人只要每天收获一点细流,日积月累就足以汇成奔腾的大河、宽阔的大海,实现辉煌的人生。
## 思考题
你平时的工作一定需要和别人以及别的组打交道,你在里面是什么地位?需要主动有效地沟通和协调吗?
如果你不断地升职,在每一个级别的岗位上,对这样的协调、沟通和学习能力有什么要求呢?你愿意现在就未雨绸缪吗?
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。

View File

@@ -0,0 +1,47 @@
<audio id="audio" title="结束语 | 不愁明月尽,自有夜珠来" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/06/59/06796bcbd759b6b15b90b288fb1e0059.mp3"></audio>
到今天,我们的专栏就正式谢幕了。衷心感谢你在这三个月里,能和我一起学习、探讨这一领域。
专栏写作的这段经历,对我自己而言,算是记忆深刻。我出国也十几年了,这么多年来很少有机会使用中文写作,自知中文有些退化。但是这个专栏,给了我一个挑战和机会,让我去总结过去的学习和工作。
我把这些知识和心得写出来,是希望它对你和其他朋友们有所帮助,对互联网的发展,尤其是对中国互联网的发展尽一份绵薄之力。若能如此,我和极客时间的编辑们在这半年里一起付出的时间和精力就值得了,我也就没有遗憾了。
其实这个专栏的结束,只是你我职业提升的开始。对你来说,如何继续提升呢?我在专栏里面讲过,性能优化和容量管理这方面的工作,需要比较广泛的技能——既需要技术知识方面的硬技能,也需要团队合作等方面的软技能。
## 锻炼你的软硬技能
关于硬技能,除了我在专栏里面讲过的,我还想说的是,这个领域的职业技术方面发展得很快,各种软件和工具会不断推陈出新。这就需要你持续不断地跟踪、学习,才能保持职场竞争力。
在这方面,我的经验是定期地浏览好的网站和博客,比如[Brendan D. Gregg的博客](http://www.brendangregg.com/)就有很多不错的内容性能优化领域。此外很多公司的工程博客也值得你一看。比如LinkedIn公司的[Engineering Blog](https://engineering.linkedin.com/blog)、Netflix公司的[Netflix TechBlog](https://netflixtechblog.com/)就写得很好。
如果你有条件也可以读些书和会议论文。经典的书籍就不用我重复了相信你都有所了解。而每个具体的专业领域比如数据库方面都有不错的会议比如VLDB、SIGMOD你如果工作的内容匹配也可以看看上面的论文相信它们会给你一些好的启发。
关于软技能方面,我想强调**情商**的重要性,尤其是在职场。“**职场生存学**”你一定也听说过一些。人是自私的生物,在职场上尤甚。技术出身的我们,也许情商方面有“先天欠缺”,所以工作中需要特别注意和其他同事的配合,避免在人际上和团队合作上出现意想不到的问题。
“长恨人心不如水,等闲平地起波澜。”
刘禹锡的《竹枝词》道出了他的职场感慨,你也可以慢慢体会这句话。
要想在职场上成功,必须尽量体会别人的意图和心态,考虑别人的出发点,才能互相愉快合作,努力达到共赢。我本人决不算是高情商的人,这些年来也学到了很多的教训,因此常常自省。之所以在这里提出来,也是希望你能避免这些坑。
## 在工作中不断学习
唐朝初期的时候,曾经举办过一次中央官员的“诗词大会”,由上官婉儿做裁判。官员们写的诗,经过层层筛选,最后只剩下大诗人沈佺期和宋之问的两首,都是上品,上官婉儿一度难以取舍。
最后宋之问被选为冠军。大家问为什么呢?上官婉儿当众评说:“沈宋二诗,功力悉敌”,但宋之问胜在气势和意境上。
沈佺期诗的最后两句是:“微臣雕朽质,羞睹豫章材”(这里的“豫章材”指的是青年才俊)。这两句虽然用词谦逊,但显得暮气沉沉,词句写完,感觉气势也用尽了。
再看人家宋之问的诗,最后两句是:“不愁明月尽,自有夜珠来。”虽然诗文结束了,但意犹未尽,气势尚在,细细品来,韵味无穷。
我和你回顾这个典故的意义何在呢?
我们的专栏有36讲一起学习了也差不多有3个月时间不算短了。但是要想成为一位好的性能工程师只看专栏的内容远远不够还需要你在工作中和工作外不断地学习。我希望专栏的内容能够起到抛砖引玉的作用启发和引导你去探索性能优化和容量效率这一领域。
[<img src="https://static001.geekbang.org/resource/image/f9/80/f9789d75066e2c363a8a8dc1bbe23880.png" alt="">](https://jinshuju.net/f/bu2Vcr)
最后,我邀请你花三分钟填写一下毕业结课问卷,希望你能在问卷里说出你的学习经历、感受和意见,毕竟专栏结课后的优化离不开你的反馈。
古人云:行百里者半于九十。何况我们才刚刚踏上征程呢?
任何征程都不会是一帆风顺的。学习顺利的时候,希望你戒骄戒躁,毕竟“长江后浪推前浪,浮事新人换旧人”。学习困难的时候,也不要灰心气馁,切记“长风破浪会有时,直挂云帆济沧海”!

View File

@@ -0,0 +1,10 @@
你好,我是庄振运。
《性能工程高手课》已经完结一段时间了。在这段时间里,我依然收到了很多用户的留言,很感谢你一直以来的认真学习和支持!
为了帮助你检验自己的学习效果,我特别给你准备了一套结课测试题。这套测试题共有 20 道题目包括9道单选题11道多选题满分 100 分,系统会自动评分。
点击下面按钮,马上开始测试吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=135&amp;exam_id=291)

View File

@@ -0,0 +1,14 @@
你好!
今天我们来公布一下《性能工程高手课》结课问卷的获奖用户名单。
在这里,首先要感谢各位同学给我们的反馈。当然,这些反馈里,有指出我们做得好的地方,也有指出我们可以继续优化的地方。
本着“对专栏的改进最有帮助”的原则,我们挑选了 3 位用户送出“极客时间Git/Redis超大鼠标垫”中奖名单如下
<img src="https://static001.geekbang.org/resource/image/e3/c3/e3a658a33d170614478f6f9edc91c0c3.jpg" alt="">
感谢以上 3 位同学提出的宝贵意见,也恭喜他们!
当然,专栏的结束是另一种开始,我们会和庄振运老师继续迭代、优化专栏内容,庄振运老师也会持续关注并回复你的留言,所以也希望你可以继续关注本专栏,并将你的问题或者建议,通过留言反馈给我们!