转载:性能调优攻略

 

关于性能优化这是一个比较大的话题,在《由12306.cn谈谈网站性能技术》中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法。本文的东西是个人一些经验和知识,并不必定全对,但愿你们指正和补充php

在开始这篇文章以前,你们能够移步去看一下酷壳之前发表的《代码优化概要》,这篇文章基本上告诉你——要进行优化,先得找到性能瓶颈! 可是在讲如何定位系统性能瓶劲以前,请让我讲一下系统性能的定义和测试,由于没有这两件事,后面的定位和优化无从谈起。html

1、系统性能定义

让咱们先来讲说如何什么是系统性能。这个定义很是关键,若是咱们不清楚什么是系统性能,那么咱们将没法定位之。我见过不少朋友会以为这很容易,可是仔细一问,其实他们并无一个比较系统的方法,因此,在这里我想告诉你们如何系统地来定位性能。 整体来讲,系统性能就是两个事:node

  1. Throughput ,吞吐量。也就是每秒钟能够处理的请求数,任务数。
  2. Latency, 系统延迟。也就是系统在处理一个请求或一个任务时的延迟。

通常来讲,一个系统的性能受到这两个条件的约束,缺一不可。好比,个人系统能够顶得住一百万的并发,可是系统的延迟是2分钟以上,那么,这个一百万的负载毫无心义。系统延迟很短,可是吞吐量很低,一样没有意义。因此,一个好的系统的性能测试必然受到这两个条件的同时做用。 有经验的朋友必定知道,这两个东西的一些关系:mysql

  • Throughput越大,Latency会越差。由于请求量过大,系统太繁忙,因此响应速度天然会低。
  • Latency越好,能支持的Throughput就会越高。由于Latency短说明处理速度快,因而就能够处理更多的请求。

2、系统性能测试

通过上述的说明,咱们知道要测试系统的性能,须要咱们收集系统的Throughput和Latency这两个值。linux

 

  • 首先,须要定义Latency这个值,好比说,对于网站系统响应时间必需是5秒之内(对于某些实时系统可能须要定义的更短,好比5ms之内,这个更根据不一样的业务来定义)
  • 其次,开发性能测试工具,一个工具用来制造高强度的Throughput,另外一个工具用来测量Latency。对于第一个工具,你能够参考一下“十个免费的Web压力测试工具”,关于如何测量Latency,你能够在代码中测量,可是这样会影响程序的执行,并且只能测试到程序内部的Latency,真正的Latency是整个系统都算上,包括操做系统和网络的延时,你可使用Wireshark来抓网络包来测量。这两个工具具体怎么作,这个还请你们本身思考去了。
  • 最后,开始性能测试。你须要不断地提高测试的Throughput,而后观察系统的负载状况,若是系统顶得住,那就观察Latency的值。这样,你就能够找到系统的最大负载,而且你能够知道系统的响应延时是多少。

再多说一些,ios

  • 关于Latency,若是吞吐量不多,这个值估计会很是稳定,当吞吐量愈来愈大时,系统的Latency会出现很是剧烈的抖动,因此,咱们在测量Latency的时候,咱们须要注意到Latency的分布,也就是说,有百分之几的在咱们容许的范围,有百分之几的超出了,有百分之几的彻底不可接受。也许,平均下来的Latency达标了,可是其中仅有50%的达到了咱们可接受的范围。那也没有意义。
  • 关于性能测试,咱们还须要定义一个时间段。好比:在某个吞吐量上持续15分钟。由于当负载到达的时候,系统会变得不稳定,当过了一两分钟后,系统才会稳定。另外,也有多是,你的系统在这个负载下前几分钟还表现正常,而后就不稳定了,甚至垮了。因此,须要这么一段时间。这个值,咱们叫作峰值极限。
  • 性能测试还须要作Soak Test,也就是在某个吞吐量下,系统能够持续跑一周甚至更长。这个值,咱们叫作系统的正常运行的负载极限。

性能测试有不少很复要的东西,好比:burst test等。 这里不能一一详述,这里只说了一些和性能调优相关的东西。总之,性能测试是一细活和累活。算法

3、定位性能瓶颈

有了上面的铺垫,咱们就能够测试到到系统的性能了,再调优以前,咱们先来讲说如何找到性能的瓶颈。我见过不少朋友会以为这很容易,可是仔细一问,其实他们并无一个比较系统的方法。sql

3.1)查看操做系统负载

首先,当咱们系统有问题的时候,咱们不要急于去调查咱们代码,这个毫无心义。咱们首要须要看的是操做系统的报告。看看操做系统的CPU利用率,看看内存使用率,看看操做系统的IO,还有网络的IO,网络连接数,等等。Windows下的perfmon是一个很不错的工具,Linux下也有不少相关的命令和工具,好比:SystemTapLatencyTOP,vmstat, sar, iostat, top, tcpdump等等 。经过观察这些数据,咱们就能够知道咱们的软件的性能基本上出在哪里。好比:shell

