性能测试:聊聊性能优化模式

我个人有收藏感兴趣的技术链接的习惯,最近太忙,没太多时间看收藏的技术贴,难得今天有空,看了篇美团技术团队的关于性能优化的内容,

感觉不错,将其中的一些观点和方法做了总结归纳,其中还掺杂一些个人的思考,写下这篇博客,以备日后查阅。。。

原文链接:性能优化模式

 

、性能优化的三个方面

1、降低响应时间

2、提高系统吞吐量

3、提高服务的可用性

三者的关系:在某些场景下互相矛盾,不可兼得

 

二、性能优化面临的挑战

1、日益增长的用户数量

2、越来越复杂的业务

3、急剧膨胀的数据

 

三、性能优化的目标

在保持和降低系统99%RT的前提下,不断提高系统吞吐量,提高流量高峰时期的服务可用性。

 

四、本文涉及的几种原则

1、最小可用原则

也称为快速接入原则,有两个关注点:

①、快速接入,快速完成;

②、实现核心功能可用;

目标:通过降低开发测试周期,增加试错机会,快速接入而实现风险可控;

注意:这并不意味着成本的降低,而需要为重构做好准备;

相关阅读:在产品设计上有一个名词叫做MVP,即最小可行性产品,这一观点在《人人都是产品经理1.0纪念版》一书中有相关章节描述。

2、经济原则

软件项目生命周期包括:预研、设计、开发、测试、运行、维护等阶段。

上面提到的最小可用原则主要运用在预研阶段,而经济原则既可以用在整个软件生命周期里,或只关注某一个或者几个阶段;

例如:运行时经济原则需要考虑的系统成本包括单次请求的CPU、内存、网络、磁盘消耗等;

设计阶段的经济原则要求避免过度设计;开发阶段的经济原则可能关注代码复用,工程师资源复用等。

3、代码复用原则

该原则分两个层次:

①、使用已有的解决方案或调用已存在的共享库(Shared Library),也称为方案复用

②、直接在现有的代码库中开发,也称之为共用代码库

方案复用原则出发点就是最大限度地利用手头已有的解决方案,即使这个方案并不好;方案的形式可以是共享库,也可以是已存在的服务。

优点:提高生产效率;

类似:方案复用类似于微服务架构(Microservice Architecture),restful风格,设计出可重用性的组件或服务,通过分层组织各层组件来实现良好的架构。

共用代码库原则要求代码库中的所有功能编译时可见,新功能代码可以无边界的调用老代码;另外,原代码库已存在的各种运行、编译、测试、配置环境可复用。

优点:

①、充分利用代码库中已有的基础设施,快速接入新业务;

②、直接调用原代码中的基础功能,避免网络或进程间调用开销,性能更佳;

类似:共用代码库和整体架构(Monolithic Architecture)很接近,它希望尽可能在一套代码库中开发,通过直接调用代码中的基础功能实现性能优化和快速迭代。

4、奥卡姆剃刀原则

一般情况下,系统的代码量会随着功能的增加而变多,健壮性有时候也需要通过编写异常处理代码来实现,异常考虑越全面,异常处理的代码量就越大。

随着代码量的增大,引入BUG的概率也越大,系统也就越不健壮。从另一个方面来说,异常流程处理代码也要考虑健壮性的问题,这就形成了无限循环。

奥卡姆剃刀原则要求

①、一个功能模块如非必要,就不要;

②、一段代码如非必写,则不写;

区别:最小可用原则主要运用于产品MVP阶段,奥卡姆剃刀原则主要指系统设计和代码编写两个方面,这是完全不同的两个概念;

MVP包含系统设计和代码编写,但同时,系统设计和代码编写也可以发生在成熟系统的迭代阶段。

 

、性能恶化模式

性能优化的目标之一就是避免系统进入性能恶化模式;

不同性能优化模式可能是避免同一种性能恶化模式;

同一种性能优化模式可能在不同阶段避免不同的性能恶化模式;

常见的性能恶化模式

1、长请求拥塞恶化模式

介绍:单次请求时延变长导致系统性能恶化甚至崩溃的模式;

典型场景:复杂的业务场景依赖于多个服务,其中某个服务的响应时间变长,随之系统整体响应时间变长,进而出现CPU、内存、Swap报警;

具体表现:被依赖服务可用性降低、大量RT变长导致线程堆积、内存使用增加,频繁GC,RT时间变得更长,最终导致服务彻底崩溃;

表现方式

线程数变多导致县城之间CPU资源竞争,反过来进一步延长了单次请求时间;

线程数增多及线程中缓存变大,内存消耗加剧,java语言的服务会频繁的full GC(垃圾回收),导致单次请求时间变得更长;

内存使用变多,使操作系统内存不足,必须使用Swap(内存)交换,可能导致服务崩溃;

