头图

在当今数字化时代,Linux 系统凭借其强大的稳定性、开放性和灵活性,广泛应用于服务器、云计算、大数据等诸多领域。然而,随着业务量的不断增长和应用场景的日益复杂,Linux 内核的性能面临着巨大挑战。哪怕是微小的性能瓶颈,都可能像滚雪球一样,在高负载运行时被无限放大,进而引发一系列严重问题。

想象一下,一个电商网站在购物高峰期,由于 Linux 内核性能不佳,导致服务器响应迟缓。用户点击商品详情,页面却迟迟无法加载;提交订单后,长时间得不到反馈。这不仅会让用户体验大打折扣,还可能导致大量潜在客户流失,给企业带来不可估量的经济损失。再比如,一个实时数据分析系统,因为内核性能问题,无法及时处理海量数据,使得分析结果严重滞后,企业决策层基于这些滞后数据做出的决策,很可能与市场实际情况脱节,进而影响企业的战略布局和发展方向。

由此可见,Linux 内核性能调优绝非小事,它直接关系到系统的稳定性、可靠性以及业务的正常运转。通过对内核参数进行合理调整、优化系统资源分配、采用高效的调度算法等手段,可以显著提升 Linux 内核的性能,让系统在面对各种复杂工作负载时都能游刃有余。接下来,就让我们一同深入探索 Linux 内核性能调优的精彩世界。

一、系统性能指标大揭秘

在深入探讨 Linux 内核性能调优方法之前,我们首先要明确衡量系统性能的关键指标。就如同评价一辆汽车的性能,我们会关注它的速度、油耗、操控性等指标一样,对于 Linux 系统,吞吐量、系统延迟、CPU 使用率、内存使用率和磁盘 I/O 等指标,能帮助我们全面了解系统的运行状态。

1.1吞吐量:数据处理的速度担当

吞吐量,是指在单位时间内系统成功处理的数据量或请求数 ,它直观地反映了系统的数据处理能力。以一个电商网站为例,假设在促销活动期间,该网站每秒能够处理 1000 个商品查询请求和 200 个订单提交请求,这些请求处理的数量就是网站系统的吞吐量体现。如果网站的吞吐量较低,在高并发的情况下,大量请求就会堆积,导致系统响应缓慢甚至崩溃。就像一条狭窄的公路,车流量稍大就会造成交通堵塞。提高系统吞吐量,能够让系统在单位时间内处理更多的任务,满足业务增长的需求。

1.2系统延迟:影响用户体验的关键

系统延迟,指的是从请求发出到收到响应所经历的时间。还是以电商网站为例,当用户点击商品详情页时,页面如果能在 1 秒内加载完成,用户几乎察觉不到延迟,体验流畅;但如果加载时间长达 5 秒甚至更久,用户就会感到不耐烦,可能会离开网站。低延迟对于提升用户体验至关重要,尤其在对实时性要求极高的场景,如在线游戏、金融交易等。在在线游戏中,玩家的每一个操作都需要及时反馈到游戏画面中,如果系统延迟过高,玩家发出的攻击指令可能要数秒后才会生效,这会严重影响游戏的公平性和趣味性。

一般来说,一个系统的性能受到这两个条件的约束,缺一不可。比如,我的系统可以顶得住一百万的并发,但是系统的延迟是2分钟以上,那么,这个一百万的负载毫无意义。系统延迟很短,但是吞吐量很低,同样没有意义。所以,一个好的系统的性能测试必然受到这两个条件的同时作用。

有经验的朋友一定知道,这两个东西的一些关系:

  1. Throughput越大,Latency会越差。因为请求量过大,系统太繁忙,所以响应速度自然会低。
  2. Latency越好,能支持的Throughput就会越高。因为Latency短说明处理速度快,于是就可以处理更多的请求。

二、定位性能瓶颈的实用方法

2.1系统性能测试

经过上述的说明,我们知道要测试系统的性能,需要我们收集系统的Throughput和Latency这两个值。

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