1)先看CPU利用率,若是CPU利用率不高,可是系统的Throughput和Latency上不去了,这说明咱们的程序并无忙于计算,而是忙于别的一些事,好比IO。(另外,CPU的利用率还要看内核态的和用户态的,内核态的一上去了,整个系统的性能就下来了。而对于多核CPU来讲,CPU 0 是至关关键的,若是CPU 0的负载高,那么会影响其它核的性能,由于CPU各核间是须要有调度的,这靠CPU0完成)数据库

2)而后,咱们能够看一下IO大不大,IO和CPU通常是反着来的,CPU利用率高则IO不大,IO大则CPU就小。关于IO,咱们要看三个事,一个是磁盘文件IO,一个是驱动程序的IO(如:网卡),一个是内存换页率。这三个事都会影响系统性能。

3)而后,查看一下网络带宽使用状况,在Linux下,你可使用iftop, iptraf, ntop, tcpdump这些命令来查看。或是用Wireshark来查看。

4)若是CPU不高,IO不高,内存使用不高,网络带宽使用不高。可是系统的性能上不去。这说明你的程序有问题,好比,你的程序被阻塞了。多是由于等那个锁,多是由于等某个资源,或者是在切换上下文。

经过了解操做系统的性能,咱们才知道性能的问题,好比:带宽不够,内存不够,TCP缓冲区不够,等等,不少时候,不须要调整程序的,只须要调整一下硬件或操做系统的配置就能够了

3.2)使用Profiler测试

接下来,咱们须要使用性能检测工具,也就是使用某个Profiler来差看一下咱们程序的运行性能。如:Java的JProfiler/TPTP/CodePro Profiler,GNU的gprof,IBM的PurifyPlus,Intel的VTune,AMD的CodeAnalyst,还有Linux下的OProfile/perf,后面两个可让你对你的代码优化到CPU的微指令级别,若是你关心CPU的L1/L2的缓存调优,那么你须要考虑一下使用VTune。 使用这些Profiler工具,可让你程序中各个模块函数甚至指令的不少东西,如:运行的时间 ,调用的次数CPU的利用率,等等。这些东西对咱们来讲很是有用。

咱们重点观察运行时间最多,调用次数最多的那些函数和指令。这里注意一下,对于调用次数多可是时间很短的函数,你可能只须要轻微优化一下,你的性能就上去了(好比:某函数一秒种被调用100万次,你想一想若是你让这个函数提升0.01毫秒的时间 ,这会给你带来多大的性能)

使用Profiler有个问题咱们须要注意一下,由于Profiler会让你的程序运行的性能变低,像PurifyPlus这样的工具会在你的代码中插入不少代码,会致使你的程序运行效率变低,从而没发测试出在高吞吐量下的系统的性能,对此,通常有两个方法来定位系统瓶颈:

1)在你的代码中本身作统计,使用微秒级的计时器和函数调用计算器,每隔10秒把统计log到文件中。

2)分段注释你的代码块,让一些函数空转,作Hard Code的Mock,而后再测试一下系统的Throughput和Latency是否有质的变化,若是有,那么被注释的函数就是性能瓶颈,再在这个函数体内注释代码,直到找到最耗性能的语句。

最后再说一点,对于性能测试,不一样的Throughput会出现不一样的测试结果,不一样的测试数据也会有不一样的测试结果。因此,用于性能测试的数据很是重要,性能测试中,咱们须要观测试不一样Throughput的结果

4、常见的系统瓶颈

下面这些东西是我所经历过的一些问题,也许并不全,也许并不对,你们能够补充指正,我纯属抛砖引玉。关于系统架构方面的性能调优,你们可移步看一下《由12306.cn谈谈网站性能技术》,关于Web方面的一些性能调优的东西,你们能够看看《Web开发中须要了解的东西》一文中的性能一章。我在这里就再也不说设计和架构上的东西了。

