老李分享:性能优化的境界

老李分享:性能优化的境界

 

  这篇文章是关于网站性能优化体验的,性能优化是一个复杂的话题,牵涉的东西很是多,我只是按照个人理解列出了性能优化整个过程当中须要考虑的种种因素。点到为止,包含的内容以浅显的介绍为主,若是你有看法能告知我那再好不过了。不管如何,但愿阅读它的你有所收获。html

 

我眼中的网站性能问题都反映了一个网站的“Availability”(中文叫作可用性,可是这个翻译也不足够达意),以往个人认识是,这个网站若是所有或者部分不可用,那是功能问题,可是若是响应慢、负载差,这才是性能问题;但是后来我逐渐意识到,性能问题涵盖的范围更广,我还无法给出一个准肯定义,可是许多非业务逻辑错误引发的网站问题均可能能够算作性能问题,好比可扩展性差,好比单点故障问题。前端

 

在网站性能优化的最初阶段,也就是所谓的“第一重境界”,作局部的定位、分析和修正,考虑的仅仅是“优化”,这也是初涉性能优化问题的大多数人的认识。在问题发生之后,发现它和业务逻辑没有太大关系,就开始尝试寻找问题产生的缘由并加以解决。java

 

不管是网站无响应仍是响应缓慢,仍是响应曲线异常波动,好比,能够围绕CPU的使用问本身这样几个问题:程序员

 

  • 从CPU使用看系统是否繁忙?
  • 若是系统繁忙,系统在作什么,为何?(典型问题:HashMap不安全并发致使的死循环)
  • 若是系统空闲,那么瓶颈在哪里?(典型问题:IO无响应)
  • 若是响应波动,是否存在周期,周期是什么?(典型问题:链接迅速占满,每一周期批量超时断开一批)
  • 若是响应波动,性能到波谷时系统在作什么?
  • 是否有背景CPU使用?(即无压力下观察CPU的使用状况。典型问题:正执行的定时任务占用过多系统资源)

在这些问题中,状况虽然变幻无穷,简单地说,CPU的使用是核心,CPU使用率高,说明系统资源被充分利用,可能系统在实实在在地作事,反之,须要寻找其余瓶颈。经过结合进程、线程的快照,来初步肯定问题的范围。CPU使用率低的状况居多并且容易定位,只须要寻找其余的系统瓶颈;CPU占用率偏高的问题每每比较不容易定位,虽然也有一些办法。关于具体性能问题的定位技术,这里不着过多笔墨,后续有机会详细介绍。数据库

 

对于一个刚开始作性能优化的网站系统,下面的事情不妨都作一作,会有立竿见影的效果:apache

 

  • 对于使用的成熟的技术,技术社区、官方文档,每每会给出这种技术的白皮书或者优化指导,请参考。好比 Struts2的官方性能调优指南Java6性能优化白皮书
  • 平台和虚拟机调优。对于使用平台和虚拟机的项目来讲,这是必需要作的,一个JVM的参数能够对系统有显著的影响。好比Linux下链接管理的参数,JVM关于堆大小分布的参数等等。
  • 前端审查。这里的审查指的是经过Page speed、YSlow等工具,以及一些业界通用的法则和经验(好比yahoo的若干条前端性能优化法则)来评估现有页面的问题。

若是你须要系统的指导,不妨参考这张图(点此下载大图和mmap文件:Site_Performance_Practice_Road_Map):浏览器

 

从使用的工具上说,性能问题的定位很大程度上是面向操做系统、虚拟机系统的问题定位。从问题定位的时机上说,又能够分为:缓存

 

  • 截取型:截取系统某个层面的一个快照加以分析。好比一些堆栈切面和分析的工具,jstack、jmap、kill -三、MAT、Heap Analyser等。
  • 监控型:监视系统变化,甚至数据流向。好比JProfiler、JConsole、JStat、BTrace等等。
  • 验尸型:系统已经宕机了,可是留下了一些“罪证”,在过后来分析它们。最有名的就是JVM挂掉以后可能会留下的hs_err_pid.log,或者是生成的crash dump文件。

了解到这里,再给出这样几个常见问题定位的场景:安全

 

第一类:请求无响应,浏览器始终处于等待状态。性能优化

定位方法:kill -3或者jstack先分析线程堆栈,找到当前block的线程。

常见于:外部接口调用无返回或者网络IO阻塞无响应;死锁;死循环;……。

 

第二类:宕机,进程挂掉。