关于Latency,如果吞吐量很少,这个值估计会非常稳定,当吞吐量越来越大时,系统的Latency会出现非常剧烈的抖动,所以,我们在测量Latency的时候,我们需要注意到Latency的分布,也就是说,有百分之几的在我们允许的范围,有百分之几的超出了,有百分之几的完全不可接受。也许,平均下来的Latency达标了,但是其中仅有50%的达到了我们可接受的范围。那也没有意义。
关于性能测试,我们还需要定义一个时间段。比如:在某个吞吐量上持续15分钟。因为当负载到达的时候,系统会变得不稳定,当过了一两分钟后,系统才会稳定。另外,也有可能是,你的系统在这个负载下前几分钟还表现正常,然后就不稳定了,甚至垮了。所以,需要这么一段时间。这个值,我们叫做峰值极限。
性能测试还需要做Soak Test,也就是在某个吞吐量下,系统可以持续跑一周甚至更长。这个值,我们叫做系统的正常运行的负载极限。
性能测试有很多很复要的东西,比如:burst test等。 这里不能一一详述,这里只说了一些和性能调优相关的东西。总之,性能测试是一细活和累活。

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

2.2查看操作系统负载

在排查 Linux 内核性能问题时,查看操作系统负载是关键的第一步。通过这一操作,我们能够了解系统当前的工作压力,进而找出可能存在的性能瓶颈。

利用 top 命令,我们可以实时查看系统的整体运行状态。在命令行中输入 “top”,随即会出现一个动态更新的界面,展示诸多重要信息。最上方的一行详细呈现了 CPU 的使用情况,其中包括用户空间进程使用的 CPU 时间百分比(us)、内核空间进程使用的 CPU 时间百分比(sy)、被调整优先级的用户进程使用的 CPU 时间(ni)、空闲的 CPU 时间百分比(id)、等待 I/O 操作完成的 CPU 时间百分比(wa)、硬件中断使用的 CPU 时间百分比(hi)、软件中断使用的 CPU 时间百分比(si)以及虚拟机管理程序等待其他虚拟 CPU 运行的时间百分比(st)。举例来说,如果发现 us 值较高,那就意味着用户空间的进程消耗了大量 CPU 资源,此时我们就需要深入检查这些进程,判断是否存在异常或低效的代码。

而 htop 命令则提供了更为直观和丰富的信息展示。它以可视化的方式呈现 CPU 的使用情况,不同颜色的条形图分别代表不同类型的 CPU 负载,如绿色表示用户进程使用的 CPU,红色表示内核进程使用的 CPU 等。同时,它还能清晰地显示每个进程的详细信息,包括进程 ID、用户、优先级、内存使用量等。当我们怀疑某个进程对系统性能产生较大影响时,使用 htop 能够更方便地定位和分析该进程。

SystemTap 是一款强大的动态内核和用户空间追踪工具。它允许我们编写脚本,对系统运行时的各种事件进行捕获和分析。假设我们想要了解某个特定进程在执行过程中对系统资源的占用情况,就可以编写一个 SystemTap 脚本,专门针对该进程进行监控。通过脚本,我们能够获取该进程的 CPU 利用率、内存访问频率等详细信息,从而精准定位问题所在。

LatencyTOP 则专注于检测系统中的延迟问题。在运行 LatencyTOP 后,它会分析系统中各个进程的延迟情况,并将延迟较高的进程凸显出来。例如,若 LatencyTOP 报告某个驱动程序进程的延迟过高,这可能表明该驱动程序存在性能缺陷,需要进一步优化或更新。

首先,当我们系统有问题的时候,我们不要急于去调查我们代码,这个毫无意义。我们首要需要看的是操作系统的报告。看看操作系统的CPU利用率,看看内存使用率,看看操作系统的IO,还有网络的IO,网络链接数,等等。Windows下的perfmon是一个很不错的工具,Linux下也有很多相关的命令和工具,比如:SystemTap,LatencyTOP,vmstat, sar, iostat, top, tcpdump等等 。通过观察这些数据,我们就可以知道我们的软件的性能基本上出在哪里。比如:

