对于网站性能优化经验总结.docx
《对于网站性能优化经验总结.docx》由会员分享,可在线阅读,更多相关《对于网站性能优化经验总结.docx(15页珍藏版)》请在冰豆网上搜索。
对于网站性能优化经验总结
对于网站性能优化的经验总结
性能优化涉及面很广。
一般而言,性能优化指降低响应时间和提高系统吞吐量两个方面,但在流量高峰时候,性能问题往往会表现为服务可用性下降,所以性能优化也可以包括提高服务可用性。
在某些情况下,降低响应时间、提高系统吞吐量和提高服务可用性三者相互矛盾,不可兼得。
例如:
增加缓存可以降低平均响应时间,但是处理线程数量会因为缓存过大而有所限制,从而降低系统吞吐量;为了提高服务可用性,对异常请求重复调用是一个常用的做法,但是这会提高响应时间并降低系统吞吐量。
对于很多像美团这样的公司,它们的系统会面临如下三个挑战:
1.日益增长的用户数量,2.日渐复杂的业务,3.急剧膨胀的数据。
这些挑战对于性能优化而言表现为:
在保持和降低系统TP95响应时间(指的是将一段时间内的请求响应时间从低到高排序,高于95%请求响应时间的下确界)的前提下,不断提高系统吞吐量,提升流量高峰时期的服务可用性。
这种场景下,三者的目标和改进方法取得了比较好的一致。
本文主要目标是为类似的场景提供优化方案,确保系统在流量高峰时期的快速响应和高可用。
文章第一部分是介绍,包括采用模式方式讲解的优点,文章所采用案例的说明,以及后面部分用到的一些设计原则;第二部分介绍几种典型的"性能恶化模式",阐述导致系统性能恶化,服务可用性降低的典型场景以及形成恶化循环的过程;第三部分是文章重点,阐述典型的"性能优化模式",这些模式或者可以使服务远离"恶化模式",或者直接对服务性能进行优化;文章最后一部分进行总结,并对未来可能出现的新模式进行展望。
介绍
模式讲解方式
关于性能优化的文章和图书已有很多,但就我所知,还没有采用模式的方式去讲解的。
本文借鉴《设计模式》("DesignPatterns-ElementsofReusableObject-OrientedSoftware")对设计模式的阐述方式,首先为每一种性能优化模式取一个贴切的名字,便于读者快速理解和深刻记忆,接着讲解该模式的动机和原理,然后结合作者在美团的具体工作案例进行深度剖析,最后总结采用该模式的优点以及需要付出的代价。
简而言之,本文采用"命名-->原理和动机-->具体案例-->缺点和优点"的四阶段方式进行性能优化模式讲解。
与其他方式相比,采用模式进行讲解有两个方面的优点:
一方面,读者不仅仅能够掌握优化手段,而且能够了解采用该手段进行性能优化的场景以及所需付出的代价,这有利于读者全面理解和灵活应用;另一方面,模式解决的是特定应用场景下的一类问题,所以应用场景描述贯穿于模式讲解之中。
如此,即使读者对原理不太了解,只要碰到的问题符合某个特定模式的应用场景(这往往比理解原理要简单),就可以采用对应的手段进行优化,进一步促进读者对模式的理解和掌握。
案例说明
文章的所有案例都来自于美团的真实项目。
出于两方面的考虑,作者做了一定的简化和抽象:
一方面,系统可以优化的问题众多,而一个特定的模式只能解决几类问题,所以在案例分析过程中会突出与模式相关的问题;另一方面,任何一类问题都需要多维度数据去描述,而应用性能优化模式的前提是多维度数据的组合值超过了某个临界点,但是精确定义每个维度数值的临界点是一件很难的事情,更别说多维度数据组合之后临界点。
因此有必要对案例做一些简化,确保相关取值范围得到满足。
基于以上以及其他原因,作者所给出的解决方案只是可行性方案,并不保证其是所碰到问题的最佳解决方案。
案例涉及的所有项目都是基于Java语言开发的,严格地讲,所有模式适用的场景是基于Java语言搭建的服务。
从另外一方面讲,Java和C++的主要区别在于垃圾回收机制,所以,除去和垃圾回收机制紧密相关的模式之外,文章所描述的模式也适用于采用C++语言搭建的服务。
对于基于其他语言开发的服务,读者在阅读以及实践的过程中需要考虑语言之间的差别。
设计原则
必须说明,本文中各种模式所要解决的问题之所以会出现,部分是因为工程师运用了某些深层次的设计原则。
有些设计原则看上去和优秀的设计理念相悖,模式所解决的问题似乎完全可以避免,但是它们却被广泛使用。
"存在即合理",世界上没有完美的设计方案,任何方案都是一系列设计原则的妥协结果,所以本文主要关注点是解决所碰到的问题而不是如何绕过这些设计原则。
下面对文中重要的设计原则进行详细阐述,在后面需要运用该原则时将不再解释。
最小可用原则
最小可用原则(快速接入原则)有两个关注点:
1.强调快速接入,快速完成;2.实现核心功能可用。
这是一个被普遍运用的原则,其目标是缩短测试周期,增加试错机会,避免过度设计。
为了快速接入就必须最大限度地利用已有的解决方案或系统。
从另外一个角度讲,一个解决方案或系统只要能够满足基本需求,就满足最小可用原则的应用需求。
过度强调快速接入原则会导致重构风险的增加,原则上讲,基于该原则去设计系统需要为重构做好准备。
经济原则
经济原则关注的是成本问题,看起来很像最小可用原则,但是它们之间关注点不同。
最小可用原则的目标是通过降低开发周期,快速接入而实现风险可控,而快速接入并不意味着成本降低,有时候为了实现快速接入可能需要付出巨大的成本。
软件项目的生命周期包括:
预研、设计、开发、测试、运行、维护等阶段。
最小可用原则主要运用在预研阶段,而经济原则可以运用在整个软件生命周期里,也可以只关注某一个或者几个阶段。
例如:
运行时经济原则需要考虑的系统成本包括单次请求的CPU、内存、网络、磁盘消耗等;设计阶段的经济原则要求避免过度设计;开发阶段的经济原则可能关注代码复用,工程师资源复用等。
代码复用原则
代码复用原则分为两个层次:
第一个层次使用已有的解决方案或调用已存在的共享库(SharedLibrary),也称为方案复用;第二个层次是直接在现有的代码库中开发,也称之为共用代码库。
方案复用是一个非常实用主义的原则,它的出发点就是最大限度地利用手头已有的解决方案,即使这个方案并不好。
方案的形式可以是共享库,也可以是已存在的服务。
方案复用的例子参见避免蚊子大炮模式的具体案例。
用搜索引擎服务来解决查找附近商家的问题是一个性能很差的方案,但仍被很多工程师使用。
方案复用原则的一个显著优点就是提高生产效率,例如:
Java之所以能够得到如此广泛应用,原因之一就是有大量可以重复利用的开源库。
实际上"Writeonce,runanywhere"是Java语言最核心的设计理念之一。
基于Java语言开发的代码库因此得以在不同硬件平台、不同操作系统上更广泛地使用。
共用代码库要求在同一套代码库中完成所有功能开发。
采用这个原则,代码库中的所有功能编译时可见,新功能代码可以无边界的调用老代码。
另外,原代码库已存在的各种运行、编译、测试、配置环境可复用。
主要有两个方面地好处:
1.充分利用代码库中已有的基础设施,快速接入新业务;2.直接调用原代码中的基础功能或原語,避免网络或进程间调用开销,性能更佳。
共用代码库的例子参见垂直分割模式的具体案例。
从设计的角度上讲,方案复用类似于微服务架构(MicroserviceArchitecture,有些观点认为这是一种形式的SOA),而共用代码库和MonolithicArchitecture很接近。
总的来说,微服务倾向于面向接口编程,要求设计出可重用性的组件(Library或Service),通过分层组织各层组件来实现良好的架构。
与之相对应,MonolithArchitecture则希望尽可能在一套代码库中开发,通过直接调用代码中的基础功能或原語而实现性能的优化和快速迭代。
使用MonolithArchitecture有很大的争议,被认为不符合"设计模式"的理念。
参考文献[4],MonolithicDesign主要的缺点包括:
1.缺乏美感;2.很难重构;3.过早优化(参见文献[6]Optimizejudiciously);4.不可重用;5.限制眼界。
微服务架构是很多互联网公司的主流架构,典型的运用公司包括Amazon、美团等。
MonolithicArchitecture也有其忠实的粉丝,例如:
Tripadvisor的全球网站就共用一套代码库;基于性能的考虑,Linux最终选择的也是Monolithickernel的模式。
奥卡姆剃刀原则
系统设计以及代码编写要遵循奥卡姆剃刀原则:
Entitiesshouldnotbemultipliedunnecessarily。
一般而言,一个系统的代码量会随着其功能增加而变多。
系统的健壮性有时候也需要通过编写异常处理代码来实现。
异常考虑越周全,异常处理代码量越大。
但是随着代码量的增大,引入Bug的概率也就越大,系统也就越不健壮。
从另外一个角度来讲,异常流程处理代码也要考虑健壮性问题,这就形成了无限循环。
所以在系统设计和代码编写过程中,奥卡姆剃刀原则要求:
一个功能模块如非必要,就不要;一段代码如非必写,就不写。
奥卡姆剃刀原则和最小可用原则有所区别。
最小可用原则主要运用于产品MVP阶段,本文所指的奥卡姆剃刀原则主要指系统设计和代码编写两个方面,这是完全不同的两个概念。
MVP包含系统设计和代码编写,但同时,系统设计和代码编写也可以发生在成熟系统的迭代阶段。
性能恶化模式
在讲解性能优化模式之前,有必要先探讨一下性能恶化模式,因为:
很多性能优化模式的目标之一就是避免系统进入性能恶化模式;
不同性能优化模式可能是避免同一种性能恶化模式;
同一种性能优化模式可能在不同阶段避免不同的性能恶化模式。
在此统一阐述性能恶化模式,避免下文重复解释。
为了便于读者清晰识别恶化模式和优化模式,恶化模式采用"XXX反模式"的方式进行命名。
长请求拥塞反模式(HighLatencyInvocatingAntiPattern)
这是一种单次请求时延变长而导致系统性能恶化甚至崩溃的恶化模式。
对于多线程服务,大量请求时间变长会使线程堆积、内存使用增加,最终可能会通过如下三种方式之一恶化系统性能:
线程数目变多导致线程之间CPU资源使用冲突,反过来进一步延长了单次请求时间;
线程数量增多以及线程中缓存变大,内存消耗随之剧增,对于基于Java语言的服务而言,又会更频繁地fullGC,反过来单次请求时间会变得更长;
内存使用增多,会使操作系统内存不足,必须使用Swap,可能导致服务彻底崩溃。
典型恶化流程图如下图:
长请求拥塞反模式所导致的性能恶化现象非常普遍,所以识别该模式非常重要。
典型的场景如下:
某复杂业务系统依赖于多个服务,其中某个服务的响应时间变长,随之系统整体响应时间变长,进而出现CPU、内存、Swap报警。
系统进入长请求拥塞反模式的典型标识包括:
被依赖服务可用性变低、响应时间变长、服务的某段计算逻辑时间变长等。
多次请求杠杆反模式(LeveredMultilayerInvocatingAntiPattern)
客户端一次用户点击行为往往会触发多次服务端请求,这是一次请求杠杆;每个服务端请求进而触发多个更底层服务的请求,这是第二次请求杠杆。
每一层请求可能导致一次请求杠杆,请求层级越多,杠杆效应就越大。
在多次请求杠杆反模式下运行的分布式系统,处于深层次的服务需要处理大量请求,容易会成为系统瓶颈。
与此同时,大量请求也会给网络带来巨大压力,特别是对于单次请求数据量很大的情况,网络可能会成为系统彻底崩溃的导火索。
典型恶化流程图如下图:
多次请求杠杆所导致的性能恶化现象非常常见,例如:
对于美团推荐系统,一个用户列表请求会有多个算法参与,每个算法会召回多个列表单元(商家或者团购),每个列表单元有多种属性和特征,而这些属性和特征数据服务又分布在不同服务和机器上面,所以客户端的一次用户展现可能导致了成千上万的最底层服务调用。
对于存在多次请求杠杆反模式的分布式系统,性能恶化与流量之间往往遵循指数曲线关系。
这意味着,在平常流量下正常运行服务系统,在流量高峰时通过线性增加机器解决不了可用性问题。
所以,识别并避免系统进