1

性能是客观的指标,可以具体到响应时间、吞吐量等技术指标,同时也是主观的感受,用户的感受和工程师的感受不同,不同的用户感受也不同。

性能测试

性能测试指标

不同视角下有不同的性能标准,不同的标准有不同的性能测试指标,常用的指标有如下几个:

  • 响应时间:指应用执行一个操作需要的时间,包括从发出请求开始到收到最后响应数据所需要的时间。响应时间是系统最重要的性能指标,直观地反映了系统的“快慢”。
  • 并发数:指系统能够同时处理请求的数目,这个数字也反映了系统的负载特性。
  • 吞吐量:指单位时间内系统处理的请求数量,体现系统的整体处理能力。

性能测试方法

性能测试具体可细分为性能测试、负载测试、压力测试、稳定性测试。

性能测试是一个不断对系统增加访问压力,以获得系统性能指标、最大负载能力的过程。所谓的增加访问压力,在系统测试环境中,就是不断增加测试程序的并发请求数,一般说来,性能测试遵循如图所示的抛物线规律。

image.png

在开始阶段,随着并发请求数目的增加,系统使用较少的资源就达到较好的处理能力(a~b段),这一段是网站的日常运行区间,网站的绝大部分访问负载压力都集中在这一段区间,被称作性能测试,测试目标是评估系统性能是否符合需求及设计目标。

随着压力的持续增加,系统处理能力增加变缓,直到达到一个最大值(c点),这是系统的最大负载点,这一段被称作负载测试。测试目标是评估当系统因为突发事件超出日常访问压力的情况下,保证系统正常运行情况下能够承受的最大访问负载压力。

超过这个点后,再增加压力,系统的处理能力反而下降,而资源消耗却更多,直到资源消耗达到极限(d点),这个点可以看作是系统的崩溃点,超过这个点继续加大并发请求数目,系统不能再处理任何请求,这一段被称作压力测试,测试目标是评估可能导致系统崩溃的最大访问负载压力。

性能优化策略

如果系统存在性能问题,必须对请求经历的各个环节进行分析,排查可能出现性能瓶颈的地方,定位问题。

排查一个网站的性能瓶颈和排查一个程序的性能瓶颈的手法基本相同:检查请求处理的各个环节的日志,分析哪个环节响应时间不合理、超过预期;然后检查监控数据,分析影响性能的主要因素是内存、磁盘、网络、还是CPU,是代码问题还是架构设计不合理,或者系统资源确实不足。

定位产生性能问题的具体原因后,就需要进行性能优化,根据网站分层架构,可分为Web前端性能优化、应用服务器性能优化、存储服务器性能优化3大类。

Web前端性能优化

一般说来Web前端指网站业务逻辑之前的部分,包括浏览器加载、网站视图模型、图片服务、CDN服务等,主要优化手段有优化浏览器访问、使用反向代理、CDN等。

浏览器访问优化

减少http请求

HTTP协议是无状态的应用层协议,意味着每次HTTP请求都需要建立通信链路、进行数据传输,而在服务器端,每个HTTP都需要启动独立的线程去处理。这些通信和服务的开销都很昂贵,减少HTTP请求的数目可有效提高访问性能。

减少HTTP的主要手段是合并CSS、合并JavaScript、合并图片。将浏览器一次访问需要的JavaScript、CSS合并成一个文件,这样浏览器就只需要一次请求。图片也可以合并,多张图片合并成一张,如果每张图片都有不同的超链接,可通过CSS偏移响应鼠标点击操作,构造不同的URL。

使用浏览器缓存

对一个网站而言,CSS、JavaScript、Logo、图标这些静态资源文件更新的频率都比较低,如果将这些文件缓存在浏览器中,可以极好地改善性能。通过设置HTTP头中Cache-Control和Expires的属性,可设定是否开启浏览器缓存以及缓存时间。