先看CPU利用率,如果CPU利用率不高,但是系统的Throughput和Latency上不去了,这说明我们的程序并没有忙于计算,而是忙于别的一些事,比如IO。(另外,CPU的利用率还要看内核态的和用户态的,内核态的一上去了,整个系统的性能就下来了。而对于多核CPU来说,CPU 0 是相当关键的,如果CPU 0的负载高,那么会影响其它核的性能,因为CPU各核间是需要有调度的,这靠CPU0完成)
然后,我们可以看一下IO大不大,IO和CPU一般是反着来的,CPU利用率高则IO不大,IO大则CPU就小。关于IO,我们要看三个事,一个是磁盘文件IO,一个是驱动程序的IO(如:网卡),一个是内存换页率。这三个事都会影响系统性能。
然后,查看一下网络带宽使用情况,在Linux下,你可以使用iftop, iptraf, ntop, tcpdump这些命令来查看。或是用Wireshark来查看。
如果CPU不高,IO不高,内存使用不高,网络带宽使用不高。但是系统的性能上不去。这说明你的程序有问题,比如,你的程序被阻塞了。可能是因为等那个锁,可能是因为等某个资源,或者是在切换上下文。
通过了解操作系统的性能,我们才知道性能的问题,比如:带宽不够,内存不够,TCP缓冲区不够,等等,很多时候,不需要调整程序的,只需要调整一下硬件或操作系统的配置就可以了。

2.3使用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这样的工具会在你的代码中插入很多代码,会导致你的程序运行效率变低,从而没发测试出在高吞吐量下的系统的性能,对此,一般有两个方法来定位系统瓶颈:

在你的代码中自己做统计,使用微秒级的计时器和函数调用计算器,每隔10秒把统计log到文件中。
分段注释你的代码块,让一些函数空转,做Hard Code的Mock,然后再测试一下系统的Throughput和Latency是否有质的变化,如果有,那么被注释的函数就是性能瓶颈,再在这个函数体内注释代码,直到找到最耗性能的语句。
最后再说一点,对于性能测试,不同的Throughput会出现不同的测试结果,不同的测试数据也会有不同的测试结果。所以,用于性能测试的数据非常重要,性能测试中,我们需要观测试不同Throughput的结果。

2.4剖析 IO 情况

IO 操作在系统性能中扮演着举足轻重的角色,对其进行深入剖析,能帮助我们发现许多潜在的性能问题。

在磁盘文件 IO 方面,iostat 命令是我们的得力助手。通过执行 “iostat -d -x 1”(其中 “-d” 表示仅显示磁盘相关信息,“-x” 表示显示更详细的信息,“1” 表示每隔 1 秒输出一次数据),我们可以获取到每个磁盘设备的详细统计信息。其中,“r/s” 代表每秒完成的读次数,“w/s” 代表每秒完成的写次数,“rkB/s” 表示每秒读数据量,“wkB/s” 表示每秒写数据量,“% util” 则表示设备的繁忙程度。当 “% util” 接近 100% 时,说明磁盘 I/O 系统已经满负荷运转,很可能成为系统性能的瓶颈。比如,在一个数据存储服务器中,如果发现某个磁盘的 “% util” 长期保持在 95% 以上,那么就需要考虑对该磁盘进行优化,如更换为更高性能的磁盘,或者对数据存储方式进行调整。

iotop 命令能够让我们清晰地看到哪些进程正在大量占用磁盘 I/O 资源。执行 “iotop” 后,会列出各个进程的 I/O 使用情况,包括读速率、写速率等。当我们发现某个进程的 I/O 读写速率异常高时,就需要进一步分析该进程的业务逻辑,看是否存在不合理的 I/O 操作。例如,某个备份进程在进行数据备份时,采用了频繁的小文件读写方式,这可能会导致磁盘 I/O 性能下降,此时可以考虑优化备份策略,采用大文件读写或者批量操作的方式。

驱动程序 IO 对系统性能的影响也不容小觑。如果驱动程序存在问题,可能会导致设备与系统之间的数据传输效率低下。以网络驱动程序为例,若驱动程序版本过旧,可能无法充分发挥网卡的性能,导致网络传输速度受限。在这种情况下,及时更新驱动程序往往能显著提升系统性能。

内存换页率也是衡量系统性能的重要指标。vmstat 命令可以帮助我们查看内存换页情况。在输出结果中,“si” 表示从磁盘交换到内存的交换页数量,“so” 表示从内存交换到磁盘的交换页数量。如果 “si” 和 “so” 的值较大,说明系统频繁进行内存换页操作,这会严重影响系统性能。通常,这可能是由于内存不足导致的。比如,在一个运行多个大型应用程序的服务器上,如果发现内存换页率过高,就需要考虑增加物理内存,或者对应用程序的内存使用进行优化。