通常来讲,性能优化也就是下面的几个策略:

  • 用空间换时间。各类cache如CPU L1/L2/RAM到硬盘,都是用空间来换时间的策略。这样策略基本上是把计算的过程一步一步的保存或缓存下来,这样就不用每次用的时候都要再计算一遍,好比数据缓冲,CDN,等。这样的策略还表现为冗余数据,好比数据镜象,负载均衡什么的。
  • 用时间换空间。有时候,少许的空间可能性能会更好,好比网络传输,若是有一些压缩数据的算法(如前些天说的“Huffman 编码压缩算法” 和 “rsync 的核心算法”),这样的算法其实很耗时,可是由于瓶颈在网络传输,因此用时间来换空间反而能省时间。
  • 简化代码。最高效的程序就是不执行任何代码的程序,因此,代码越少性能就越高。关于代码级优化的技术大学里的教科书有不少示例了。如:减小循环的层数,减小递归,在循环中少声明变量,少作分配和释放内存的操做,尽可能把循环体内的表达式抽到循环外,条件表达的中的多个条件判断的次序,尽可能在程序启动时把一些东西准备好,注意函数调用的开销(栈上开销),注意面向对象语言中临时对象的开销,当心使用异常(不要用异常来检查一些可接受可忽略并常常发生的错误),…… 等等,等等,这连东西须要咱们很是了解编程语言和经常使用的库。
  • 并行处理。若是CPU只有一个核,你要玩多进程,多线程,对于计算密集型的软件会反而更慢(由于操做系统调度和切换开销很大),CPU的核多了才能真正体现出多进程多线程的优点。并行处理须要咱们的程序有Scalability,不能水平或垂直扩展的程序没法进行并行处理。从架构上来讲,这表再为——是否能够作到不改代码只是加加机器就能够完成性能提高?

总之,根据2:8原则来讲,20%的代码耗了你80%的性能,找到那20%的代码,你就能够优化那80%的性能。 下面的一些东西都是个人一些经验,我只例举了一些最有价值的性能调优的的方法,供你参考,也欢迎补充。

4.1)算法调优。算法很是重要,好的算法会有更好的性能。举几个我经历过的项目的例子,你们能够感受一下。

  • 一个是过滤算法,系统须要对收到的请求作过滤,咱们把能够被filter in/out的东西配置在了一个文件中,原有的过滤算法是遍历过滤配置,后来,咱们找到了一种方法能够对这个过滤配置进行排序,这样就能够用二分折半的方法来过滤,系统性能增长了50%。
  • 一个是哈希算法。计算哈希算法的函数并不高效,一方面是计算太费时,另外一方面是碰撞过高,碰撞高了就跟单向链表一个性能(可参看Hash Collision DoS 问题)。咱们知道,算法都是和须要处理的数据颇有关系的,就算是被你们所嘲笑的“冒泡排序”在某些状况下(大多数数据是排好序的)其效率会高于全部的排序算法。哈希算法也同样,广为人知的哈希算法都是用英文字典作测试,可是咱们的业务在数据有其特殊性,因此,对于还须要根据本身的数据来挑选适合的哈希算法。对于我之前的一个项目,公司内某牛人给我发来了一个哈希算法,结果让咱们的系统性能上升了150%。(关于各类哈希算法,你必定要看看StackExchange上的这篇关于各类hash算法的文章 )
  • 分而治之和预处理。之前有一个程序为了生成月报表,每次都须要计算很长的时间,有时候须要花将近一成天的时间。因而咱们把咱们找到了一种方法能够把这个算法发成增量式的,也就是说我天天都把当天的数据计算好了后和前一天的报表合并,这样能够大大的节省计算时间,天天的数据计算量只须要20分钟,可是若是我要算整个月的,系统则须要10个小时以上(SQL语句在大数据量面前性能成级数性降低)。这种分而治之的思路在大数据面前对性能有很帮助,就像merge排序同样。SQL语句和数据库的性能优化也是这一策略,如:使用嵌套式的Select而不是笛卡尔积的Select,使用视图,等等。