定位方法(这一类问题广泛比较难定位):

    (1)寻找hs_err_pidxxx.log这样的JVM日志

    (2)使用JVM参数在JVM crash时写入到dump文件中

    (3)catalina.out中寻找最后的日志

    (4)宕机前环境数据采集

常见于:JDK bug(数次遇到过JIT引发的这一类问题);调用dll的问题;……

 

第三类:请求响应时间长。

定位方法:kill -3或者jstack先分析线程堆栈,看线程大都停留在什么操做上面,再细化分析。

常见于: 内存不足,可见到连续的Full GC;网络拥塞;LoadRunner等压力客户端瓶颈;数据库瓶颈,可进一步分析DB快照;……

 

第四类:TPS低;TPS逐渐下降;TPS振荡幅度过大。

定位方法(这一类问题最多见,定位的方法也最复杂):

首先观察在压力增大时,CPU使用率可否上去,若是不能上去,寻找其余瓶颈:网络/内存/磁盘/……;CPU

使用率上去了,观察在无压力时,是否有背景CPU使用(例若有后台定时任务线程消耗了大量CPU资源),若是没有,那能够尝试JProfiler等工具结合线程分析、业务分析,寻找热点。

常见于:其余业务线程干扰;内存泄露;链接句柄用完;缓存命中率低下……

 

好,暂时说到这里,下面来看第二重境界。达到这重境界意味着已经可以跳出“过后优化”的局限了,在设计和编码的过程中,可以正式和全面地考虑性能的因素,好比:

 

  • 减小使用时间敏感的容器管理,而使用容量或数量敏感的容器管理。好比我往一个缓冲里面存放若干数据,一种设计是每10分钟flush入库一次,还有一种设计是数据到达10M大小的时候flush入库一次,一般状况下,你以为哪一个方案更可靠?
  • 线程的统一管理使用。个人经验是,10次对线程建立或者线程池的使用,每每就有5次是会出问题的。
  • 避免使用同步Ajax。同步Ajax会形成浏览器假死,直至响应返回。
  • 分析对同步、锁的使用。即使在一些有名的开源库中,咱们也不止一次发现过不合理的同步设计,N多数据,单一的全局同步块(这是一种性能设计层面上的“中心化”),结果它就成为了瓶颈,改动还不容易下手,很麻烦。

对于不成熟的团队,建议能安排有经验的程序员把关设计文档和编码中的性能问题,把常见的问题列出来参考学习。

 

达到第二重境界还有一个明显的特征,就是在软件流程的前中期就开始作性能目标的论证和性能问题的验证:

 

  • 性能切面分析。这指的是在系统设计初期,为了评估一个系统的性能表现,作出一个性能相似的系统原型,并对其作性能测试和评估,这时候由于性能问题而涉及到方案的变动,影响较小。据我所知,可以作到这一点的项目极少。在大多数团队中,依赖于架构师和掌握话语权的设计者依靠经验来避免性能问题带来的大的方案变动(或者,干脆摔一次跤,再进行痛苦的“重构”)。
  • 性能的自动化测试验证。这一步必须伴随着Coding进行才有较大的意义,以便尽早发现性能问题。
  • 设计和代码层面的评审。其实功能问题考虑得多、暴露得早,真正有危险的每每都是那些被忽视的非功能性问题,好比性能问题。

 

最后是第三重境界。达到这重境界的团队可以在早期规划构想阶段就将性能做为一个必备因素包含在内,这可不是随口说说的经验的估计,而是要有数据驱动的理论设计,好比作性能建模,根据市场大小、业务量、服务等级等等计算出性能的具体指标,而且在此要求下作合理的架构设计。

 

这里涉及的东西有不少,除了数据,还须要有大量的思考,对于一个网站来讲,不妨问问以下的问题:

 

  • 数据量会有多大,我该设计什么样的存储?一致性的要求又如何?
  • 实时性要求是怎么样的?用户能够接受多少时间的数据延迟?
  • 网站须要考虑到什么程度的可伸缩性?
  • 哪些流程的数据处理有性能风险,数据量是什么级别的?怎么解决这个问题?
  • 主要的业务时间消耗是怎样的,我须要设计怎样的业务流来知足?

全部的性能问题和其余一切非功能性问题同样,都是必定程度上的trade off,因此越优秀的设计者越须要思考,来规划这些问题的解决方案,在规划中由于性能问题而涉及到的因素有哪些,太多太多了。