2.5洞察网络带宽使用

在网络环境中,了解网络带宽的使用情况对于定位性能瓶颈至关重要。

iftop 是一款出色的实时流量监控工具。通过执行 “iftop -i eth0”(其中 “-i” 指定要监测的网卡,“eth0” 是常见的网卡名称),我们可以直观地看到指定网卡的实时流量情况。在 iftop 的界面中,会清晰地显示出各个 IP 地址之间的流量传输情况,包括发送流量(TX)、接收流量(RX)以及总流量(TOTAL)。同时,还能看到不同时间段的平均流量,如过去 2 秒、10 秒、40 秒的平均流量。这有助于我们快速发现哪些 IP 地址之间的流量过大,从而判断是否存在网络瓶颈。例如,在一个企业网络中,如果发现某个 IP 地址与外部服务器之间的流量持续超过网络带宽的 80%,那么就需要进一步检查该 IP 地址对应的业务,看是否存在数据传输异常的情况。

iptraf 同样是一款功能强大的网络流量监测工具。它不仅可以实时监测网络接口的流量,还能提供详细的网络连接信息,如 TCP 连接、UDP 连接等。通过 “iptraf -g” 命令,我们可以进入图形化界面,方便地查看各个网络接口的流量统计信息。此外,iptraf 还支持按协议类型查看流量,这对于分析网络中不同协议的使用情况非常有帮助。比如,在一个以 HTTP 协议为主的网络环境中,如果发现 HTTP 流量占比过高,导致其他业务的网络带宽受到挤压,那么就可以考虑对 HTTP 业务进行优化,如采用缓存技术、优化页面加载方式等,以减少网络带宽的占用。

三、性能调优的方法

下面这些东西是我所经历过的一些问题,也许并不全,也许并不对,大家可以补充指正,我纯属抛砖引玉。一般来说,性能优化也就是下面的几个策略:

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

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

3.1算法调优

算法非常重要,好的算法会有更好的性能。举几个我经历过的项目的例子,大家可以感觉一下。

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

3.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(还整理内存碎片),他就像“恐龙特级克赛号”一样,他运行的时候,整个世界的时间都停止了。

3.3网络调优

关于网络调优,尤其是TCP Tuning(你可以以这两个关键词在网上找到很多文章),这里面有很多很多东西可以说。看看Linux下TCP/IP的那么多参数就知道了(顺便说一下,你也许不喜欢Linux,但是你不能否认Linux给我们了很多可以进行内核调优的权力)。

⑴TCP调优

我们知道TCP链接是有很多开销的,一个是会占用文件描述符,另一个是会开缓存,一般来说一个系统可以支持的TCP链接数是有限的,我们需要清楚地认识到TCP链接对系统的开销是很大的。正是因为TCP是耗资源的,所以,很多攻击都是让你系统上出现大量的TCP链接,把你的系统资源耗尽。比如著名的SYNC Flood攻击。

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

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服务器上。对此,有两个参数需要注意:

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的带宽使用率会马上受到影响(会盲目减半),再丢包,再减半,然后如果不丢包了,就逐步恢复。相关参数如下:

net.core . wmem _default = 8388608
net. core . rmem_ default = 8388608
net. core. rmem max = 16777216
net. core .wmem max = 16777216

传所有的数据,反而影响网络性能。(当然,网络差的情况下,就别玩什么高性能了) 所以,高性能的网络重要的是要让网络丢包率非常非常地小(基本上是用在LAN里),如果网络基本是可信的,这样用大一点的buffer会有更好的网络传输性能(来来回回太多太影响性能了)。

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

⑵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多播,这个技术对于你需要在内网里通知多台结点时非常方便和高效。而且,多播这种技术对于机会的水平扩展(需要增加机器来侦听多播信息)也很有利。

⑶网卡调优

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

⑷其它网络性能

关于多路复用技术,也就是用一个线程来管理所有的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,某些用户或文件相关的系统调用就很慢,所以要小心。

3.4系统调优