4.2)代码调优。从个人经验上来讲,代码上的调优有下面这几点:

  • 字符串操做。这是最费系统性能的事了,不管是strcpy, strcat仍是strlen,最须要注意的是字符串子串匹配。因此,能用整型最好用整型。举几个例子,第一个例子是N年前作银行的时候,个人同事喜欢把日期存成字符串(如:2012-05-29 08:30:02),我勒个去,一个select  where between语句至关耗时。另外一个例子是,我之前有个同事把一些状态码用字符串来处理,他的理由是,这样能够在界面上直接显示,后来性能调优的时候,我把这些状态码全改为整型,而后用位操做查状态,由于有一个每秒钟被调用了150K次的函数里面有三处须要检查状态,通过改善之后,整个系统的性能上升了30%左右。还有一个例子是,我之前从事的某个产品编程规范中有一条是要在每一个函数中把函数名定义出来,如:const char fname[]=”functionName()”, 这是为了好打日志,可是为何不声明成 static类型的呢?
  • 多线程调优。有人说,thread is evil,这个对于系统性能在某些时候是个问题。由于多线程瓶颈就在于互斥和同步的锁上,以及线程上下文切换的成本,怎么样的少用锁或不用锁是根本(好比:多版本并发控制(MVCC)在分布式系统中的应用 中说的乐观锁能够解决性能问题),此外,还有读写锁也能够解决大多数是读操做的并发的性能问题。这里多说一点在C++中,咱们可能会使用线程安全的智能指针AutoPtr或是别的一些容器,只要是线程安全的,其无论三七二十一都要上锁,上锁是个成本很高的操做,使用AutoPtr会让咱们的系统性能降低得很快,若是你能够保证不会有线程并发问题,那么你应该不要用AutoPtr。我记得我上次咱们同事去掉智能指针的引用计数,让系统性能提高了50%以上。对于Java对象的引用计数,若是我猜的没错的话,处处都是锁,因此,Java的性能问题一直是个问题。另外,线程不是越多越好,线程间的调度和上下文切换也是很夸张的事,尽量的在一个线程里干,尽量的不要同步线程。这会让你有不少的性能。
  • 内存分配。不要小看程序的内存分配。malloc/realloc/calloc这样的系统调很是耗时,尤为是当内存出现碎片的时候。我之前的公司出过这样一个问题——在用户的站点上,咱们的程序有一天不响应了,用GDB跟进去一看,系统hang在了malloc操做上,20秒都没有返回,重启一些系统就行了。这就是内存碎片的问题。这就是为何不少人抱怨STL有严重的内存碎片的问题,由于太多的小内存的分配释放了。有不少人会觉得用内存池能够解决这个问题,可是实际上他们只是从新发明了Runtime-C或操做系统的内存管理机制,彻底于事无补。固然解决内存碎片的问题仍是经过内存池,具体来讲是一系列不一样尺寸的内存池(这个留给你们本身去思考)。固然,少进行动态内存分配是最好的。说到内存池就须要说一下池化技术。好比线程池,链接池等。池化技术对于一些短做业来讲(如http服务) 至关至关的有效。这项技术能够减小连接创建,线程建立的开销,从而提升性能。
  • 异步操做。咱们知道Unix下的文件操做是有block和non-block的方式的,像有些系统调用也是block式的,如:Socket下的select,Windows下的WaitforObject之类的,若是咱们的程序是同步操做,那么会很是影响性能,咱们能够改为异步的,可是改为异步的方式会让你的程序变复杂。异步方式通常要经过队列,要注间队列的性能问题,另外,异步下的状态通知一般是个问题,好比消息事件通知方式,有callback方式,等,这些方式一样可能会影响你的性能。可是一般来讲,异步操做会让性能的吞吐率有很大提高(Throughput),可是会牺牲系统的响应时间(latency)。这须要业务上支持。
  • 语言和代码库。咱们要熟悉语言以及所使用的函数库或类库的性能。好比:STL中的不少容器分配了内存后,那怕你删除元素,内存也不会回收,其会形成内存泄露的假像,并可能形成内存碎片问题。再如,STL某些容器的size()==0  和 empty()是不同的,由于,size()是O(n)复杂度,empty()是O(1)的复杂度,这个要当心。Java中的JVM调优须要使用的这些参数:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold,还须要注意JVM的GC,GC的霸气你们都知道,尤为是full GC(还整理内存碎片),他就像“恐龙特级克赛号”同样,他运行的时候,整个世界的时间都中止了。

4.3)网络调优

关于网络调优,尤为是TCP Tuning(你能够以这两个关键词在网上找到不少文章),这里面有不少不少东西能够说。看看Linux下TCP/IP的那么多参数就知道了(顺便说一下,你也许不喜欢Linux,可是你不可否认Linux给咱们了不少能够进行内核调优的权力)。强烈建议你们看看《TCP/IP 详解 卷1:协议》这本书。我在这里只讲一些概念上的东西。

A) TCP调优

咱们知道TCP连接是有不少开销的,一个是会占用文件描述符,另外一个是会开缓存,通常来讲一个系统能够支持的TCP连接数是有限的,咱们须要清楚地认识到TCP连接对系统的开销是很大的。正是由于TCP是耗资源的,因此,不少攻击都是让你系统上出现大量的TCP连接,把你的系统资源耗尽。好比著名的SYNC Flood攻击。

因此,咱们要注意配置KeepAlive参数,这个参数的意思是定义一个时间,若是连接上没有数据传输,系统会在这个时间发一个包,若是没有收到回应,那么TCP就认为连接断了,而后就会把连接关闭,这样能够回收系统资源开销。(注:HTTP层上也有KeepAlive参数)对于像HTTP这样的短连接,设置一个1-2分钟的keepalive很是重要。这能够在必定程度上防止DoS攻击。有下面几个参数(下面这些参数的值仅供参考):

1
2
3
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_fin_timeout = 30

对于TCP的TIME_WAIT这个状态,主动关闭的一方进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),默认为4分钟,TIME_WAIT状态下的资源不能回收。有大量的TIME_WAIT连接的状况通常是在HTTP服务器上。对此,有两个参数须要注意,