恶化流程图如下

2、反复缓存恶化模式

介绍:为降低响应时间,加缓存是种很有效的方式,缓存数据越多,命中率越高,ART就越快;

典型场景:流量高峰冲击时,系统内存使用增多,出发了JVM进行full GC,进而导致大量缓存被释放,而大量请求又使得缓存被迅速填满,

反复缓存导致频繁的full GC,频繁的full GC往往导致系统性能急剧恶化;

原因:反复缓存所导致性能恶化的原因是无节制的使用缓存;

指导原则:全局考虑,精细规划,确保系统完全缓存情况下系统仍然不会频繁full GC,严格控制缓存大小,甚至废除缓存;

解决措施:线性的增加机器和提高机器的内存大小,可以显著减少系统崩溃的概率;

恶化流程图如下:

3、多次请求杠杆恶化模式

介绍:客户端一次点击往往会出发多次服务端请求,每个服务端请求触发更多底层服务请求,请求层级越多,杠杆效应越大;

典型场景:多次请求杠杆模式下运行的分布式系统,深层次的服务需处理大量请求,容易成为系统瓶颈;

另外大量请求会给网络带来巨大压力,可能会成为系统彻底崩溃的导火索;

表现方式:性能恶化和流量之间往往遵循指数曲线关系,且线性增加机器解决不了可用性问题;

恶化流程图如下:

 

六、性能优化模式

1、水平分割模式

动机:典型的服务端流程包含四个环节:接受请求、获取数据、处理数据、返回结果,大部分耗时长的服务发生在中间两个环节。

如果服务处理请求采用的是串行调用,那么其累加效应会极大延长单次请求RT,这就增加了系统进入长请求拥塞恶化模式的概率,进一步的降低系统性能。

如果能对不同的业务请求并行处理,请求总耗时就会大大降低。如下图所示:

Client需要对三个服务进行调用,如果采用顺序调用模式,系统的响应时间为18ms,而采用并行调用只需要7ms。

关于client和service之间的连接机制,具体可参考我之前的博客:http连接管理

水平分割模式原理

将整个请求流程切分为必须相互依赖的多个Stage,每个Stage包含相互独立的多种业务处理。切分之后,水平分割模式串行处理多个Stage,但是在Stage内部并行处理。

一次请求总耗时等于各个Stage耗时总和,每个Stage所耗时间等于该Stage内部最长的业务处理时间。

优化关键点:减少Stage数量和降低每个Stage耗时。

优点:

①、降低系统的平均响应时间和TP95响应时间,以及流量高峰时系统崩溃的概率。需要明白的一点:有时候,即使少量的并行化也可以显著提高整体性能

②、代码重构比较复杂,但是水平切割模式非常容易理解,只要熟悉系统的业务,识别出可以并行处理的流程,就能够进行水平切割;

③、对于新系统而言,如果存在可预见的性能问题,把水平分割模式作为一个重要的设计理念将会大大地提高系统的可用性、降低系统的重构风险;

④、有效、容易识别和理解;

 

2、垂直分割模式

动机:不同业务功能并存于同一个运行系统里面意味着资源共享,同时也意味着资源使用冲突。

不同业务功能,无论其调用量多小,都有一些内存开销。对于存在大量缓存的业务功能,数量的增加会极大地提高内存消耗,从而增大系统进入反复缓存反模式的概率。

思路:将系统按照不同的业务功能进行分割,主要有两种分割模式:部署垂直分割和代码垂直分割

部署垂直分割:按照可用性要求将系统进行等价分类,不同可用性业务部署在不同机器上,高可用业务单独部署;

代码垂直分割:让不同业务系统不共享代码,彻底解决系统资源使用冲突问题。

缺点:

①、增加了维护成本。一方面代码库数量增多提高了开发工程师的维护成本,另一方面,部署集群的变多会增加运维工程师的工作量;

②、代码不共享所导致的重复编码工作。

优点:

①、简单有效,特别适用于系统已经出现问题而又需要快速解决的场景;

②、部署层次的分割既安全又有效(大部分情况下,即使不增加机器,仅通过部署分割,系统整体吞吐量和可用性都有可能提升);

注意点:对于代码层次的分割,开发工程师需要在业务承接效率和系统可用性上面做一些折衷考虑。

 

3、恒变分离模式

原理:基于性能的设计要求变化的数据和不变的数据分开,而在面向对象设计中,为了便于对一个对象有整体的把握,

紧密相关的数据集合往往被组装进一个类,存储在一个数据库表,即使有部分数据冗余。

很多系统的主要工作是处理变化的数据,如果变化的数据和不变的数据被紧密组装在一起,系统对变化数据的操作将引入额外的开销。

而如果易变数据占总数据比例非常小,这种额外开销将会通过杠杆效应恶化系统性能。

