如何分析 Linux 系统的性能瓶颈?有哪些工具可以使用?
-
top:查看系统当前运行的进程以及资源占用情况。
-
vmstat:报告虚拟内存统计信息,包括系统、进程和块设备等方面的信息。
-
iostat:监视系统I/O活动,包括磁盘、Tty、CPU等指标。
-
sar:收集、报告和保存系统活动信息,如CPU利用率、内存使用量等。
-
perf:Linux性能事件采集工具,可进行高级性能分析。
-
strace:跟踪系统调用和信号,帮助排查应用程序中出现的问题。
-
tcpdump:抓取网络数据包,用于诊断网络相关性能问题。
-
dmesg:显示内核环缓冲区中的消息,提供有关硬件和驱动程序错误的信息。
在 Linux 内核开发中,有哪些方法可以提高系统的并发性能?
优化内核参数:
调整文件句柄限制:Linux 内核对于每个进程都有一个文件描述符数组,在高并发场景下,进程所需的文件描述符数量会增加,需适当调整文件句柄数量,增加系统可同时打开的文件数。例如,使用ulimit -n 65535命令来增加文件句柄限制,也可以通过修改/etc/security/limits.conf文件来调整,如* soft nproc 65535和* hard nproc 65535分别设置每个用户可创建的软进程数量和硬进程数量限制为 65535。
调整进程数量限制:在高并发场景下,可能需要适当调整进程数量限制。同样通过修改/etc/security/limits.conf文件,如* soft nproc 65535和* hard nproc 65535来将每个用户可创建的进程数量修改为 65535。
调整 tcp 参数:
-
net.ipv4.tcp_max_syn_backlog:指定 syn 队列的最大长度,适当增大可提高应对大量连接请求的能力。
-
net.ipv4.tcp_syncookies:启用 syncookies 机制,防止 syn flood 攻击。
-
net.ipv4.tcp_syn_retries和net.ipv4.tcp_synack_retries:分别指定 syn 重试次数和 syn/ack 重试次数。
-
net.ipv4.tcp_fin_timeout:指定 tcp 连接关闭的超时时间,根据实际情况适当缩短,可更快释放资源。
-
net.ipv4.tcp_tw_reuse:启用 time-wait 状态的连接重用,允许在安全的情况下将处于 time-wait 状态的连接重新用于新的连接,提高连接的利用率。
-
net.ipv4.tcp_tw_recycle:启用 time-wait 状态的连接回收,加快 time-wait 状态的连接的回收速度,但在一些网络环境中可能会导致问题,需谨慎使用。
-
net.ipv4.tcp_max_tw_buckets:指定 time-wait 状态的连接的最大数量,防止过多的 time-wait 连接占用系统资源。
-
net.ipv4.tcp_rmem和net.ipv4.tcp_wmem:分别用于指定接收缓冲区和发送缓冲区的大小,根据网络带宽和应用需求进行调整,增大缓冲区大小可提高网络数据的传输效率,但也可能增加内存占用,需权衡考虑。
-
net.core.somaxconn:指定套接字的最大连接数,增大该值可提高系统的并发连接处理能力。
-
net.core.netdev_max_backlog:指定网络设备接收队列的最大长度,当网络流量较大时,适当增大可减少数据包丢失。
调整内存参数
vm.min_free_kbytes:指定系统中最小的空闲内存大小,对于高并发服务,建议将这个值调整为较高的数值,例如 65536(64MB),以确保系统有足够的空闲内存用于处理突发的内存需求。
vm.swappiness:控制系统使用 swap 分区的程度,取值范围是 0 到 100,值越小,内核越不倾向于使用 swap 分区。对于高并发服务,建议将这个值调整为较低的数值,例如 10,以减少页面交换的频率,提高内存使用效率。但如果物理内存非常小,过度降低该值可能会导致内存不足,系统性能下降,所以需要根据实际内存情况进行调整。
vm.dirty_ratio和vm.dirty_background_ratio:这两个参数用来控制脏页(即被修改但尚未被写回磁盘的页)的数量。当系统中的脏页数量超过这两个参数指定的阈值时,系统会触发写回操作。对于高并发服务,建议将这两个值调整为较低的数值,例如vm.dirty_ratio = 5和vm.dirty_background_ratio = 2,这样可以增加脏页的数量,提高写入磁盘的效率,同时也能降低文件系统缓存的压力,减少因大量脏页写回磁盘而导致的性能波动,但设置过低可能会导致数据丢失风险增加,需根据数据重要性和系统稳定性要求进行权衡。
使用合适的调度策略
选择合适的进程调度算法:Linux 内核提供了多种进程调度算法,如完全公平调度算法(CFS)、实时调度算法等。对于高并发场景,CFS 通常是一个不错的选择,它可以公平地分配 CPU 时间给各个进程,确保每个进程都能得到合理的执行机会。但在某些对实时性要求极高的场景下,可能需要使用实时调度算法,并根据任务的优先级进行调度,以保证关键任务能够及时得到处理。
设置进程优先级:通过nice值和renice命令可以调整进程的优先级。较高优先级的进程会获得更多的 CPU 时间片,从而更快地执行。对于高并发系统中的关键任务或对响应时间敏感的任务,可以适当提高其优先级,使其能够更快地得到处理,但需注意不要过度提高某些进程的优先级,以免导致其他进程长时间得不到执行,影响系统整体的公平性和稳定性。
利用多核处理器:现代服务器通常配备多核处理器,Linux 内核支持多核并行处理。在开发应用程序时,应尽可能地利用多核优势,将任务分配到不同的 CPU 核心上并行执行,例如使用多线程或多进程编程模型。同时,内核也提供了一些调度策略和参数来优化多核处理器的使用,如cpu affinity(CPU 亲和性)设置,可以将特定的进程或线程绑定到指定的 CPU 核心上,减少 CPU 核心之间的任务切换开销,提高缓存命中率,从而进一步提高系统的并发性能。
优化文件系统
选择合适的文件系统类型:不同的文件系统在性能、功能和适用场景上有所差异。例如,ext4 文件系统是 Linux 系统中常用的文件系统,具有较好的稳定性和兼容性;而 XFS 文件系统在处理大文件和高并发写入方面表现出色,适合用于数据库服务器等对磁盘性能要求较高的场景;Btrfs 文件系统则提供了更多的高级功能,如快照、数据校验等,但在某些情况下可能性能不如 ext4 或 XFS。根据系统的具体需求和应用场景,选择合适的文件系统类型可以提高文件系统的读写性能和并发处理能力。
优化文件系统缓存:文件系统缓存是为了加速文件的读写操作而在内存中保留的一部分数据。通过调整内核参数vm.dirty_ratio、vm.dirty_background_ratio和vm.min_free_kbytes等,可以优化文件系统缓存的使用。增大vm.dirty_ratio可以增加脏页的数量,提高写入磁盘的效率;调大vm.dirty_background_ratio可以增加后台写入磁盘的速度,降低文件系统缓存的压力;而适当增加vm.min_free_kbytes可以确保系统有足够的空闲内存用于缓存文件数据,提高文件读取的速度。此外,还可以使用tmpfs(临时文件系统)将一些经常访问的文件或数据存储在内存中,以加快访问速度,但需要注意tmpfs占用的内存空间,避免因过度使用导致内存不足。
合理设置文件系统挂载选项:在挂载文件系统时,可以根据具体需求设置一些挂载选项来优化性能。例如,使用noatime选项可以避免每次文件访问时都更新文件的访问时间,减少磁盘 I/O 操作;使用nodiratime选项可以避免更新目录的访问时间,对于大量目录操作的场景可以提高性能;使用async选项可以使文件系统的写入操作异步进行,加快文件写入速度,但可能会增加数据丢失的风险(在系统突然崩溃或断电时),需要根据数据的重要性和系统的可靠性要求来权衡使用。
利用中断处理机制
中断共享:多个设备可以共享同一个中断号。通过适当配置中断共享,可以合理分配中断处理程序的执行时间,提高系统性能。例如,在一些设备数量较多但中断资源有限的系统中,可以将多个具有相似特性或较低中断频率的设备共享一个中断号,然后在中断处理程序中通过设备标识或其他方式区分不同设备的中断请求,并进行相应的处理。这样可以减少中断号的使用数量,提高中断资源的利用率,同时也能降低中断处理的开销,提高系统的并发处理能力。
中断处理程序优化:编写高效的中断处理程序是提高性能的关键。中断处理程序应尽可能地快速执行,并且避免阻塞或长时间占用 CPU。可以使用一些优化技术,如减少不必要的操作、使用异步处理、合理设置中断上下文等。例如,在中断处理程序中,只进行必要的关键操作,将一些耗时较长或可以延迟处理的任务放到中断处理程序之外的工作队列或线程中进行异步处理,避免中断中断处理程序长时间占用 CPU 而影响其他中断的响应和处理。此外,合理设置中断上下文,如保存和恢复关键寄存器的值、避免在中断处理程序中进行复杂的内存分配等操作,也可以提高中断处理的效率。
中断控制器调优:中断控制器负责管理系统的中断资源。通过合理配置中断控制器,可以提高中断处理的效率。可以考虑调整中断优先级、设置中断触发方式(边沿触发或电平触发)等。例如,对于一些对实时性要求较高的设备,可以将其对应的中断优先级设置为较高的值,确保它们的中断请求能够及时得到处理;根据设备的特性和实际需求,选择合适的中断触发方式,如对于一些快速变化的信号源,使用边沿触发可以更准确地捕捉中断事件,而对于一些电平持续时间较长的设备,使用电平触发可能更为合适。
中断亲和性设置:在多核系统中,可以设置中断亲和性来优化中断的处理。中断亲和性决定了中断处理程序运行在哪个 CPU 核心上,可以通过将中断处理程序绑定到某个特定的 CPU 核心,降低中断处理程序的上下文切换开销。例如,对于一些频繁产生中断的设备,可以将其中断处理程序绑定到一个特定的 CPU 核心上,使得该中断处理程序始终在同一个 CPU 核心上执行,避免了因中断处理程序在不同 CPU 核心之间切换而导致的上下文切换开销,提高中断处理的速度和效率。同时,结合 CPU 核心的负载均衡策略,可以更好地分配中断处理任务,充分发挥多核系统的性能优势。
采用异步 I/O 机制
使用内核异步 I/O 函数接口:Linux 内核提供了真正的异步 I/O 函数接口,如io_setup、io_submit、io_getevents和io_destroy等。通过这些函数,可以实现异步的文件读写操作。与传统的同步 I/O 相比,异步 I/O 允许应用程序在发起 I/O 操作后立即返回,而不用等待 I/O 操作完成,从而可以继续执行其他任务,提高系统的并发性能。例如,在一个网络服务器应用中,当需要读取大量文件并发送给客户端时,可以使用异步 I/O 方式同时发起多个文件的读取操作,而无需等待每个文件读取完成后再进行下一步操作,这样可以在同一时间内处理更多的客户端请求,提高服务器的并发处理能力。
结合 epoll 等 I/O 多路复用技术:通常,异步 I/O 会和 epoll 等 I/O 多路复用技术配合使用。epoll 可以高效地管理多个文件描述符的 I/O 事件,当使用异步 I/O 时,通过 epoll 来监听一个可以通知异步 I/O 完成的描述符(如使用eventfd函数获得的描述符)。当异步 I/O 操作完成后,epoll 会收到通知,然后应用程序可以通过io_getevents函数获取已经完成的异步 I/O 事件,并进行相应的处理。这种结合方式可以充分发挥异步 I/O 和 epoll 的优势,既能实现高效的 I/O 事件管理,又能利用异步操作提高并发性能,适用于高并发的网络服务、大规模文件处理等场景。
优化网络性能
调整网络缓冲区大小:网络缓冲区的大小会影响网络性能。可以通过调整内核参数net.core.rmem_max(接收缓冲区的最大大小)、net.core.wmem_max(发送缓冲区的最大大小)、net.core.rmem_default(接收缓冲区的默认大小)和net.core.wmem_default(发送缓冲区的默认大小)来优化网络缓冲区。例如,对于高带宽、高并发的网络环境,可以适当增大接收和发送缓冲区的大小,以减少数据包的丢失和提高网络传输效率,但过大的缓冲区也可能会导致内存占用过多,需要根据实际网络情况和系统资源进行权衡调整。
优化网络协议栈
启用 TCP 窗口缩放:TCP 窗口缩放可以增加 TCP 窗口的大小,从而允许在网络连接上传输更多的数据,提高数据传输的速度和效率。在高并发场景下,启用 TCP 窗口缩放可以更好地利用网络带宽,减少数据传输的延迟。可以通过修改内核参数net.ipv4.tcp_window_scaling = 1来启用 TCP 窗口缩放(默认情况下可能已经启用)。
调整 TCP 拥塞控制算法:Linux 内核支持多种 TCP 拥塞控制算法,如 Cubic、Reno 等。不同的拥塞控制算法适用于不同的网络环境和应用场景。例如,Cubic 算法在高带宽、高延迟的网络环境中表现较好,而 Reno 算法在低带宽、低延迟的网络环境中较为适用。可以根据实际网络情况选择合适的 TCP 拥塞控制算法,通过修改内核参数net.ipv4.tcp_congestion_control = "算法名称"来进行切换。例如,net.ipv4.tcp_congestion_control = "cubic"启用 Cubic 算法。
减少网络延迟:可以通过调整一些内核参数来减少网络延迟,如net.ipv4.tcp_slow_start_after_idle = 0(关闭 TCP 连接空闲后的慢启动)、net.ipv4.tcp_no_metrics_save = 1(不保存未使用的连接的度量数据)等。这些参数的调整可以减少 TCP 连接建立和数据传输过程中的一些不必要的延迟,提高网络响应速度。
使用大页内存(Hugepages):Hugepages 是 Linux 内核提供的一种功能,可以将大块的内存页面映射到物理内存,减少页表的开销,提高内存访问效率。对于网络应用程序,特别是那些需要频繁进行内存访问和数据处理的程序,使用 Hugepages 可以显著提高性能。需要在系统启动时配置内核参数,指定 Hugepages 的大小和数量。例如,通过在启动参数中添加hugepagesz=2M hugepages=1024(假设使用 2MB 大小的 Hugepages,分配 1024 个页面)来启用 Hugepages,并在应用程序中进行相应的配置,使其使用 Hugepages 进行内存分配。
使用锁机制优化
读写锁:读写锁将读和写操作分开,允许多个线程同时对一个队列进行读操作,而在有写操作时,其他读操作和写操作必须等待。这种机制在多读少写的场景中,如数据库操作,可以提高效率。
多队列缓冲技术:在实际项目中,经常遇到一个线程读数据到队列,另一个线程从队列中取数据的情况。如果两个线程操作太过频繁,会造成大量进锁出锁操作,引起锁竞争。为了减少这种情况,可以使用两个队列,一个队列为读线程读数据的队列,另一个队列为处理数据的线程用的队列,当前一个队列数据达到一定量时,一并复制到另外一个队列,将多次进锁出锁变成一次,从而减少锁竞争。
无锁队列:无锁队列本质上使用原子操作属性,例如在 Windows 上的InterlockedCompareExchange API。其本质是将锁的粒度变得更小,锁内部的操作尽可能少,这样可以减少一部分锁竞争。
双锁并发队列:双锁并发队列使用插入锁和删除锁两个操作,在队列只有一个元素时需保证隔开插入和删除操作,以避免出现问题。据说使用这种队列可以提高程序效率。
怎样优化 Linux 系统的内存使用效率?
调整内核参数:
-
vm.swappiness:该参数控制内核将内存页面换出到交换空间(swap)的倾向程度,取值范围是 0 到 100。默认值为 60,值越小,内核越不倾向于使用交换空间。如果系统具有大量可用内存,并且内存压力很小,可以将此值设置为较低的值,如 10 或 20,以减少页面交换的频率,提高内存使用效率。可以使用 sudo sysctl -w vm.swappiness=10 命令来修改。
-
vm.dirty_ratio 和 vm.dirty_background_ratio:vm.dirty_ratio 表示脏页(即被修改但尚未写入磁盘的页面)占系统内存的比例上限,当脏页比例达到这个值时,系统会将所有脏页写回磁盘;vm.dirty_background_ratio 则表示后台异步写回磁盘的脏页比例上限。适当增大这两个参数的值可以提高系统的响应速度,但也不能设置得过大,否则可能会导致磁盘 I/O 压力过大。可以根据系统的实际情况进行调整,例如 sudo sysctl -w vm.dirty_ratio=40 和 sudo sysctl -w vm.dirty_background_ratio=10。
限制进程的内存使用量:
-
使用 cgroups:cgroups(Control Groups)是一种内核功能,可以限制或隔离进程组的资源使用。通过为进程组分配内存限制,可以确保系统中其他进程也能够获得足够的内存。首先创建一个名为 mygroup 的 cgroup,使用 sudo cgcreate -g memory:/mygroup 命令;然后设置内存限制为 1GB,使用 sudo cgset -r memory.limit_in_bytes=1g /mygroup;最后将进程的 PID 添加到 cgroup 中,使用 sudo cgclassify -g memory:/mygroup <pid>。
-
使用 ulimit:ulimit 命令可以限制 shell 启动的进程的资源使用。例如,使用 ulimit -m <内存大小> 可以限制进程使用的内存大小(单位为 KB),使用 ulimit -v <内存大小> 可以限制进程使用的虚拟内存大小(单位为 KB)。这种方法适用于限制单个进程的内存使用,但对于长期运行的服务或系统级别的内存管理,cgroups 更为合适。
合理使用内存交换(swap):
-
调整 swap 分区大小:如果系统的物理内存较小,可以适当增大 swap 分区的大小,以便在物理内存不足时能够有足够的虚拟内存可用。但是,如果物理内存足够大,过多地使用 swap 分区可能会导致系统性能下降,因为磁盘的读写速度比内存慢得多。可以根据系统的实际内存使用情况和需求来调整 swap 分区的大小。
-
使用 swap 优化工具:Linux 系统提供了一些内存压缩技术,如 zswap 和 zram。zswap 可以将一部分需要被交换到 swap 分区的内存压缩保存,减少交换过程的 I/O 开销;zram 则是将一部分内存用作压缩存储空间,以提高内存的利用率。
优化应用程序:
-
优化应用程序的配置参数:对于一些占用内存较大的应用程序,可以查看其官方文档,了解是否有可以优化内存使用的配置参数。例如,数据库应用程序可以调整缓存大小、连接数等参数,以减少内存占用。
-
升级应用程序:新版本的应用程序可能会修复一些内存泄漏的问题,并且在内存管理方面可能会有更好的优化,因此及时升级应用程序可以提高内存使用效率。
-
关闭不必要的应用程序:对于那些不必要运行的应用程序,及时关闭或卸载它们,以释放内存资源。可以使用 top 或 htop 命令查看系统中正在运行的进程,并使用 kill 命令关闭不需要的进程。
定期清理缓存:Linux 系统中的缓存(buffer/cache)是为了加速 I/O 操作而存在的,但在某些情况下,缓存可能会占用过多的内存。可以使用 sync 命令将缓存中的数据写入磁盘,然后使用 echo > /proc/sys/vm/drop_caches 命令来清理缓存。但需要注意的是,频繁地清理缓存可能会影响系统的性能,因此应该根据实际情况进行操作。
使用内存分析工具:使用 top、vmstat、free 等命令可以实时监控系统的内存使用情况,帮助用户及时发现内存使用异常的进程,并采取相应的措施4。例如,top 命令可以查看系统中最耗费内存的进程,vmstat 命令可以查看系统的内存和交换分区的使用情况,free 命令可以查看系统的内存使用情况和可用内存大小2。
使用大页内存(hugepages):hugepages 是 Linux 内核提供的一种功能,可以将大块的内存页面映射到物理内存,减少页表的开销,提高内存访问效率。通过使用 hugepages 技术,可以加速内存访问速度,提高系统性能。需要在系统启动时配置内核参数,指定 hugepages 的大小和数量1。
减少内存碎片:内存碎片是指内存中出现的不连续、无法分配的小块内存空间,会影响系统的内存分配效率。可以通过定期进行内存碎片整理或使用内存池技术来减少内存碎片。一些应用程序或库可能已经实现了内存池技术,可以在开发应用程序时使用这些技术来减少内存碎片