1
2
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1

前者表示重用TIME_WAIT,后者表示回收TIME_WAIT的资源。

TCP还有一个重要的概念叫RWIN(TCP Receive Window Size),这个东西的意思是,我一个TCP连接在没有向Sender发出ack时能够接收到的最大的数据包。为何这个很重要?由于若是Sender没有收到Receiver发过来ack,Sender就会中止发送数据并会等一段时间,若是超时,那么就会重传。这就是为何TCP连接是可靠连接的缘由。重传还不是最严重的,若是有丢包发生的话,TCP的带宽使用率会立刻受到影响(会盲目减半),再丢包,再减半,而后若是不丢包了,就逐步恢复。相关参数以下:

1
2
3
4
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

通常来讲,理论上的RWIN应该设置成:吞吐量  * 回路时间。Sender端的buffer应该和RWIN有同样的大小,由于Sender端发送完数据后要等Receiver端确认,若是网络延时很大,buffer太小了,确认的次数就会多,因而性能就不高,对网络的利用率也就不高了。也就是说,对于延迟大的网络,咱们须要大的buffer,这样能够少一点ack,多一些数据,对于响应快一点的网络,能够少一些buffer。由于,若是有丢包(没有收到ack),buffer过大可能会有问题,由于这会让TCP重传全部的数据,反而影响网络性能。(固然,网络差的状况下,就别玩什么高性能了) 因此,高性能的网络重要的是要让网络丢包率很是很是地小(基本上是用在LAN里),若是网络基本是可信的,这样用大一点的buffer会有更好的网络传输性能(来来回回太多太影响性能了)。

另外,咱们想想,若是网络质量很是好,基本不丢包,而业务上咱们不怕偶尔丢几个包,若是是这样的话,那么,咱们为何不用速度更快的UDP呢?你想过这个问题了吗?

B)UDP调优

说到UDP的调优,有一些事我想重点说同样,那就是MTU——最大传输单元(其实这对TCP也同样,由于这是链路层上的东西)。所谓最大传输单元,你能够想像成是公路上的公交车,假设一个公交车能够最多坐70人,带宽就像是公路的车道数同样,若是一条路上最多能够容下100辆公交车,那意味着我最多能够运送7000人,可是若是公交车坐不满,好比平均每辆车只有20人,那么我只运送了2000人,因而我公路资源(带宽资源)就被浪费了。 因此,咱们对于一个UDP的包,咱们要尽可能地让他大到MTU的最大尺寸再往网络上传,这样能够最大化带宽利用率。对于这个MTU,以太网是1500字节,光纤是4352字节,802.11无线网是7981。可是,当咱们用TCP/UDP发包的时候,咱们的有效负载Payload要低于这个值,由于IP协议会加上20个字节,UDP会加上8个字节(TCP加的更多),因此,通常来讲,你的一个UDP包的最大应该是1500-8-20=1472,这是你的数据的大小。固然,若是你用光纤的话, 这个值就能够更大一些。(顺便说一下,对于某些NB的千光以态网网卡来讲,在网卡上,网卡硬件若是发现你的包的大小超过了MTU,其会帮你作fragment,到了目标端又会帮你作重组,这就不须要你在程序中处理了)

再多说一下,使用Socket编程的时候,你可使用setsockopt() 设置 SO_SNDBUF/SO_RCVBUF 的大小,TTL和KeepAlive这些关键的设置,固然,还有不少,具体你能够查看一下Socket的手册。

最后说一点,UDP还有一个最大的好处是multi-cast多播,这个技术对于你须要在内网里通知多台结点时很是方便和高效。并且,多播这种技术对于机会的水平扩展(须要增长机器来侦听多播信息)也颇有利。

C)网卡调优

对于网卡,咱们也是能够调优的,这对于千兆以及网网卡很是必要,在Linux下,咱们能够用ifconfig查看网上的统计信息,若是咱们看到overrun上有数据,咱们就可能须要调整一下txqueuelen的尺寸(通常默认为1000),咱们能够调大一些,如:ifconfig eth0 txqueuelen 5000。Linux下还有一个命令叫:ethtool能够用于设置网卡的缓冲区大小。在Windows下,咱们能够在网卡适配器中的高级选项卡中调整相关的参数(如:Receive Buffers, Transmit Buffer等,不一样的网卡有不一样的参数)。把Buffer调大对于须要大数据量的网络传输很是有效。

D)其它网络性能