静态资源文件变化需要及时应用到客户端浏览器,这种情况,可通过改变文件名实现,即生成一个新的JS文件并更新HTML文件中的引用。在更新静态资源时,应采用批量更新的方法,并有一定的间隔时间,以免用户浏览器突然大量缓存失效,集中更新缓存,造成服务器负载骤增、网络拥塞的情况。

启用压缩

在服务器端对文件进行压缩,在浏览器端对文件解压缩,可有效减少通信传输的数据量。但是压缩对服务器和浏览器产生一定的压力,在通信带宽良好,而服务器资源不足的情况下要权衡考虑

减少Cookie传输

一方面,Cookie包含在每次请求和响应中,太大的Cookie会严重影响数据传输,因此哪些数据需要写入Cookie需要慎重考虑,尽量减少Cookie中传输的数据量。

另一方面,对于某些静态资源的访问,如CSS、Script等,发送Cookie没有意义,可以考虑静态资源使用独立域名访问,避免请求静态资源时发送Cookie,减少Cookie传输的次数。

CDN加速

CDN(Content Distribute Network,内容分发网络)的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户以最快速度获取数据。

由于CDN部署在网络运营商的机房,这些运营商又是终端用户的网络服务提供商,因此用户请求路由的第一跳就到达了CDN服务器,当CDN中存在浏览器请求的资源时,从CDN直接返回给浏览器,加快用户访问速度,减少数据中心负载压力。

反向代理

反向代理服务器位于网站机房一侧,代理网站Web服务器接收HTTP请求。反向代理服务器具有保护网站安全的作用,除了安全功能,代理服务器也可以通过配置缓存功能加速Web请求。

当用户第一次访问静态内容的时候,静态内容就被缓存在反向代理服务器上,这样当其他用户访问该静态内容的时候,就可以直接从反向代理服务器返回,加速Web请求响应速度。有些网站会把某些热门的动态内容也缓存在代理服务器上,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容再次缓存起来。

此外,反向代理也可以实现负载均衡的功能,进而改善网站高并发情况下的性能。

应用服务器性能优化

应用服务器就是处理网站业务的服务器,网站的业务代码都部署在这里,优化手段主要有缓存、集群、异步等。

缓存

性能优化第一定律:优先考虑使用缓存优化性能。缓存指将数据存储在相对较高访问速度的存储介质中,以供系统处理。

使用策略

使用缓存的首要前提就是存在热点数据,如果不存在热点访问,那么会出现大部分数据还没有被再次访问就被挤出缓存。而且需要缓存的数据读写比必须足够高,数据的读写比至少应该在2:1以上,缓存才有意义。

数据不一致

一般会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载。因此应用要容忍一定时间的数据不一致。在互联网应用中,这种延迟通常是可以接受的,但是具体应用仍需慎重对待。还有一种策略是数据更新时立即更新缓存,不过这也会带来更多系统开销和事务一致性的问题。

缓存预热

新启动的缓存系统如果没有任何数据,在构建缓存数据的过程中,系统的性能和数据库负载都不太好,那么最好在缓存系统启动时就把热点数据加载好,这个缓存预加载手段叫作缓存预热。

缓存可用性

随着业务的发展,缓存会承担大部分数据访问的压力,当缓存服务崩溃时,数据库会因为完全不能承受如此大的压力而宕机,进而导致整个网站不可用。

通过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在一定程度上改善缓存的可用性。当一台缓存服务器宕机的时候,只有部分缓存数据丢失,重新从数据库加载这部分数据不会对数据库产生很大影响。

缓存穿透

如果因为不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,由于缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大压力,甚至崩溃。一个简单的对策是将不存在的数据(null)也缓存起来,亦或是使用布隆过滤器。

异步

使用消息队列将调用异步化,可改善系统的扩展性,同时也可以改善系统性能。由于消息队列服务器处理速度远快于数据库(消息队列服务器也比数据库具有更好的伸缩性),用户的响应延迟可得到有效改善,并且可以降低数据库的负载压力。

消息队列具有很好的削峰作用——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。在电子商务网站促销活动中,合理使用消息队列,可有效抵御促销活动刚开始大量涌入的订单对系统造成的冲击。