⑴I/O模型

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

  1. 第一种,同步阻塞式I/O,这个不说了。
  2. 第二种,同步无阻塞方式。其通过fctnl设置 O_NONBLOCK 来完成。
  3. 第三种,对于select/poll/epoll这三个是I/O不阻塞,但是在事件上阻塞,算是:I/O异步,事件同步的调用。
  4. 第四种,AIO方式。这种I/O 模型是一种处理与 I/O 并行的模型。I/O请求会立即返回,说明请求已经成功发起了。在后台完成I/O操作时,向应用程序发起通知,通知有两种方式:一种是产生一个信号,另一种是执行一个基于线程的回调函数来完成这次 I/O 处理过程。
  5. 第四种因为没有任何的阻塞,无论是I/O上,还是事件通知上,所以,其可以让你充分地利用CPU,比起第二种同步无阻塞好处就是,第二种要你一遍一遍地去轮询。Nginx之所所以高效,是其使用了epoll和AIO的方式来进行I/O的。

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

  1. a)一个是WriteFile系统调用,这个系统调用可以是同步阻塞的,也可以是同步无阻塞的,关于看文件是不是以Overlapped打开的。关于同步无阻塞,需要设置其最后一个参数Overlapped,微软叫Overlapped I/O,你需要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。
  2. b)另一个叫WriteFileEx的系统调用,其可以实现异步I/O,并可以让你传入一个callback函数,等I/O结束后回调之, 但是这个回调的过程Windows是把callback函数放到了APC(Asynchronous Procedure Calls)的队列中,然后,只用当应用程序当前线程成为可被通知状态(Alterable)时,才会被回调。只有当你的线程使用了这几个函数时WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx, SignalObjectAndWait 和 SleepEx,线程才会成为Alterable状态。可见,这个模型,还是有wait,所以性能也不高。
  3. c)然后是IOCP – IO Completion Port,IOCP会把I/O的结果放在一个队列中,但是,侦听这个队列的不是主线程,而是专门来干这个事的一个或多个线程去干(老的平台要你自己创建线程,新的平台是你可以创建一个线程池)。IOCP是一个线程池模型。这个和Linux下的AIO模型比较相似,但是实现方式和使用方式完全不一样。
    当然,真正提高I/O性能方式是把和外设的I/O的次数降到最低,最好没有,所以,对于读来说,内存cache通常可以从质上提升性能,因为内存比外设快太多了。对于写来说,cache住要写的数据,少写几次,但是cache带来的问题就是实时性的问题,也就是latency会变大,我们需要在写的次数上和相应上做权衡。

⑵多核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,如:

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

⑶文件系统调优

关于文件系统,因为文件系统也是有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日志文件系统及性能分析》

3.5数据库调优

数据库调优并不是我的强项,我就仅用我非常有限的知识说上一些吧。注意,下面的这些东西并不一定正确,因为在不同的业务场景,不同的数据库设计下可能会得到完全相反的结论,所以,我仅在这里做一些一般性的说明,具体问题还要具体分析。

⑴数据库引擎调优

我对数据库引擎不是熟,但是有几个事情我觉得是一定要去了解的。

数据库的锁的方式。这个非常非常地重要。并发情况下,锁是非常非常影响性能的。各种隔离级别,行锁,表锁,页锁,读写锁,事务锁,以及各种写优先还是读优先机制。性能最高的是不要锁,所以,分库分表,冗余数据,减少一致性事务处理,可以有效地提高性能。NoSQL就是牺牲了一致性和事务处理,并冗余数据,从而达到了分布式和高性能。

数据库的存储机制。不但要搞清楚各种类型字段是怎么存储的,更重要的是数据库的数据存储方式,是怎么分区的,是怎么管理的,比如Oracle的数据文件,表空间,段,等等。了解清楚这个机制可以减轻很多的I/O负载。比如:MySQL下使用show engines;可以看到各种存储引擎的支持。不同的存储引擎有不同的侧重点,针对不同的业务或数据库设计会让你有不同的性能。

数据库的分布式策略。最简单的就是复制或镜像,需要了解分布式的一致性算法,或是主主同步,主从同步。通过了解这种技术的机理可以做到数据库级别的水平扩展。

⑵SQL语句优化