关于多路复用技术,也就是用一个线程来管理全部的TCP连接,有三个系统调用要重点注意:一个是select,这个系统调用只支持上限1024个连接,第二个是poll,其能够突破1024的限制,可是select和poll本质上是使用的轮询机制,轮询机制在连接多的时候性能不好,因主是O(n)的算法,因此,epoll出现了,epoll是操做系统内核支持的,仅当在连接活跃时,操做系统才会callback,这是由操做系统通知触发的,但其只有Linux Kernel 2.6之后才支持(准确说是2.5.44中引入的),固然,若是全部的连接都是活跃的,过多的使用epoll_ctl可能会比轮询的方式还影响性能,不过影响的不大。

另外,关于一些和DNS Lookup的系统调用要当心,好比:gethostbyaddr/gethostbyname,这个函数可能会至关的费时,由于其要到网络上去找域名,由于DNS的递归查询,会致使严重超时,而又不能经过设置什么参数来设置time out,对此你能够经过配置hosts文件来加快速度,或是本身在内存中管理对应表,在程序启动时查好,而不要在运行时每次都查。另外,在多线程下面,gethostbyname会一个更严重的问题,就是若是有一个线程的gethostbyname发生阻塞,其它线程都会在gethostbyname处发生阻塞,这个比较变态,要当心。(你能够试试GNU的gethostbyname_r(),这个的性能要好一些) 这种到网上找信息的东西不少,好比,若是你的Linux使用了NIS,或是NFS,某些用户或文件相关的系统调用就很慢,因此要当心。

4.4)系统调优

A)I/O模型

前面说到过select/poll/epoll这三个系统调用,咱们都知道,Unix/Linux下把全部的设备都当成文件来进行I/O,因此,那三个操做更应该算是I/O相关的系统调用。说到  I/O模型,这对于咱们的I/O性能至关重要,咱们知道,Unix/Linux经典的I/O方式是(关于Linux下的I/O模型,你们能够读一下这篇文章《使用异步I/O大大提升性能》):

第一种,同步阻塞式I/O,这个不说了。

第二种,同步无阻塞方式。其经过fctnl设置 O_NONBLOCK 来完成。

第三种,对于select/poll/epoll这三个是I/O不阻塞,可是在事件上阻塞,算是:I/O异步,事件同步的调用。

第四种,AIO方式。这种I/O 模型是一种处理与 I/O 并行的模型。I/O请求会当即返回,说明请求已经成功发起了。在后台完成I/O操做时,向应用程序发起通知,通知有两种方式:一种是产生一个信号,另外一种是执行一个基于线程的回调函数来完成此次 I/O 处理过程。

第四种由于没有任何的阻塞,不管是I/O上,仍是事件通知上,因此,其可让你充分地利用CPU,比起第二种同步无阻塞好处就是,第二种要你一遍一遍地去轮询。Nginx之所因此高效,是其使用了epoll和AIO的方式来进行I/O的。

再说一下Windows下的I/O模型,

a)一个是WriteFile系统调用,这个系统调用能够是同步阻塞的,也能够是同步无阻塞的,关于看文件是否是以Overlapped打开的。关于同步无阻塞,须要设置其最后一个参数Overlapped,微软叫Overlapped I/O,你须要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。

b)另外一个叫WriteFileEx的系统调用,其能够实现异步I/O,并可让你传入一个callback函数,等I/O结束后回调之, 可是这个回调的过程Windows是把callback函数放到了APC(Asynchronous Procedure Calls)的队列中,而后,只用当应用程序当前线程成为可被通知状态(Alterable)时,才会被回调。只有当你的线程使用了这几个函数时WaitForSingleObjectExWaitForMultipleObjectsExMsgWaitForMultipleObjectsExSignalObjectAndWait 和 SleepEx,线程才会成为Alterable状态。可见,这个模型,仍是有wait,因此性能也不高。

c)而后是IOCP – IO Completion Port,IOCP会把I/O的结果放在一个队列中,可是,侦听这个队列的不是主线程,而是专门来干这个事的一个或多个线程去干(老的平台要你本身建立线程,新的平台是你能够建立一个线程池)。IOCP是一个线程池模型。这个和Linux下的AIO模型比较类似,可是实现方式和使用方式彻底不同。

固然,真正提升I/O性能方式是把和外设的I/O的次数降到最低,最好没有,因此,对于读来讲,内存cache一般能够从质上提高性能,由于内存比外设快太多了。对于写来讲,cache住要写的数据,少写几回,可是cache带来的问题就是实时性的问题,也就是latency会变大,咱们须要在写的次数上和相应上作权衡。

B)多核CPU调优

关于CPU的多核技术,咱们知道,CPU0是很关键的,若是0号CPU被用得过狠的话,别的CPU性能也会降低,由于CPU0是有调整功能的,因此,咱们不能任由操做系统负载均衡,由于咱们本身更了解本身的程序,因此,咱们能够手动地为其分配CPU核,而不会过多地占用CPU0,或是让咱们关键进程和一堆别的进程挤在一块儿。

  • 对于Windows来讲,咱们能够经过“任务管理器”中的“进程”而中右键菜单中的“设置相关性……”(Set Affinity…)来设置并限制这个进程能被运行在哪些核上。
  • 对于Linux来讲,可使用taskset命令来设置(你能够经过安装schedutils来安装这个命令:apt-get install schedutils)