需要注意的是,由于数据写入消息队列后立即返回给用户,数据在后续的业务校验、写数据库等操作可能失败,因此在使用消息队列进行业务异步处理后,需要适当修改业务流程进行配合。如订单提交后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单,甚至商品出库后,再通过电子邮件或SMS消息通知用户订单成功,以免交易纠纷。

注:任何可以晚点做的事情都应该晚点再做。

集群

在高并发访问的场景下,使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,将并发访问请求分发到多台服务器上处理,避免单一服务器因负载压力过大而响应缓慢,使用户请求具有更好的响应延迟特性。

代码优化

理优化业务代码,可以很好地改善系统性能。代码优化手段有很多,这里我们概要地关注比较重要的几个方面。

多线程

多用户并发访问是应用系统的基本需求,大型网站的并发用户数会达到数万,单台服务器的并发用户也会达到数百。从资源利用的角度看,使用多线程的原因主要有两个:IO阻塞与多CPU。

网站的应用程序一般都被Web服务器容器管理,用户请求的多线程也通常被Web服务器容器管理,但不管是Web容器管理的线程,还是应用程序自己创建的线程,一台服务器上启动多少线程合适呢?假设服务器上执行的都是相同类型任务,针对该类任务启动的线程数有个简化的估算公式可供参考:启动线程数 = [任务执行时间 /(任务执行时间-IO等待时间)] × CPU内核数。

多线程编程一个需要注意的问题是线程安全问题,也即同步问题。解决线程安全的主要手段有使用无状态对象和局部对象(不共享数据)以及使用锁等。

资源复用

系统运行时,要尽量减少那些开销很大的系统资源的创建和销毁,比如数据库连接、网络通信连接、线程、复杂对象等。从编程角度,资源复用主要有两种模式:单例(Singleton)和对象池(ObjectPool)。

单例虽然是GoF经典设计模式中被较多诟病的一个模式,但由于目前Web开发中主要使用贫血模式,从Service到Dao都是无状态对象,无需重复创建,这种情况下使用单例模式也就很自然了。

对象池模式通过复用对象实例,减少对象创建和资源消耗。所谓的连接池、线程池,本质上都是对象池,池管理方式也基本相同。

垃圾回收

现在的主力编程语言如Java、PHP、Golang等都具有自动垃圾回收功能,垃圾回收可能会对系统的性能特性产生巨大影响。理解所使用的编程语言的垃圾回收机制有助于程序优化和参数调优,以及编写内存安全的代码。

存储性能优化

在应用系统中,海量的数据读写对磁盘访问造成巨大压力,虽然可以通过Cache解决一部分数据读压力,但是磁盘仍然是系统最严重的瓶颈。

机械硬盘和固态硬盘

机械硬盘是目前最常用的一种硬盘,通过马达驱动磁头臂,带动磁头到指定的磁盘位置访问数据。机械硬盘在数据连续访问和随机访问时,性能差别非常大。

固态硬盘没有机械装置,数据存储在可持久记忆的硅晶体上,因此可以像内存一样快速随机访问。在应用系统中,大部分数据访问都是随机的,这种情况下SSD具有更好的性能表现。

B+树和LSM树

为了改善数据访问特性,文件系统或数据库系统通常会对数据排序后存储,加快数据检索速度,这就需要保证数据在不断更新、插入、删除后依然有序,传统关系数据库的做法是使用B+树,如图所示。

image.png

B+树是一种专门针对磁盘存储而优化的N叉排序树,以树节点为单位存储在磁盘中,从根开始查找所需数据所在的节点编号和磁盘位置,将其加载到内存中然后继续查找,直到找到所需的数据。

目前许多NoSQL产品采用LSM树作为主要数据结构,如图所示。

image.png

LSM树可以看作是一个N阶合并树。数据写操作都在内存中进行,并且都会创建一个新记录(修改会记录新的数据值,而删除会记录一个删除标志),这些数据在内存中仍然还是一棵排序树,当数据量超过设定的内存阈值后,会将这棵排序树和磁盘上最新的排序树合并。当这棵排序树的数据量也超过设定阈值后,和磁盘上下一级的排序树合并。合并过程中,会用最新更新的数据覆盖旧的数据(或者记录为不同版本)。