分离易变和恒定不变的数据在对象创建、内存管理、网络传输等方面都有助于性能提高。

缺点

①、不符合面向对象的设计原则;

②、增加了类不变量的维护难度;

③、一张数据库表变成多张,增加维护成本。

恒变分离模式需要满足两个条件

①、易变数据占整体数据比例很低(比例越低,杠杆效应越大);

②、易变数据所导致的操作又是系统的主要操作;

分离原则:

对于复杂的业务系统,尽量按照面向对象的原则进行设计,只有在性能出现问题的时候才开始考虑恒变分离模式;

而对于高性能,业务简单的基础数据服务,恒变分离模式应该是设计之初的一个重要原则。

 

4、数据局部性模式

动机:数据局部性模式是多次请求杠杆反模式的针对性解决方案。

典型场景:大数据和强调个性化服务的时代,一个服务消费几十种不同类型数据的现象非常常见,同时每种类型数据服务都可能需要一个大的集群提供服务。

这就意味着客户端的一次请求有可能会导致服务端成千上万次调用操作,很容易使系统进入多次请求杠杆反模式。

数据服务数量暴增的原因:

①、缓存滥用以及缺乏规划,

②、数据量太大以至于无法在一台机器上提供全量数据服务,数据局部性模的核心思想是合理组织数据服务,减少服务调用次数。

优化方案:

①、对服务进行重新规划;

②、对客户端进行优化,包括:

  本地缓存,对于一致性要求不高且缓存命中率较高的数据服务,本地缓存可以减少服务端调用次数;

  批处理,对于单机或者由等价的机器集群提供的数据服务,尽可能采用批处理方式,将多个请求合成在一个请求中;

  客户端Hash,对需要通过Hash将请求分配到不同数据服务机器的服务,尽量在客户端Hash,对于落入同一等价集群的请求采用批处理方式进行调用。

 

5、实时离线分离模式

该模式的极端要求:离线服务永远不要调用实时服务,严格地讲它不是一种系统设计模式,而是一种管理规范。

离线服务和在线服务从可用性、可靠性、一致性的要求上完全不同。

原则上,应该遵循的就是离线服务编程规范,按照在线服务编程规范要求,成本就会大大提高,不符合经济原则;

从另外一方面讲,按照离线服务的需求去写在线服务代码,可用性、可靠性、一致性等往往得不到满足。

具体而言,实时离线分离模式建议如下几种规范

如果离线程序需要访问在线服务,应该给离线程序单独部署一套服务;

类似于MapReduce的云端多进程离线程序禁止直接访问在线服务;

分布式系统永远不要直接写传统的DBMS。

优缺点:

需要为在线环境和离线环境单独部署,维护多套环境所带来运维成本;

在线环境的数据在离线环境中可能很难获取;;

BUT:遵从实时离线分离模式是一个非常重要的安全管理准则,任何违背这个准则的行为都意味着系统性安全漏洞,都会增大线上故障概率

 

6、降级模式

降级模式是系统性能保障的最后一道防线。理论上讲,不存在绝对没有漏洞的系统,或者说,最好的安全措施就是为处于崩溃状态的系统提供预案。

从系统性能优化的角度来讲,不管系统设计地多么完善,总会有一些意料之外的情况会导致系统性能恶化,最终可能导致崩溃。

对于要求高可用性的服务,在系统设计之初,就必须做好降级设计。

良好的降级方案应该包含如下措施

①、在设计阶段,确定系统的开始恶化数值指标(例如:响应时间,内存使用量);

②、当系统开始恶化时,需要第一时间报警;

③、在收到报警后,或者人工手动控制系统进入降级状态,或者编写一个智能程序让系统自动降级;

典型的降级策略有三种:流量降级、效果降级和功能性降级。

流量降级是指当通过主动拒绝处理部分流量的方式让系统正常服务未降级的流量,这会造成部分用户服务不可用;

效果降级表现为服务质量的降级,即在流量高峰时期用相对低质量、低延时的服务来替换高质量、高延时的服务,保障所有用户的服务可用性;

功能性降级也表现为服务质量的降级,指通过减少功能的方式来提高用户的服务可用性。

区别:效果降级强调的是主功能服务质量的下降,功能性降级更多强调的是辅助性功能的缺失。

优缺点:

在确定使用降级模式的前提下,工程师需要权衡这三种降级策略的利弊;

对于不能接受降级后果的系统,必须要通过其他方式来提高系统的可用性;

总结:降级模式是一种设计安全准则,任何高可用性要求的服务,必须要按照降级模式的准则去设计。

对于处于MVP阶段的系统,或者对于可用性要求不高的系统,降级模式并非必须采纳的原则。

 

 

posted @ 2018-06-02 15:30 老_张 阅读(...) 评论(...) 编辑 收藏