而要解决这样在规划中就预料到的性能问题,也有许多内容值得讨论,下面列出一些供参考:

 

  • 一、集群组网:这是最基本的横向扩展的方式,把单节点的压力经过负载均衡分担到多个节点下,提升了系统负载能力的同时,亦提升了稳定性。
  • 二、反向代理:一个大型的互联网网站不能不引入反向代理对静态资源的处理,Servlet容器用来处理静态图像和文本是很是奢侈的,Apache、Nginx、Squid都是优秀的解决方案。
  • 三、页面静态化:互联网应用“缓存为王”,这多是数种方案中能带来惠利最明显的一种,经过静态页面的生成和访问,有效地下降了系统负载。Web2.0的应用缓存命中率一般要稍差。
  • 四、数据库优化:用户的访问难以知足了,数据库硬件设备的强化之外,从最基本的拆表、SQL调优,到纵向和横向的分库几乎成为必不可少的解决办法,或者更换廉价存储解决方案,使用NoSQL数据库等等。
  • 五、CDN:CDN指的是内容分发网络,经过网络的广域层面对用户需求的分担,避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,提升用户体验。
  • 六、分布式存储:海量信息的爆炸,须要廉价存储的解决方案,Web2.0的数据尤甚。分布式存储系统能够保证大吞吐量的数据读写和海量数据存储,实时性就显得不那么重要了。
  • 七、数据缓存:这里的数据缓存和页面缓存区分开,数据缓存一般包括持久层层面的缓存和外部接口调用的缓存,数据缓存能够减少各种I/O调用,增长用户响应的平均时间。
  • 八、功能性集群:初步的集群是对等的,这类集群方式简单可控;可是随着产品日益复杂化,用户访问压力日益增大,单纯的对等集群解决不了全部的问题,且产生大量冗余处理逻辑,使用功能性集群能够将完成不一样功能的节点规约在一块儿。
  • 九、页面分区:对一个大型网站,这是必不可少的。目的就是要进行页面静态化,并将动态和静态的区域分离开,以便在用户访问的时候,只作简单的聚合操做。
  • 十、页面片断的生成和页面的聚合相剥离:许多频繁访问的相对静态的页面片断一般只须要的定时或事件触发的状况下才生成一次,甚至能够放在系统压力较轻的夜间生成。用户每次请求时只须要将静态的页面片断聚合成一个完整的页面(亦须要添加上动态的部分)便可。
  • 十一、隔离:对复杂系统的隔离和备份主要是为了解决稳定性问题,保持每个单元的“简单”,化整为零,更容易将单元独立开发、产品化。
  • 十二、聚合方式的改进:引入高性能的服务端页面聚合方式(通过验证,常规SSI、ESI的性能存在缺陷);甚至客户端聚合:将展现模板送到客户端,再经过Ajax请求将JSON(或其它简单格式)数据流送到客户端,在客户端使用Ajax聚合出最终的页面来,好处在于将服务端的压力分担到客户端。
  • 1三、组件服务化:服务化的好处在于易于将组件的处理并行化,增长总体的响应速度。模式能够遵循SOA的方式,系统中使用高性能的ESB来进行服务编排和任务分派。

要达到第三重境界还要可以预测性能问题。这就须要成熟的监控体系,监控系统的变化,尽快作出反应。

 

好比国内发生了重大事件,用户量陡增,监控系统可以及时识别出用户量监控曲线一个很是明显的跳跃过程(好比持续事件超过某个值,且曲线斜率超过某个值),发出告警,而且自动扩容来应付潜在的风险。这些,都是创建在常规的业务运营数据收集基础之上的,而后须要作数据挖掘,给出关键点。

 

再好比互联网应用“缓存为王”。对于缓存的设计,甚至很大程度上决定了应用的成败(若是你颇有钱,靠大量的CDN这种很是规路线的另说,呵呵)。缓存的设计须要考虑到缓存的大小、分级、队列、命中率计算、生命周期、更新换页、数据分发、数据一致性和数据持久化等等问题,这些东西每每被不少只重视那些页面展现效果和功能的人所忽视,但若是你是优秀的设计者,你须要积累这些思考。

 