在需要进行读操作时,总是从内存中的排序树开始搜索,如果没有找到,就从磁盘上的排序树顺序查找。

在LSM树上进行一次数据更新不需要磁盘访问,在内存即可完成,速度远快于B+树。当数据访问以写操作为主,而读操作则集中在最近写入的数据上时,使用LSM树可以极大程度地减少磁盘的访问次数,加快访问速度。

延伸阅读:【分布式—基础】数据存储与检索

RAID

RAID(廉价磁盘冗余阵列)技术主要是为了改善磁盘的访问延迟,增强磁盘的可用性和容错能力。目前服务器级别的计算机都支持插入多块磁盘,通过使用RAID技术,实现数据在多块磁盘上的并发读写和数据备份。常用RAID技术有以下几种,如图所示。

image.png

RAID 0

数据在从内存缓冲区写入磁盘时,根据磁盘数量将数据分成N份,这些数据同时并发写入N块磁盘,使得数据整体写入速度是一块磁盘的N倍。读取时也一样,因此RAID0具有极快的数据读写速度,但是RAID0不做数据备份,N块磁盘中只要有一块损坏,数据完整性就被破坏,所有磁盘的数据都会损坏。

RAID 1

数据在写入磁盘时,将一份数据同时写入两块磁盘,这样任何一块磁盘损坏都不会导致数据丢失,插入一块新磁盘就可以通过复制数据的方式自动修复,具有极高的可靠性。

RAID 10

结合RAID0和RAID1两种方案,将所有磁盘平均分成两份,数据同时在两份磁盘写入,相当于RAID1,但是在每一份磁盘里面的N/2块磁盘上,利用RAID0技术并发读写,既提高可靠性又改善性能,不过RAID10的磁盘利用率较低,有一半的磁盘用来写备份数据。

RAID 3

一般情况下,一台服务器上不会出现同时损坏两块磁盘的情况,在只损坏一块磁盘的情况下,如果能利用其他磁盘的数据恢复损坏磁盘的数据,这样在保证可靠性和性能的同时,磁盘利用率也得到大幅提升。

在数据写入磁盘的时候,将数据分成N-1份,并发写入N-1块磁盘,并在第N块磁盘记录校验数据,任何一块磁盘损坏(包括校验数据磁盘),都可以利用其他N-1块磁盘的数据修复。

但是在数据修改较多的场景中,修改任何磁盘数据都会导致第N块磁盘重写校验数据,频繁写入的后果是第N块磁盘比其他磁盘容易损坏,需要频繁更换,所以RAID3很少在实践中使用。

RAID 5

RAID5和RAID3很相似,但是校验数据不是写入第N块磁盘,而是螺旋式地写入所有磁盘中。这样校验数据的修改也被平均到所有磁盘上,避免RAID3频繁写坏一块磁盘的情况。

RAID 6

如果数据需要很高的可靠性,在出现同时损坏两块磁盘的情况下,仍然需要修复数据,这时候可以使用RAID6。RAID6和RAID5类似,但是数据只写入N-2块磁盘,并螺旋式地在两块磁盘中写入校验信息(使用不同算法生成)。

各种RAID技术的比较如下表所示。

RAID类型访问速度数据可靠性磁盘利用率
RAID0很快很低100%
RAID1很慢很高50%
RAID10中等很高50%
RAID5较快较高(N - 1)/ N
RAID6较快较高(N - 2)/ N

RAID技术可以通过硬件实现,比如专用的RAID卡或者主板直接支持,也可以通过软件实现。RAID技术在传统关系数据库及文件系统中应用比较广泛,但是在大型网站比较喜欢使用的NoSQL,以及分布式文件系统中,RAID技术却遭到冷落。


与昊
225 声望636 粉丝

IT民工,主要从事web方向,喜欢研究技术和投资之道