多核CPU还有一个技术叫NUMA技术(Non-Uniform Memory Access)。传统的多核运算是使用SMP(Symmetric Multi-Processor )模式,多个处理器共享一个集中的存储器和I/O总线。因而就会出现一致存储器访问的问题,一致性一般意味着性能问题。NUMA模式下,处理器被划分红多个node, 每一个node有本身的本地存储器空间。关于NUMA的一些技术细节,你能够查看一下这篇文章《Linux 的 NUMA 技术》,在Linux下,对NUMA调优的命令是:numactl 。以下面的命令:(指定命令“myprogram arg1 arg2”运行在node 0 上,其内存分配在node 0 和 1上)

1
numactl --cpubind=0 --membind=0,1 myprogram arg1 arg2

固然,上面这个命令并很差,由于内存跨越了两个node,这很是很差。最好的方式是只让程序访问和本身运行同样的node,如:

1
$ numactl --membind 1 --cpunodebind 1 --localalloc myapplication

C)文件系统调优

关于文件系统,由于文件系统也是有cache的,因此,为了让文件系统有最大的性能。首要的事情就是分配足够大的内存,这个很是关键,在Linux下可使用free命令来查看 free/used/buffers/cached,理想来讲,buffers和cached应该有40%左右。而后是一个快速的硬盘控制器,SCSI会好不少。最快的是Intel SSD 固态硬盘,速度超快,可是写次数有限。

接下来,咱们就能够调优文件系统配置了,对于Linux的Ext3/4来讲,几乎在全部状况下都有所帮助的一个参数是关闭文件系统访问时间,在/etc/fstab下看看你的文件系统 有没有noatime参数(通常来讲应该有),还有一个是dealloc,它可让系统在最后时刻决定写入文件发生时使用哪一个块,可优化这个写入程序。还要注间一下三种日志模式:data=journal、data=ordered和data=writeback。默认设置data=ordered提供性能和防御之间的最佳平衡。

固然,对于这些来讲,ext4的默认设置基本上是最佳优化了。

这里介绍一个Linux下的查看I/O的命令—— iotop,可让你看到各进程的磁盘读写的负载状况。

其它还有一些关于NFS、XFS的调优,你们能够上google搜索一些相关优化的文章看看。关于各文件系统,你们能够看一下这篇文章——《Linux日志文件系统及性能分析

4.5)数据库调优

数据库调优并非个人强项,我就仅用我很是有限的知识说上一些吧。注意,下面的这些东西并不必定正确,由于在不一样的业务场景,不一样的数据库设计下可能会获得彻底相反的结论,因此,我仅在这里作一些通常性的说明,具体问题还要具体分析。

A)数据库引擎调优

我对数据库引擎不是熟,可是有几个事情我以为是必定要去了解的。

  • 数据库的锁的方式。这个很是很是地重要。并发状况下,锁是很是很是影响性能的。各类隔离级别,行锁,表锁,页锁,读写锁,事务锁,以及各类写优先仍是读优先机制。性能最高的是不要锁,因此,分库分表,冗余数据,减小一致性事务处理,能够有效地提升性能。NoSQL就是牺牲了一致性和事务处理,并冗余数据,从而达到了分布式和高性能。
  • 数据库的存储机制。不但要搞清楚各类类型字段是怎么存储的,更重要的是数据库的数据存储方式,是怎么分区的,是怎么管理的,好比Oracle的数据文件,表空间,段,等等。了解清楚这个机制能够减轻不少的I/O负载。好比:MySQL下使用show engines;能够看到各类存储引擎的支持。不一样的存储引擎有不一样的侧重点,针对不一样的业务或数据库设计会让你有不一样的性能。
  • 数据库的分布式策略。最简单的就是复制或镜像,须要了解分布式的一致性算法,或是主主同步,主从同步。经过了解这种技术的机理能够作到数据库级别的水平扩展。

B)SQL语句优化

关于SQL语句的优化,首先也是要使用工具,好比:MySQL SQL Query AnalyzerOracle SQL Performance Analyzer,或是微软SQL Query Analyzer,基本上来讲,全部的RMDB都会有这样的工具,来让你查看你的应用中的SQL的性能问题。 还可使用explain来看看SQL语句最终Execution Plan会是什么样的。

还有一点很重要,数据库的各类操做须要大量的内存,因此服务器的内存要够,优其应对那些多表查询的SQL语句,那是至关的耗内存。