关于SQL语句的优化,首先也是要使用工具,比如:MySQL SQL Query Analyzer,Oracle 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。
其它。

  1. 不要select *,而是明确指出各个字段,如果有多个表,一定要在字段名前加上表名,不要让引擎去算。
  2. 不要用Having,因为其要遍历所有的记录。性能差得不能再差。
  3. 尽可能地使用UNION ALL 取代 UNION。
  4. 索引过多,insert和delete就会越慢。而update如果update多数索引,也会慢,但是如果只update一个,则只会影响一个索引表。
    等等。

四、内核性能调优实战技巧

4.1内存相关参数调整

在 Linux 系统中,内存管理对于系统性能起着举足轻重的作用。合理调整内存相关参数,能够显著提升系统的运行效率和稳定性。*

vm.swappiness 是一个关键的内存参数,它主要用于控制系统在内存不足时将页面交换到磁盘交换空间(swap)的倾向程度 。该参数的取值范围是 0 - 100,默认值为 60。当 vm.swappiness 的值设置得较高时,比如接近 100,系统会更频繁地使用磁盘交换空间。这在物理内存不足的情况下,虽然能暂时满足系统对内存的需求,但由于磁盘 I/O 操作的速度远远慢于内存访问速度,会导致系统性能大幅下降。相反,如果将 vm.swappiness 的值设置得较低,如 10 或 20,系统则会尽量避免使用交换空间,优先使用物理内存。这对于那些对性能要求较高、内存使用频繁的应用场景来说非常重要。例如,在一个运行着数据库服务器的 Linux 系统中,如果数据库操作频繁且对响应速度要求极高,将 vm.swappiness 设置为 10,可以减少磁盘交换操作,提高数据库的读写性能,从而提升整个系统的响应速度。调整 vm.swappiness 参数的方法很简单,我们可以通过修改 /etc/sysctl.conf 文件来实现。在该文件中添加或修改 “vm.swappiness = [想要的值]” 这一行,然后执行 “sysctl -p” 命令使配置生效。

vm.overcommit_memory 参数则控制系统是否允许超额分配内存 。它有三个可选值:0、1 和 2。当取值为 0 时,这是系统的默认设置,内核会尝试估算当前系统剩余的可用内存,只有在估算认为内存分配请求不会导致系统内存不足时,才会允许分配内存。当取值为 1 时,内核会允许超量使用内存,直到物理内存被耗尽为止。这种设置适用于那些对内存需求有明确预估,且在内存使用上比较保守的应用场景。比如在一些科学计算任务中,应用程序能够准确控制自身的内存使用量,设置为 1 可以充分利用系统内存资源,提高计算效率。当取值为 2 时,内核会采用一种严格的内存分配算法,确保系统的整个内存地址空间(包括物理内存和交换空间)不会超过 “swap + 50% 的 RAM 值”。这是一种非常保守的设置,能有效防止系统因内存过度分配而崩溃。调整 vm.overcommit_memory 参数同样可以通过修改 /etc/sysctl.conf 文件来完成。在文件中添加或修改 “vm.overcommit_memory = [想要的值]”,然后执行 “sysctl -p” 命令使更改生效。

4.2网络相关参数调整

在当今网络互联的时代,网络性能的优劣直接影响着系统的整体表现。通过合理调整网络相关的内核参数,能够有效提升网络的吞吐量、降低延迟,确保系统在网络通信方面的高效稳定。

net.core.somaxconn 参数定义了 TCP 连接的最大排队数量 ,也就是当服务器在监听某个端口时,处于等待状态的最大 TCP 连接请求数。其默认值通常为 128,在一些高并发的网络应用场景中,这个值可能显得过小。比如在一个大型的电商网站服务器上,在促销活动期间,大量用户同时访问服务器,发起海量的 TCP 连接请求。如果 net.core.somaxconn 的值仍然保持默认的 128,那么当等待连接的请求数超过这个值时,后续的连接请求就可能会被丢弃,导致用户无法正常访问网站,严重影响用户体验和业务的正常开展。