Think big。有这样一个真实的例子,咱们曾经发现页面模板的OGNL性能不高(两次反射之故),遂在项目中把大部分OGNL表达式都改为了EL表达式,花了不少时间精力,性能也确实提升了,可是能提升多少呢?大概只有30%,这是一种细水长流的改进,对系统的破坏性不大,可是收效也不足以使人沾沾自喜,还失去了一些OGNL的灵活性。以后,咱们换了一个思路,从大局入手,给页面划分区域,定制缓存框架,引入页面缓存能力,虽然整套方案有些复杂,可是这种架构上的进化,因为页面的生成或者部分生成直接命中了缓存文件,性能一下有了飞跃,提升了600%~800%。这就是Think big,从大处着想,见获得工程大块的结构,须要足够的视野、足够的经验和积累,能够带来显著的效果。

 

一般系统容量的设计都会要求到峰值容量以上,若是是像秒杀、抢购之类对性能要求很是高的系统,每每还存在一个问题:设计了这么大的容量,平时大部分时间业务量都比较小,这些资源浪费怎么办?(题外话:这大概也是Amazon涉足云存储和云计算的初始原因吧)

 

咱们来看这样一个在性能驱动下架构变迁发展的例子:

初期,只有简单的应用服务器和DB服务器分家,使用简单的Jetty容器,系统的瓶颈在DB侧。简单就是美,网站刚刚运营,能访问就是王道:

系统在发展中不断地演化。

有一天发现用户压力愈来愈大,终于没法承受了,系统屡屡到达崩溃的边缘,在现有硬件和架构条件下很难支撑现有的业务,作出了这样的改变:

在此次改变中,作了这几件重要的事情:

  • 一、引入了全页面的缓存。互联网应用缓存为王,全页面的缓存能够起到立竿见影的效果。
  • 二、把页面展示抽象成为“主题”,和页面数据分离开来。而且,为此,引入了“聚合”的概念,它为之后的进一步发展打下了一个伏笔。
  • 三、为了缓解数据库的瓶颈,使用了RAC方式作持久层的集群。
  • 四、对于JS、CSS、图片等几乎一成不变的静态资源,引入反向代理,优先处理。

网站继续安安静静地发展,悄悄地演化。

终于有一天,用户访问量激增,百万级的PV达到了,WEB2.0业务也增长进来,缓存的命中率愈来愈低,CPU成为了瓶颈,访问异常缓慢。这一次,又要动刀了:

这一次的架构重构作了这么几件重要的事情:

  • 一、静态资源(特别是可供下载的文件),使用CDN缓解压力。
  • 二、把请求拆分红主请求、异步数据请求和静态资源请求,其中主请求仅仅是获取页面不变的部分(模板+静态数据),动态的数据以异步JSON的方式获取,并在浏览器端使用JavaScript聚合。这一步把某些聚合操做放置到了客户端进行,缓解了服务端压力。
  • 三、真正将页面的聚合展示和页面的生成拆分开来,保证了用户响应是快速的。
  • 四、引入多层次缓存(内存中对象集合使用Memcached缓存,接口层面缓存报文,页面缓存缓存文件等),同时,对于层次的划分,容易将整个系统拆分红若干个子部件独立运做,简单、独立。
  • 五、数据库进一步拆分,读写分离。
  • 六、页面分块。这是大型Web2.0网站共有的特色,一个页面上每每总有那么一部分是固定不变的,这些部分应当能以页面片断的形式缓存到磁盘上,每次页面生成的时候只须要更关注变化的部分便可。

继续、继续……

访问量增加了几十倍,集群的服务器也第一次达到了三位数,系统不稳定,速度从新落下,问题定位也无比困难,一切又开始扑朔迷离起来。

这一次,不可避免地又作了架构上的调整,首要的目标,是以隔离解耦的方式增长系统稳定性,同时,更便于产品化管理:

  • 一、总体采用SOA方式布局,按照功能划分集群,而且每一个功能集群定义为一个“服务”,内部采用REST风格的接口访问服务。服务驱动和编制引擎(ESB角色)定时把能够提早生成的静态数据存放到共享存储上。
  • 二、清晰化聚合逻辑,静态的数据尽可能在服务端聚合完成,减小客户端数据请求的流量。
  • 三、引入NOSQL数据库和廉价存储,适当放弃一致性,为海量数据作妥协。
  • 四、开发核心业务功能包部署引擎(基于OSGi),对于业务的定制,只须要按照功能包定义的格式开发,完成后可作到不重启应用增长业务功能。

最后要说的是,如你所见,性能因素是一个网站系统发展的其中一个重要推进力,再细致的思考也难以兼容那么多未知的场景,不妨多在扩展性和兼容性上下下功夫,避免网站冷清痛苦,网站大热更痛苦。

相关文章
相关标签/搜索