下面我根据我有限的数据库SQL的知识说几个会有性能问题的SQL:

  • 全表检索。好比:select * from user where lastname = “xxxx”,这样的SQL语句基本上是全表查找,线性复杂度O(n),记录数越多,性能也越差(如:100条记录的查找要50ms,一百万条记录须要5分钟)。对于这种状况,咱们能够有两种方法提升性能:一种方法是分表,把记录数降下来,另外一种方法是建索引(为lastname建索引)。索引就像是key-value的数据结构同样,key就是where后面的字段,value就是物理行号,对索引的搜索复杂度是基本上是O(log(n)) ——用B-Tree实现索引(如:100条记录的查找要50ms,一百万条记录须要100ms)。
  • 索引。对于索引字段,最好不要在字段上作计算、类型转换、函数、空值判断、字段链接操做,这些操做都会破坏索引本来的性能。固然,索引通常都出如今Where或是Order by字句中,因此对Where和Order by子句中的子段最好不要进行计算操做,或是加上什么NOT之类的,或是使用什么函数。
  • 多表查询。关系型数据库最多的操做就是多表查询,多表查询主要有三个关键字,EXISTS,IN和JOIN(关于各类join,能够参看图解SQL的Join一文)。基原本说,现代的数据引擎对SQL语句优化得都挺好的,JOIN和IN/EXISTS在结果上有些不一样,但性能基本上都差很少。有人说,EXISTS的性能要好于IN,IN的性能要好于JOIN,我各人以为,这个还要看你的数据、schema和SQL语句的复杂度,对于通常的简单的状况来讲,都差很少,因此千万不要使用过多的嵌套,千万不要让你的SQL太复杂,宁肯使用几个简单的SQL也不要使用一个巨大无比的嵌套N级的SQL。还有人说,若是两个表的数据量差很少,Exists的性能可能会高于In,In可能会高于Join,若是这两个表一大一小,那么子查询中,Exists用大表,In则用小表。这个,我没有验证过,放在这里让你们讨论吧。另,有一篇关于SQL Server的文章你们能够看看《IN vs JOIN vs EXISTS
  • JOIN操做。有人说,Join表的顺序会影响性能,只要Join的结果集是同样,性能和join的次序无关。由于后台的数据库引擎会帮咱们优化的。Join有三种实现算法,嵌套循环,排序归并,和Hash式的Join。(MySQL只支持第一种)
    • 嵌套循环,就好像是咱们常见的多重嵌套循环。注意,前面的索引说过,数据库的索引查找算法用的是B-Tree,这是O(log(n))的算法,因此,整个算法复法度应该是O(log(n)) * O(log(m)) 这样的。
    • Hash式的Join,主要解决嵌套循环的O(log(n))的复杂,使用一个临时的hash表来标记。
    • 排序归并,意思是两个表按照查询字段排好序,而后再合并。固然,索引字段通常是排好序的。

仍是那句话,具体要看什么样的数据,什么样的SQL语句,你才知道用哪一种方法是最好的。

  • 部分结果集。咱们知道MySQL里的Limit关键字,Oracle里的rownum,SQL Server里的Top都是在限制前几条的返回结果。这给了咱们数据库引擎不少能够调优的空间。通常来讲,返回top n的记录数据须要咱们使用order by,注意在这里咱们须要为order by的字段创建索引。有了被建索引的order by后,会让咱们的select语句的性能不会被记录数的所影响。使用这个技术,通常来讲咱们前台会以分页方式来显现数据,Mysql用的是OFFSET,SQL Server用的是FETCH NEXT,这种Fetch的方式其实并很差是线性复杂度,因此,若是咱们可以知道order by字段的第二页的起始值,咱们就能够在where语句里直接使用>=的表达式来select,这种技术叫seek,而不是fetch,seek的性能比fetch要高不少。
  • 字符串。正如我前面所说的,字符串操做对性能上有很是大的恶梦,因此,能用数据的状况就用数字,好比:时间,工号,等。
  • 全文检索。千万不要用Like之类的东西来作全文检索,若是要玩全文检索,能够尝试使用Sphinx
  • 其它
    • 不要select *,而是明确指出各个字段,若是有多个表,必定要在字段名前加上表名,不要让引擎去算。
    • 不要用Having,由于其要遍历全部的记录。性能差得不能再差。
    • 尽量地使用UNION ALL  取代  UNION。
    • 索引过多,insert和delete就会越慢。而update若是update多数索引,也会慢,可是若是只update一个,则只会影响一个索引表。
    • 等等。

关于SQL语句的优化,网上有不少文章, 不一样的数据库引擎有不一样的优化技巧,正如本站之前转发的《MySQL性能优化的最佳20+条经验

 

转载地址:http://coolshell.cn/articles/7490.html

相关文章
相关标签/搜索