为了应对这种高并发的情况,我们可以根据服务器的实际性能和预估的并发连接数,将 net.core.somaxconn 的值适当增大,比如设置为 1024 或更高。这样可以让服务器能够容纳更多的等待连接请求,避免因连接队列溢出而造成的连接失败问题。要调整 net.core.somaxconn 参数,可以编辑 /etc/sysctl.conf 文件,添加或修改 “net.core.somaxconn = [想要的值]” 这一行,之后执行 “sysctl -p” 命令使新的配置生效。

net.ipv4.tcp_syncookies 是一个用于应对 SYN 洪水攻击的重要参数 。SYN 洪水攻击是一种常见的网络攻击方式,攻击者通过向目标服务器发送大量伪造的 SYN 请求,耗尽服务器的连接资源,从而使服务器无法正常处理合法的连接请求。当 net.ipv4.tcp_syncookies 设置为 1 时,系统启用 syncookies 机制。在这种机制下,当服务器接收到 SYN 请求时,如果发现 SYN 队列已满,它不会直接丢弃该请求,而是根据接收到的 SYN 包中的信息计算出一个特殊的 cookie 值,并将其作为 SYN + ACK 包的序列号发送给客户端。

客户端在收到 SYN + ACK 包后,会将这个 cookie 值包含在 ACK 包中回传给服务器。服务器通过验证这个 cookie 值,来确认该连接请求的合法性,从而在不占用过多系统资源的情况下,有效地抵御 SYN 洪水攻击。而当 net.ipv4.tcp_syncookies 设置为 0 时,系统则不启用 syncookies 机制。对于那些面临较高网络安全风险,尤其是可能遭受 SYN 洪水攻击的服务器来说,将 net.ipv4.tcp_syncookies 设置为 1 是一个非常必要的安全措施。与前面的参数调整方法类似,我们可以通过修改 /etc/sysctl.conf 文件,添加或修改 “net.ipv4.tcp_syncookies = 1” 这一行,并执行 “sysctl -p” 命令来使设置生效。

4.3文件系统相关参数调整

文件系统是 Linux 系统中数据存储和管理的核心部分,其性能的好坏直接关系到系统对文件的读写效率,进而影响整个系统的运行速度。通过优化文件系统相关的内核参数,可以显著提升文件系统的性能,满足不同应用场景下对文件操作的高效需求。

fs.file - max 参数用于指定系统中所有进程总共能够打开的最大文件句柄数量 。文件句柄是系统用于标识和管理打开文件的一种资源,每个进程在进行文件操作时,都需要获取相应的文件句柄。在一些大规模的数据处理应用场景中,例如一个数据仓库系统,可能需要同时处理大量的文件,包括读取数据文件进行分析、写入结果文件等操作。如果 fs.file - max 的值设置得过低,当进程打开的文件句柄数量达到这个上限时,后续的文件打开操作就会失败,导致应用程序无法正常运行。为了确保这类应用能够顺利进行,我们需要根据实际的业务需求和系统资源情况,合理地增大 fs.file - max 的值。例如,如果系统的内存资源充足,且预计在高峰时期需要同时打开数万个文件句柄,那么可以将 fs.file - max 设置为一个较大的值,如 1048576。调整 fs.file - max 参数的方式是在 /etc/sysctl.conf 文件中添加或修改 “fs.file - max = [想要的值]” 这一行,然后执行 “sysctl -p” 命令,使新的配置生效,让系统能够支持更多的文件句柄打开操作。

fs.aio - max - nr 参数主要控制着系统中允许的并发异步 I/O 请求的最大数量 。异步 I/O 是一种高效的文件 I/O 操作方式,它允许应用程序在发起 I/O 请求后,无需等待 I/O 操作完成,就可以继续执行其他任务,从而提高系统的并发处理能力和整体性能。在一些对 I/O 操作性能要求极高的场景中,如数据库的读写操作、大数据的实时处理等,大量的并发异步 I/O 请求能够充分利用系统资源,加快数据的传输速度。如果 fs.aio - max - nr 的值设置得过小,那么系统能够同时处理的异步 I/O 请求数量就会受到限制,无法充分发挥异步 I/O 的优势。例如,在一个高性能的数据库服务器中,可能需要同时处理成千上万的并发异步 I/O 请求来满足大量用户的读写需求。此时,将 fs.aio - max - nr 设置为一个较大的值,如 102400,能够确保系统有足够的能力处理这些并发请求,提高数据库的响应速度和吞吐量。要调整 fs.aio - max - nr 参数,同样需要编辑 /etc/sysctl.conf 文件,在其中添加或修改 “fs.aio - max - nr = [想要的值]”,之后执行 “sysctl -p” 命令,使系统按照新的配置来管理并发异步 I/O 请求。

五、调优案例深度剖析

5.1案例背景介绍

某在线教育平台,随着业务的迅猛发展,用户数量呈现爆发式增长。原本运行流畅的系统,在高并发的访问压力下,逐渐暴露出性能问题。用户反馈在观看课程视频时,经常出现卡顿现象,视频加载缓慢,甚至有时会出现长时间无法加载的情况。在进行课程互动,如提交作业、参与讨论等操作时,响应时间也明显变长,严重影响了用户的学习体验。

该平台的服务器基于 Linux 系统搭建,采用了常见的 LAMP 架构(Linux + Apache + MySQL + PHP)。面对日益严峻的性能挑战,平台的技术团队决定深入排查问题,并对 Linux 内核进行性能调优,以提升系统的整体性能和稳定性。

5.2问题排查过程

技术团队首先对系统的运行日志进行了详细分析。通过查看 Apache 服务器的日志,发现大量的请求超时记录,这表明服务器在处理用户请求时遇到了困难,无法及时响应。同时,MySQL 数据库的日志中也出现了一些慢查询记录,这意味着数据库的查询性能可能受到了影响。

为了进一步确定性能瓶颈所在,团队使用了 top 命令来实时监控系统的资源使用情况。结果发现,CPU 的使用率长时间保持在高位,尤其是在用户访问高峰期,几乎达到了 100%。通过分析 top 命令的输出,发现一些与视频处理和数据库查询相关的进程占用了大量的 CPU 资源。

接着,团队使用 iostat 命令来检查磁盘 I/O 情况。从输出结果可以看出,磁盘的读写速度较慢,尤其是在读取视频文件时,磁盘的繁忙程度(% util)接近 100%,这表明磁盘 I/O 可能成为了系统性能的瓶颈。

在网络方面,团队使用 iftop 命令来监控网络带宽的使用情况。发现网络带宽在高并发情况下被大量占用,尤其是视频传输所占用的带宽较大,导致其他业务的网络请求受到影响。

5.3调优措施实施

针对排查出的问题,技术团队采取了一系列针对性的调优措施。

在 CPU 方面,对一些与视频处理相关的进程进行了优化,通过调整算法和代码逻辑,减少了不必要的计算量。同时,启用了 CPU 的多核特性,将一些任务分配到不同的核心上并行处理,提高了 CPU 的利用率。

对于磁盘 I/O 问题,将存储视频文件的磁盘更换为更高性能的固态硬盘(SSD),显著提升了磁盘的读写速度。此外,对数据库的查询语句进行了优化,减少了不必要的磁盘访问,并添加了适当的索引,加快了数据的检索速度。

在网络方面,对视频传输进行了优化,采用了流媒体技术,实现了视频的分段传输和缓存,减少了网络带宽的占用。同时,调整了网络相关的内核参数,如增大了 net.core.somaxconn 的值,以提高服务器能够处理的并发连接数。

5.4调优效果展示

经过一系列的调优措施实施后,系统的性能得到了显著提升。视频加载速度明显加快,卡顿现象几乎消失,用户在观看课程视频时能够享受到流畅的体验。在课程互动方面,提交作业、参与讨论等操作的响应时间大幅缩短,用户能够及时得到反馈。

从性能指标上看,系统的吞吐量得到了显著提高,在相同的时间内能够处理更多的用户请求。系统延迟也明显降低,平均响应时间从原来的数秒缩短到了 1 秒以内。CPU 的使用率在高并发情况下也能够保持在合理范围内,不再出现长时间满载的情况。磁盘 I/O 的性能得到了极大改善,磁盘的繁忙程度(% util)始终保持在较低水平。网络带宽的使用更加合理,各业务之间的网络请求能够得到有效的保障。

通过这次 Linux 内核性能调优,该在线教育平台成功应对了业务量增长带来的挑战,为用户提供了更加优质的服务,同时也为平台的持续发展奠定了坚实的基础。


深度Linux
1 声望0 粉丝