Linux 性能优化之 IO 子系统

本文介绍了对 Linux IO 子系统性能进行优化时需要考虑的因素,以及一些 IO 性能检测工具。

本文的大部分内容来自 IBM Redbook - Linux Performance and Tuning Guidelines

FileSystem

VFS(Virtual FileSystem) 虚拟文件系统

文件系统是内核的功能,是一种工作在内核空间的软件,访问一个文件必须要需要文件系统的存在才可以。Linux 可以支持多达数十种不同的文件系统,它们的实现各不相同,因此 Linux 内核向用户空间提供了虚拟文件系统这个统一的接口用来对文件系统进行操作。

虚拟文件系统是位于用户空间进程和内核空间中多种不同的底层文件系统的实现之间的一个抽象的接口层,它提供了常见的文件系统对象模型(如 i-node, file object, page cache, directory entry, etc.)和访问这些对象的方法(如 open, close, delete, write, read, create, fstat, etc.),并将它们统一输出,类似于库的作用。从而向用户进程隐藏了各种不同的文件系统的具体实现,这样上层软件只需要和 VFS 进行交互而不必关系底层的文件系统,简化了软件的开发,也使得 linux 可以支持多种不同的文件系统。

Journaling

非日志型文件系统

在非日志型文件系统中,对文件系统实施一个写操作,内核会首先修改对应的元数据,然后修改数据块。如果在写入元数据时,文件系统发生崩溃或某种故障,那么数据的一致性将会遭到破坏。fsck 命令可以在下次重启时检查所有的元数据并修复数据一致性,但是如果文件系统非常大,或者系统运行关键业务不允许停机,使用非日志型文件系统的风险会非常高。

日志型文件系统

日志型文件系统的区别在于,在进行对文件系统写数据之前,写将数据写到「日志区」,然后再写入文件系统,在写入文件系统之后删除日志。日志区可以在文件系统内部也可以在文件系统外部。日志区的数据称作文件系统日志,这些数据包含了修改了的元数据,也可能包含将要修改的数据。写日志也会带来一定的额外开销。

EXT2

ext2 文件系统组成不再赘述,需要注意的是 ext2 文件系统没有日志功能。

EXT3

ext3 是带有日志功能文件系统,它基于ext2文件系统实现。

  • 以一致性的方式写入数据,即使断电或文件系统崩溃,恢复的时间会大大减少

  • 数据完整性:在挂载时指定选项 data=journal 则可以使日志记录下元数据和文件数据

  • 速度:指定挂载选项 data=writeback 使用回写的方式写数据

  • 灵活性:可以从 ext2 系统升级,而不需要重新格式化文件系统,也可以以非日志模式挂载,就如同 ext2 一样

ext3的日志模式

  • journal,提供最大数据完整性,日志会记录元数据和文件数据

  • ordered,日志只记录元数据。但是会保证文件数据先被写入,这是默认选项。

  • writeback,使用回写的方式写数据,只保证记录元数据至日志。

其他文件系统

  • ReiserFS,ReiserFS是一个快速的日志型文件系统,有着良好的磁盘空间使用和快速的崩溃恢复。属于 Novell 公司,在 SUSE Linux 中使用。

  • JFS (Journal File System), JFS 是一个完全 64 位文件系统,可以支持非常大的文件和分区,早先由 IBM 公司为 AIX 操作系统开发,现已使用 GPL 协议开源。JFS适用于非常大的分区和文件如 HPC 或者数据库业务。

  • XFS (eXtended File System), 一个高性能的日志型文件系统,和 JFS 比较相似。

I/O子系统架构

上图概括了一次磁盘 write 操作的过程,假设文件已经被从磁盘中读入了 page cache 中

  1. 一个用户进程通过 write() 系统调用发起写请求

  2. 内核更新对应的 page cache

  3. pdflush 内核线程将 page cache 写入至磁盘中

  4. 文件系统层将每一个 block buffer 存放为一个 bio 结构体,并向块设备层提交一个写请求

  5. 块设备层从上层接受到请求,执行 IO 调度操作,并将请求放入IO 请求队列中

  6. 设备驱动(如 SCSI 或其他设备驱动)完成写操作

  7. 磁盘设备固件执行对应的硬件操作,如磁盘的旋转,寻道等,数据被写入到磁盘扇区中

Block Layer

Block layer 处理所有和块设备相关的操作。block layer 最关键是数据结构是 bio 结构体。bio 结构体是 file system layer 到 block layer 的接口。 当执行一个写操作时,文件系统层将数据写入 page cache(由 block buffer 组成),将连续的块放到一起,组成 bio 结构体,然后将 bio 送至 block layer。

block layer 处理 bio 请求,并将这些请求链接成一个队列,称作 IO 请求队列,这个连接的操作就称作 IO 调度(也叫 IO elevator 即电梯算法).

IO scheduler

IO 调度器的总体目标是减少磁盘的寻道时间(因此调度器都是针对机械硬盘进行优化的),IO 调度器通过两种方式来减少磁盘寻道:合并排序

合并即当两个或多个 IO 请求的是相邻的磁盘扇区,那么就将这些请求合并为一个请求。通过合并请求,多个 IO 请求只需要向磁盘发送一个请求指令,减少了磁盘的开销。

排序就是将不能合并的 IO 请求,根据请求磁盘扇区的顺序,在请求队列中进行排序,使得磁头可以按照磁盘的旋转顺序的完成 IO 操作,可以减小磁盘的寻道次数。

调度器的算法和电梯运行的策略相似,因此 IO 调度器也被称作 IO 电梯( IO Elevator )。由于对请求进行了重排,一部分的请求可能会被延迟,以提升整体的性能。

Linux 2.4 只使用了一种通用的 IO 算法。到 Linux 2.6 实现了 4 种 IO 调度模型,其中 anticipatory 在 2.6.33 中被移除

Linus Elevator

早先的 IO 调度器就叫做 Linus Elevator,当一个请求被加入到请求队列中,它首先检查队列中是否存在相邻的请求以合并两个请求,这可能包含前合并和后合并。如果不能合并,则寻找是否能够将新请求按扇区顺序插入到请求队列,如果没有找到适合插入的位置,那么就将这个请求插入到队列的末尾。另外,如果请求队列中的某个请求超过的预先定义的过期阈值,新请求即使可以进行排序,也被插入到队列的末尾。这样可以防止磁盘上某个区域产生大量请求,而其他区域的请求被饿死。然而,这种过期策略并不十分高效。

这种算法可能导致请求饿死的情况,它是 Linux 2.4 的唯一调度器。

当一个请求加入到请求队列时,IO 调度器所完成的操作如下

  1. 如果队列中存在对相邻扇区的请求,则合并两个请求为一个

  2. 如果队列中存在超过过期时间的请求,那么新请求被插入到队列的末尾,以防止饿死老的请求

  3. 如果队列中存在可以按扇区地址排序的合适位置,那么将请求插入到这个位置

  4. 如果队列中没有合适的可插入位置,请求被插入到队列末尾

Deadline - latency-oriented

Deadline 调度器是设计用来解决 Linus Elevator 导致的 I/O 饿死的问题。对磁盘上某个区域的大量请求,会无限期的饿死 (starvation) 对磁盘其他区域上的请求。

请求饿死的一个特例是 写请求饿死读请求 (writes starving reads),对于进程来说,当需要进行写操作时,由于需要写的数据在内存的 page cache 中,进程是需要修改对应的内存,向内核发送写请求即可,之后进程可以去进行其他操作,由内核负责将数据同步至磁盘中即可。对于进程来说,写请求是异步操作。而读操作是完全不同的,当进程需要读取数据时,需要向内核发送读请求,内核将数据载入到内存中,由于进程往往需要处理读取的数据,因此进程将处于阻塞状态,直到请求被处理完成。对于进程来说,读请求是同步的操作。写请求延迟对系统的性能影响不大,而读请求延迟对系统性能影响是非常大的,因此两种 IO 请求需要区别对待。

Dealine 调度器专门针对读请求延迟进行了优化,在 deadline 算法中,每一个请求都有一个过期时间。默认情况下,读请求的过期时间是 500ms,写请求的过期时间是 5s。Dealine 调度器也会对请求队列进行合并和排序操作,这个队列称作排序队列(sorted queue)。当新请求被提交,Deadline将其加入到排序队列中进行合并和排序。同时 Deadline 将这个请求加入到第二种类型的队列中,读请求被加入至读FIFO队列 (Read FIFO queue),写请求被加入到写FIFO队列 (Write FIFO queue),这两个队列中,请求完全按照 FIFO 顺序排列,即新请求永远被放入到队列的末尾。

这样一来 Dealine 就维护三个队列,正常情况下,Deadline 将排序队列中的请求放入到调度队列 (dispatch queue,即将写入磁盘的队列)中,调度队列把请求发送至磁盘驱动。如果写 FIFO 队列或读 FIFO 队列中的请求发生了超时,Deadline 调度器就不再使用排序队列,而是开始将发生超时的 FIFO 队列的请求放入调度队列,直至队列中没有超时的请求,Deadline 通过这样的方式保证所有的请求都不会长时间超时。

Deadling 防止了请求饿死的出现,由于读请求的超时时间远小于写请求,它同时也避免了出现写请求饿死读请求。

Deadline 比较适合于MySQL数据库。

Anticipatory(AS)

AS 调度器是基于 Deadline 调度器,加上了一个启发式的「预测」,假设系统中有大量的写请求,这时如果夹杂几个读请求,由于读请求的过期时间短,读请求立即在多个写请求的中间产生,这样会导致磁盘的来回寻道,AS 试图减少大量写请求夹杂少量读请求产生的寻道风暴(seek storm)。当一个读请求完成后,AS不会立即返回处理队列中的剩余请求,而是等待一个预测时间(默认为 6ms),如果等待的时间内发生了相邻位置的读请求,那么立即处理这个相邻位置的读请求,再返回处理队列中的请求,这样可以优化多余的寻道时间。

它可以为连续 IO 请求(如顺序读)进行了一定的优化,但是对于随机读的场景 AS 会产生较大的延迟,对于数据库应用很糟糕,而对于 Web Server 可能会表现的不错。这个算法也可以简单理解为面向低速磁盘的,对于使用了 TCG(Tagged Command Queueing)高性能的磁盘和 RAID,使用 AS 会降低性能。

在 Linux 2.6 - 2.6.18 AS 是内核默认调度器,然而大多数的企业发行版选择的是 CFQ 调度器。

到Linux 2.6.33 版本,AS 被从内核中移除,因为可以通过调整其他调度器(如 CFQ)来实现与其相似的功能。

Complete Fair Queuing(CFQ)- fairness-oriented

CFQ 为每个进程分配一个 I/O 请求队列,在每个队列的内部,进行合并和排序的优化。CFQ 以轮询的方式处理这些队列,每次从一个队列中处理特定数量的请求(默认为 4 个)。它试图将 I/O 带宽均匀的分配至每个进程。CFQ 原本针对的是多媒体或桌面应用场景,然而,CFQ 其实在许多场景中内表现的很好。CFQ 对每一个 IO 请求都是公平的。这使得 CFQ 很适合离散读的应用 (eg: OLTP DB)

多数企业发型版选择 CFQ 作为默认的 I/O 调度器。

NOOP(No Operation)

该算法实现了最简单的 FIFO 队列,所有 IO 请求按照大致的先后顺序进行操作。之所以说「大致」,原因是 NOOP 在 FIFO 的基础上还做了相邻 IO 请求的合并,但其不会进行排序操作。

NOOP 适用于底层硬件自身就具有很强调度控制器的块设备(如某些SAN设备),或者完全随机访问的块设备(如SSD)。

各个调度器在数据库应用下的性能,可以看出CFQ的性能是比较均衡的

IO 参数调整

队列长度

查看队列长度 
/sys/block//queue/nr_requests

下图展示了各种队列长度时,Deadline 和 CFQ 调度器的性能

 

由 ext3 的表现可以看出,对于大量的小文件写操作,队列长度更长,性能会有所提升,在 16KB 左右,性能提升最为明显,在 64KB 时,64 至 8192 的队列长度有着差不多的性能。随着文件大小的增大,小队列长度反而有着更好的性能。 RHEL 操作系统中,每个设备有一个队列长度。对于类似数据库日志的存放分区,大部分写操作属于小文件 IO,可以将队列长度调小。

对于大量的连续读取,可以考虑增加读取首部的窗口大小
/sys/block//queue/read_ahead_kb

IO调度器

选择设备的IO调度器 
/sys/block//queue/scheduler

或者在 grub.conf 中加入内核参数 elevator=SCHEDULER

Deadline参数

/sys/block//queue/iosched/writes_starved 
进行一个写操作之前,允许进行多少次读操作

/sys/block//queue/iosched/read_expire 
读请求的过期时间

/sys/block//queue/iosched/read_expire 
写请求的过期时间,默认为 500ms

/sys/block/sda/queue/iosched/front_merges 
是否进行前合并

Anticipatory参数

/sys/block//queue/iosched/antic_expire 
预测等待时长,默认为 6ms

/sys/block//queue/iosched/{write_expire,read_expire} 
读写请求的超时时长

/sys/block//queue/iosched/{write_batch_expire,read_batch_expire} 
读写的批量处理时长

CFQ参数

/sys/block//queue/iosched/slice_idle 
当一个进程的队列被分配到时间片却没有 IO 请求时,调度器在轮询至下一个队列之前的等待时间,以提升 IO 的局部性,对于 SSD 设备,可以将这个值设为 0。

/sys/block//queue/iosched/quantum 
一个进程的队列每次被处理 IO 请求的最大数量,默认为 4,RHEL6 为 8,增大这个值可以提升并行处理 IO 的性能,但可能会造成某些 IO 延迟问题。

/sys/block//queue/iosched/slice_async_rq 
一次处理写请求的最大数

/sys/block//queue/iosched/low_latency 
如果IO延迟的问题很严重,将这个值设为 1

调整CFQ调度器的进程IO优先级

$ ionice [[-c class] [-n classdata] [-t]] -p PID [PID]...$ ionice [-c class] [-n classdata] [-t] COMMAND [ARG]...

CFQ 的进程 IO 优先级和进程 CPU 优先级是独立的。使用 ionice 调整进程优先级,有三种调度类型可以选择

  • idle 
    只有在没有更高优先级的进程产生 IO 时,idle 才可以使用磁盘 IO,适用于哪些不重要的程序(如 updatedb),让它们在磁盘空闲时再产生 IO

  • Best-effort 
    这个类型共有 8 个优先级,分别为 0-7,数字越低,优先级越高,相同级别的优先级使用轮询的方式处理。适用于普通的进程。

    在2.6.26之前,没有指定调度类型的进程使用"none" 调度类型,IO调度器将其视作Best-effort进行处理,这个类别中进程优先级由CPU优先级计算得来: io_priority = (cpu_nice + 20) / 5 

    2.6.26之后,没有指定调度类型的进程将从CPU调度类型继承而来,这个类别的优先级仍按照CPU优先级计算而来: io_priority = (cpu_nice + 20) / 5

  • Real time 
    这个调度级别的进程产生的IO被优先处理,这个调度类型应小心使用,防止饿死其他进程IO, 它也有8个优先级,数字越大分的的IO时间片越长

文件系统

一般来说 ReiserFS 更适合于小文件 IO,而 XFS 和 JFS 适合大文件 IO,ext4 则处于两种之间。

JFS 和 XFS 适用于大型的数据仓库,科学计算,大型 SMP 服务器,多媒体流服务等。ReiserFS 和 Ext4 适合于文件服务器,Web 服务器或邮件服务器。

noatime

atime 用于记录文件最后一次被读取的时间。默认情况下,文件每次被读取或修改(也需要先读取),系统将更新 atime 并写入至文件元数据中。由于写操作是很昂贵的,减少不必要的写操作可以提升磁盘性能。然后,大多数时候,关闭文件的 atime 也只能获得非常小的性能提升(这个说法来自于IBM Redbook,表示怀疑)。

使用 noatime 挂载文件系统,将不对文件的 atime 进行更新,此时 atime就相当于 mtime。磁盘性能可以得到0-10%的提升。

使用 noatime挂载方法 
/dev/sdb1 /mountlocation ext3 defaults,noatime 1 2

nobarrier

barrier 是保证日志文件系统的 WAL (write ahead logging) 一种手段,数据写入磁盘时,理应先写入 journal 区,再写入数据在磁盘的实际对应位置,磁盘厂商为了加快磁盘写入速度,磁盘都内置 cache,数据一般都先写入磁盘的 cache。

cache 能加快写入速度,但磁盘一般会对 cache 内缓存数据排序使之最优刷新到磁盘,这样就可能导致要刷新的实际数据和 journal 顺序错乱。一旦系统崩溃,下次开机时磁盘要参考 journal 区来恢复,但此时 journal 记录顺序与数据实际刷新顺序不同就会导致数据反而「恢复」到不一致了。而barrier 如其名——「栅栏」,先加一个「栅栏」,保证 journal 总是先写入记录,然后对应数据才刷新到磁盘,这种方式保证了系统崩溃后磁盘恢复的正确性,但对写入性能有影响。

数据库服务器底层存储设备要么采用 RAID 卡,RAID 卡本身的电池可以掉电保护;要么采用 Flash 卡,它也有自我保护机制,保证数据不会丢失。所以我们可以安全的使用 nobarrier 挂载文件系统。设置方法如下: 对于 ext3,ext4 和 reiserfs 文件系统可以在 mount 时指定 barrier=0;对于 xfs 可以指定 nobarrier 选项。

read-ahead 预读

Linux 把读模式分为随机读和顺序读两大类,并只对顺序读进行预读。这一原则相对保守,但是可以保证很高的预读命中率。

为了保证预读命中率,Linux只对顺序读 (sequential read) 进行预读。内核通过验证如下两个条件来判定一个 read() 是否顺序读:

  1. 这是文件被打开后的第一次读,并且读的是文件首部;
  2. 当前的读请求与前一(记录的)读请求在文件内的位置是连续的。

如果不满足上述顺序性条件,就判定为随机读。任何一个随机读都将终止当前的顺序序列,从而终止预读行为(而不是缩减预读大小)。

预读窗口

Linux采用了一个快速的窗口扩张过程

  • 首次预读: readahead_size = read_size * 2 // or *4 
    预读窗口的初始值是读大小的二到四倍。这意味着在您的程序中使用较大的读粒度(比如32KB)可以稍稍提升I/O效率。

  • 后续预读:readahead_size *= 2 
    后续的预读窗口将逐次倍增,直到达到系统设定的最大预读大小,其缺省值是 256KB

查看和修改预读窗口 
$ blockdev -getra device 
$ blockdev -setra N device

日志模式

大多数文件系统可以设置三种日志模式,对于 ext4 文件系统,日志模式对磁盘的性能有较大的影响。

  • data=journal 
    数据和元数据都写入日志,提供了最高的数据一致性

  • data=ordered (默认) 
    只记录元数据,然后它会保证先将数据写入磁盘

  • data=writeback 
    采用回写的方式,牺牲数据一致性,获得更好的性能。仍然会将元数据记录到日志中,此模式对小文件 IO 性能提升最为明显,但可能造成数据丢失。

将日志放在单独的设备中

  1. 卸载文件系统

  2. 查看文件系统块大小和日志参数 
    $ dumpe2fs /dev/sdb1

  3. 移除文件系统内部的日志区 
    $ tune2fs -O ^has_journal /dev/sdb1

  4. 创建外部的日志设备 
    $ mke2fs –O journal_dev -b block-size /dev/sdc1

  5. 更新原文件系统的超级块 
    $ tune2fs -j -J device=/dev/sdc1 /dev/sdb1

commit

设置多少秒从日志中进行一个同步,默认是5

优化Ext4

  1. 格式大文件系统化时延迟 inode 的写入 

    对于超大文件系统,mkfs.ext4 进程要花很长时间初始化文件系统中到所有内节点表。 

    可使用 -E lazy_itable_init=1 选项延迟这个进程。如果使用这个选项,内核进程将在挂载文件系统后继续初始化该文件它。 
    $ mkfs.ext4 -E lazy_itable_init=1 /dev/sda5

  2. 关闭Ext4的自动 fsync() 调用 
    -o noauto_da_alloc 或 mount -o noauto_da_alloc

  3. 降低日志IO的优先级至与数据IO相同 
    -o journal_ioprio=n 
    n的用效取值范围为0-7,默认为3;

查看文件系统锁

$ cat /proc/locks

Benchmark 基准测试

iozone

使用iozone对文件系统进行基准测试

  • $ iozone -a 
    iozone将在所有模式下进行测试,使用记录块从4k到16M,测试文件大小从64k到512M

  • $ iozone -Ra 或 iozone -Rab output.xls 
    iozone将测试结果放在Excel中

  • $ iozone -Ra -g 2g 
    如果内存大于512MB,则测试文件需要更大;最好测试文件是内存的两倍。例如内存为1G,将测试文件设置最大为2G

$ iozone -Ra -g 2g -i 0 -i 1 
如果只关心文件磁盘的read/write性能,而不必花费时间在其他模式上测试,则我们需要指定测试模式

$ iozone -Rac 
如果测试NFS,将使用-c,这通知iozone在测试过程中执行close()函数。使用close()将减少NFS客户端缓存的影响。但是如果测试文件比内存大,就没有必要使用参数-c

-a  在所有模式下进行测试,使用记录块从4k16M,测试文件大小从64k512M
-b  生成excel文件
-R  生成excel输入
-f  指定临时文件名
-g  自动模式的最大文件大小
-n  自动模式的最小文件大小
-s  指定文件大小,默认单位是KB, 也可以使用m  g
-y  自动模式的最小记录大小,默认单位是KB
-q  自动模式的最大记录大小,默认单位是KB
-r  指定记录大小,单位是KB
-i  选择测试模式
0=write/rewrite
1=read/re-read
2=random-read/write
3=Read-backwards
4=Re-write-record
5=stride-read
6=fwrite/re-fwrite
7=fread/Re-fread,
8=random mix
9=pwrite/Re-pwrite
10=pread/Re-pread
11=pwritev/Re-pwritev
12=preadv/Repreadv

一个例子

$ iozone -a -s 128M -y 512 -q 16384 -i 0 -i 1 -i 2 -f /test/a.out -Rb /root/ext3_writeback.out

dd

$ dd bs=1M count=128 if=/dev/zero of=test conv=fdatasync 
使用这个参数,dd命令执行到最后会真正执行一次同步(sync)操作

$ dd bs=1M count=128 if=/dev/zero of=test oflag=direct 使用直接IO(绕过缓存)

bonnie++

usage: bonnie++ [-d scratch-dir] [-s size(Mb)[:chunk-size(b)]]
 [-n number-to-stat[:max-size[:min-size][:num-directories]]]
 [-m machine-name]
 [-r ram-size-in-Mb]
 [-x number-of-tests] [-u uid-to-use:gid-to-use] [-g gid-to-use]
 [-q] [-f] [-b] [-p processes | -y]
-d 生成测试文件的路径
-s 生成测试文件的大小,以M为单位(如果不使用-r参数,则要求文件大小至少是系统物理内存的2倍)
-m 机器名,实际上我们可以认为是本次测试的方案名,可以随便定义。默认是本机的hostname
-r 内存大小,指定内存大小,这样可以通过-s参数创建r*2大小的文件,通常用于缩短测试时间,但是需要注意这样由于内存的cache可能导致测试结果的不准确
-x 测试的次数
-u 测试文件的属主和组,默认是执行bonnie++的当前用户和当前组
-g 测试文件的组,默认是执行bonnie++的当前用组
-b 在每次写文件时调用fsync()函数,对于测试邮件服务器或者数据库服务器这种通常需要同步操作的情况比较适合,而不使用该参数则比较适合测试copy文件或者编译等操作的效率。

可以简单地运行如下命令进行磁盘性能测试:

$ bonnie++ -d /global/oradata –m sun3510 
这样将会在指定的目录下(通常我们会指定一个盘阵上卷的挂载点),生成相当于主机物理内存两倍的文件,如果总量大于1G,则生成多个大小为1G的文件。假设主机内存为4G,那么在测试中就会生成8个1G的文件,到测试结束,这些文件会被自动删除。

如果我们的主机内存是4G,但是我们想缩短测试的时间,比如说只写2G的文件,就应该执行下面的命令:

$ bonnie++ -d /global/oradata –m sun3510 –s 2048 –r 1024

blktrace & blkparse

blktrace 是一个针对 Linux 内核中块设备 IO 层的跟踪工具,用来收集磁盘IO 信息中当 IO 进行到块设备层(block层,所以叫blk trace)时的详细信息(如 IO 请求提交,入队,合并,完成等等一些列的信息),blktrace 需要借助内核经由 debugfs 文件系统(debugf s文件系统在内存中)来输出信息,所以用 blktrace 工具之前需要先挂载 debugfs 文件系统,blktrace需要结合 blkparse 来使用,由 blkparse 来解析 blktrace 产生的特定格式的二进制数据。

blktrace语法:

 blktrace -d dev [ -r debugfs_path ] [ -o output ] [-k ] [ -w time ] [ -a action ] [ -A action_mask ] [ -v ]

blktrace选项:
 -A hex-mask        #设置过滤信息mask成十六进制mask
 -a mask            #添加mask到当前的过滤器
 -b size            #指定缓存大小for提取的结果,默认为512KB
 -d dev         #添加一个设备追踪
 -I file            #Adds the devices found in file as devices to trace
 -k             #杀掉正在运行的追踪
 -n num-sub     #指定缓冲池大小,默认为4个子缓冲区 
 -o file            #指定输出文件的名字
 -r rel-path        #指定的debugfs挂载点
 -V             #版本号 
 -w seconds     #设置运行的时间

文件输出

$ blktrace –d /dev/sda –o trace

解析结果 $ blkparse -i trace -o /root/trace.txt

FIO

FIO 是测试 IOPS 的非常好的工具,用来对硬件进行压力测试和验证,支持 13 种不同的 IO 引擎,包括:sync, mmap, libaio, posixaio, SG v3, splice, null, network, syslet, guasi, solarisaio 等等。

1、IOPS (Input/Output Operations Per Second), 即每秒进行读写(I/O)操作的次数,多用于数据库等场合,衡量随机访问的性能。 IOPS是指存储每秒可接受多少次主机发出的访问,主机的一次IO需要多次访问存储才可以完成。

2、bw 传输带宽。bw=19372KB/s,

测试序列读, 每次读取大小4k的块,默认的ioengine=sync,运行60s(runtime),-direct=1,跳过buffer 

[root@205]# fio -filename=/data/hello -direct=1 -bs=4k -rw=write -size=5G -numjobs=8 -runtime=60 -group_reporting -name=test_for_sync

test_for_sync: (g=0): rw=write, bs=4K-4K/4K-4K/4K-4K, ioengine=sync, iodepth=1

...

test_for_sync: (g=0): rw=write, bs=4K-4K/4K-4K/4K-4K, ioengine=sync, iodepth=1

fio-2.0.13

Starting 8 processes

test_for_sync: Laying out IO file(s) (1 file(s) / 5120MB)

Jobs: 8 (f=8): [WWWWWWWW] [100.0% done] [0K/20532K/0K /s] [0 /5133 /0  iops] [eta 00m:00s]

test_for_sync: (groupid=0, jobs=8): err= 0: pid=9796: Thu Aug 20 19:23:10 2015

  write: io=1135.1MB, bw=19372KB/s, iops=4843 , runt= 60045msec

    clat (usec): min=125 , max=303991 , avg=1649.93, stdev=12015.69

     lat (usec): min=125 , max=303992 , avg=1650.11, stdev=12015.72

    clat percentiles (usec):

     |  1.00th=[  139],  5.00th=[  147], 10.00th=[  151], 20.00th=[  159],

     | 30.00th=[  165], 40.00th=[  175], 50.00th=[  183], 60.00th=[  189],

     | 70.00th=[  197], 80.00th=[  211], 90.00th=[  249], 95.00th=[  306],

     | 99.00th=[69120], 99.50th=[96768], 99.90th=[154624], 99.95th=[179200],

     | 99.99th=[228352]

    bw (KB/s)  : min=   18, max= 9221, per=12.60%, avg=2440.72, stdev=1456.77

    lat (usec) : 250=90.02%, 500=7.42%, 750=0.33%, 1000=0.14%

    lat (msec) : 2=0.15%, 4=0.08%, 10=0.06%, 20=0.03%, 50=0.37%

    lat (msec) : 100=0.95%, 250=0.45%, 500=0.01%

  cpu          : usr=0.23%, sys=1.98%, ctx=581549, majf=0, minf=216

  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%

     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%

     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%

     issued    : total=r=0/w=290803/d=0, short=r=0/w=0/d=0


Run status group 0 (all jobs):

  WRITE: io=1135.1MB, aggrb=19372KB/s, minb=19372KB/s, maxb=19372KB/s, mint=60045msec, maxt=60045msec


Disk stats (read/write):

 

  sdb: ios=0/290469, merge=0/97, ticks=0/52818, in_queue=52639, util=87.76%

随机读

$ fio -filename=/dev/sdb1 -direct=1 -iodepth 1 -thread -rw=randread -ioengine=psync -bs=16k -size=200G 
-numjobs=10 -runtime=1000 -group_reporting -name=mytest

说明

filename=/dev/sdb1       测试文件名称,通常选择需要测试的盘的data目录。 
direct=1                 测试过程绕过机器自带的buffer。使测试结果更真实。 
rw=randwrite             测试随机写的I/O 
rw=randrw                测试随机写和读的I/O 
bs=16k                   单次io的块文件大小为16k 
bsrange=512-2048         同上,提定数据块的大小范围 
size=5g                  本次的测试文件大小为5g,以每次4kio进行测试。 
numjobs=30               本次的测试线程为30. 
runtime=1000             测试时间为1000秒,如果不写则一直将5g文件分4k每次写完为止。 
ioengine=psync           io引擎使用pync方式 
rwmixwrite=30            在混合读写的模式下,写占30% 
group_reporting          关于显示结果的,汇总每个进程的信息。 
此外 
lockmem=1g               只使用1g内存进行测试。 
zero_buffers             0初始化系统buffer 
nrfiles=8                每个进程生成文件的数量。 

顺序读

$ fio -filename=/dev/sdb1 -direct=1 -iodepth 1 -thread -rw=read -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest

随机写

$ fio -filename=/dev/sdb1 -direct=1 -iodepth 1 -thread -rw=randwrite -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest

顺序写

$ fio -filename=/dev/sdb1 -direct=1 -iodepth 1 -thread -rw=write -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest

混合随机读写

$ fio -filename=/dev/sdb1 -direct=1 -iodepth 1 -thread -rw=randrw -rwmixread=70 -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=100 -group_reporting -name=mytest -ioscheduler=noop 

stress

它可以给我们的系统施加 CPU,内存,IO 和磁盘的压力,在模拟极端场景给应用系统造成的压力方面很有帮助。

示例 
$ stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10d


参考:


转载自: http://blog.sina.com.cn/s/blog_416656f70102vwld.html
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个进程池的服务器程序 下面做了非常简单的http服务器,该服务器只能接收Get请求。 流程大概如下: 1,父进程listen,创建pipe(下面所有父子进程之间的通信都用该pipe) 2,父进程预fork n个子进程 3,各个子进程accept(listenfd),即所有子进程竞争accept请求。由于listenfd是在fork之前就有的,所以所有子进程都可以访问到,不需用到“进程间文件描述符传递”问题; 4,子进程每accept到一个请求都告诉父进程,父进程把请求数加1;子进程没完成一个请求,父进程把请求数减1;当父进程发现请求数 >= 子进程数时,父进程创建新的子进程,并把子进程数加1(当然子进程数有个预先上限);当父进程发现子进程数大于请求数加1时,父进程杀死多余的子进程。 总的来说,思想是让子进程accept并处理请求,父进程通过子进程发来的信息控制请求数与子进程数之间的关系。 代码如下: 代码如下: #include <time.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <dirent.h> #include <sys/stat.h> #include <signal.h> #include <sys/wait.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #define PRECHILD 5 #define MAXCHILD 50 #define BUFSIZE 4096 #define PIDPATH "pid" #define head503 "HTTP/1.1 503 Service unavailable\r\n" #define head404 "HTTP/1.1 404 Not Found\r\n" #define head200 "HTTP/1.1 200 0K\n\rContent—Type: text/html\n\rContent—Length: " int len503, len404, len200; int fd1[2], fd2[2]; typedef struct { pid_t pid; char status; // 'n' means new request; 'f' means finish the request } REPORT; void answer(int listenfd) { int connfd; char buf[BUFSIZE]; int count; int pid = getpid(); struct sockaddr_in cliaddr; int size = sizeof(cliaddr); char comm; REPORT rep; rep.pid = pid; while (1) { connfd = accept(listenfd, (struct sockaddr *)&cliaddr,(socklen_t *)&size ); //子进程accept请求 rep.status = 'n'; if (write(fd1[1], &rep, sizeof(rep)) < 0) { //通知父进程已经accept了请求 perror("write pipe new failed"); exit(-1); } count = read(connfd, buf, BUFSIZE); char req[10]; char filepath[256]; sscanf(buf, "%s%s", req, filepath + 1); filepath[0] = '.'; if (strcmp("GET", req) != 0) {//503 write(connfd, head503, len503); //goto err_out; close(connfd); exit(-1); } char content[BUFSIZE]; struct stat stbuf; if (lstat(filepath, &stbuf) != 0) { int err = errno; if (err == ENOENT) {//404 write(connfd, head404, len404); } close(connfd); exit(-1); } count = write(connfd, head200, len200); u_int filesize = stbuf.st_size; sprintf(content, "%u\n\r\n\r", filesize); count = write(connfd, content, strlen(content)); FILE *fp = fopen(filepath, "r"); if (fp == NULL) { printf("open file %s failed\n", filepath); close(connfd); exit(-1); } while((count = fread(content, 1, sizeof(content), fp)) > 0) { //printf("%s", content); if (write(connfd, content, count) != count) { printf("write failed\n"); } } fclose(fp); close(connfd); rep.status = 'f'; if (write(fd1[1], &rep, sizeof(rep)) < 0) {//告诉父进程自己处理完了请求 perror("write pipe finish failed"); exit(-1); } if (read(fd2[0], &comm, 1) < 1) {//等待来自父进程的命令 perror("read pipe failed"); exit(-1); } //printf("[%d] reve %c from pa\n", pid, comm); if (comm == 'e') { //收到exit命令 printf("[%d] exit\n", pid); exit(-1); } else if (comm == 'c') { //收到继续accept的命令 printf("[%d] continue\n", pid); } else { printf("[%d] comm : %c illeagle\n", pid, comm); } } } void usage() { printf("Usage: http-serv port\n"); } int write_pid() { int fd; if ((fd = open(PIDPATH, O_WRONLY | O_TRUNC | O_CREAT, S_IWUSR)) < 0){ perror("open pidfile faild"); return -1; } struct flock lock; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { int err = errno; perror("fcntl faild"); if (err == EAGAIN) { printf("Another http-serv process is running now!\n"); } return -1; } return 0; } void daemon_init() { //clear file creation mask; umask(0); //become a session leader if (fork() != 0) exit(-1); if (setsid() < 0) exit(-1); //make sure can be never get the TTY control if (fork() != 0) exit(-1); //may chdir here int i; for (i = 0; i < 1024; i++) close(i); /* * Attach file descriptors 0, 1, and 2 to /dev/null. */ int fd0, fd1, fd2; fd0 = open("/dev/null", O_RDWR); fd1 = dup(0); fd2 = dup(0); if (fd0 != 0 || fd1 != 1 || fd2 != 2) { printf("init failed\n"); exit(-1); } } int main(int argc, char **argv) { int listenfd; struct sockaddr_in servaddr; pid_t pid; if (argc != 2) { usage(); return -1; } signal(SIGCHLD, SIG_IGN); len200 = strlen(head200); len404 = strlen(head404); len503 = strlen(head503); daemon_init(); //转为后台程序,如需打印调试,把这行注释掉 if (write_pid() < 0) //避免同时有多个该程序在运行 return -1; if (pipe(fd1) < 0) { perror("pipe failed"); exit(-1); } if (s_pipe(fd2) < 0) { perror("pipe failed"); exit(-1); } int port = atoi(argv[1]); //initialize servaddr and listenfd... bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); listenfd = socket(AF_INET, SOCK_STREAM, 0); bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); listen(listenfd, 1000); int i; for (i = 0; i < PRECHILD ; i++) { //父进程预fork 子进程 if ((pid = fork()) < 0) { perror("fork faild"); exit(3); } else if (pid == 0) { answer(listenfd); } else { printf("have create child %d\n", pid); } } char e = 'e'; char c = 'c'; int req_num = 0; int child_num = PRECHILD; REPORT rep; while (1) { //printf("req_num = %d, child_num = %d\n", req_num, child_num); if (read(fd1[0], &rep, sizeof(rep)) < sizeof(rep)) {//等待子进程发来消息 perror("parent read pipe failed"); exit(-1); } //printf("parent: receive from %d\n", pid); if (rep.status == 'n') {//子进程刚accept了新的请求 req_num ++; printf("parent: %d have receive new request\n", rep.pid); if (req_num >= child_num && child_num <= MAXCHILD) { //请求数过多,创建更多子进程 if ((pid = fork()) < 0) { perror("fork faild"); exit(3); } else if (pid == 0) { answer(listenfd); } else { printf("have create child %d\n", pid); child_num ++; } } } else if (rep.status == 'f') {//子进程刚处理完了一个请求 req_num --; //printf("parent: %d have finish a request\n", rep.pid); if (child_num > (req_num + 1) && child_num > PRECHILD) {//子进程数过多,删除多余的子进程 if (write(fd2[1], &e, sizeof(e)) < sizeof(e)) { perror("pa write pipe failed"); exit(-2); } //printf("tell child exit\n"); child_num --; } else { if (write(fd2[1], &c, sizeof(c)) < sizeof(c)) {//让子进程继续等待accept perror("pa write pipe failed"); exit(-2); } //printf("tell child continue\n"); } } } return 0; } 利用fork()创建多个子进程 11:09 pm on Oct 23rd 2010 greenMay 之间我学习了创建一个子进程,也大致理解了子进程与父进程的关系。今天无意间遇到一个创建多个子进程的问题,结果还发现了点小bug,现在写下来和大家分享。 我需要实现的目标如下:编写一段源程序,使系统调用fork()创建两个子进程,当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。 一开始我的主要代码如下: view source print? 01 int main() 02 { 03 pid_t child1; 04 pid_t child2; 05 child1 = fork(); 06 child2 = fork(); 07 if(child1 == 0) 08 { 09 printf("Child1:a\n"); 10 return 0; 11 } 12 if(child2 == 0) 13 { 14 printf("Child2:b\n"); 15 return 0; 16 } 17 else 18 { 19 waitpid(child1,NULL,0); 20 waitpid(child2,NULL,0); 21 printf("Parent:c\n"); 22 } 23 return 0; 24 } 奇怪的是,我得到的是这样一个结果: Child1:a Child1:a Child2:b Parent:c 竟然有两个Child1。可是我的代码里明明只是让Chidl1打印一次啊。搜索到一篇好的博文。文章仔细分析了和我几乎相同的情况。事实上,是我的粗心和对fork()的理解不深刻导致了上述的奇怪问题。 我们知道,fork()之后,我们还是首先执行的是父进程,也就是如下代码段: view source print? 1 waitpid(child1,NULL,0); 2 waitpid(child2,NULL,0); 3 printf("Parent:c\n"); 然后waitpid(child1,NULL,0),进入child1的执行。child1将要执行的是如下的的代码段: view source print? 1 child2 = fork(); 2 if(child1 == 0) 3 { 4 printf("Child1:a\n"); 5 return 0; 6 } 注意,第一行的那个child2 = fork()!这就意味着对于child1来说,它自己又要创建一个子进程,这时候他成为了父亲。这时候,它有一个儿子child2,但是这个child2不同与我们刚才定义的那个child2,这个child2其实是parent的孙子。之所以又打印了一边Child1。如果加上如下代码就明白了: view source print? 01 child2 = fork(); 02 if(child1 == 0) 03 { 04 if(child2 == 0) 05 { 06 printf("GrandChild!\n"); 07 } 08 printf("Child1:a\n"); 09 return 0; 10 } 这时候将出现: Child1:a GrandChild! Child1:a Child2:b Parent:c 恩,这就很明白了!我无意间多调用了一次child2=fork(); 所以,如果要达到我最初的目的,需要改变child2的fork()的位置: view source print? 01 #include <stdio.h> 02 #include <unistd.h> 03 #include <sys/types.h> 04 #include <sys/wait.h> 05 int main() 06 { 07 pid_t child1; 08 pid_t child2; 09 child1 = fork(); 10 child2 = fork(); 11 if(child1 == 0) 12 { 13 printf("Child1:a\n"); 14 return 0; 15 } 16 if(child2 == 0) 17 { 18 printf("Child2:b\n"); 19 return 0; 20 } 21 else 22 { 23 waitpid(child1,NULL,0); 24 waitpid(child2,NULL,0); 25 printf("Parent:c\n"); 26 } 27 return 0; 28 } 我参照的那个博文最后给出了一个更为普遍的fork()创建多进程的程序框架: view source print? 01 pid_t create_child() 02 { 03 pid_t p = fork(); 04 if( p == 0 ) 05 { 06 printf("in child %d\n", getpid()); 07 //do something 08 return 0; 09 } 10 return p; 11 } 12 int main(void) 13 { 14 pid_t p1 = create_child(); 15 pid_t p2 = create_child(); 16 17 int st1, st2; 18 waitpid( p1, &st1, 0); 19 waitpid( p2, &st2, 0); 20 printf("in parent, pid = %d\n", getpid()); 21 printf("in parent, child 1 exited with %d\n", st1); 22 printf("in parent, child 2 exited with %d\n", st2); 23 return 0; 24 } 注意到,期中的create_child()函数最后有一个return p。这个return p将pid返回给了父进程,其实也是将子进程对于CPU的控制权交还给了父进程,这样就避免了多个子进程在创建之时互相影响了。 可以说,今天的这个问题真是一个有趣的事情。代码有的时候就是这么奇怪~ 最后,向我引用的那篇文章致敬! Linux内核对多进程和多线程的支持方式: 线程机制支持并发程序设计技术,在多处理器上能真正保证并行处理。而在linux实现线程很特别,linux把所有的线程都当作进程实现。linux下线程看起来就像普通进程(只是该进程和其他进程共享资源,如地址空间)。上述机制与Microsoft windows或是Sun Solaris实现差异很大。 Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。 do_fork() 提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用产生多进程时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。当使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的”进程”拥有共享的运行环境,只有栈是独立的,由 __clone()传入。 即:Linux下不管是多线程编程还是多进程编程,最终都是用do_fork实现的多进程编程,只是进程创建时的参数不同,从而导致有不同的共享环境。Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager() ,每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号,而主线程pthread_create()) 的调用者则通过管道将请求信息传给管理线程。 很多朋友都说使用多线程的好处是资源占用少,其隐含之意就是说进程占用资源比线程多,对吧?但实际上Linux下多进程是否就真的点用很多资源呢?暂且不说进程是否比线程占用资源多,就进程占用资源的多少情况而言,Linux确实是做得相当节省的。产生一个多进程时肯定是要产生的一点内存是要复制进程表项,即一个task_struct结构,但这个结构本身做得相当小巧。其它对于一个进程来说必须有的数据段、代码段、堆栈段是不是全盘复制呢?对于多进程来说,代码段是肯定不用复制的,因为父进程和各子进程的代码段是相同的,数据段和堆栈段呢?也不一定,因为在Linux里广泛使用的一个技术叫copy-on-write,即写时拷贝。copy-on-write意味着什么呢?意味着资源节省,假设有一个变量x在父进程里存在,当这个父进程创建一个子进程或多个子进程时这个变量x是否复制到了子进程的内存空间呢?不会的,子进程和父进程使用同一个内存空间的变量,但当子进程或父进程要改变变量x的值时就会复制该变量,从而导致父子进程里的变量值不同。父子进程变量是互不影响的,由于父子进程地址空间是完全隔开的,变量的地址可以是完全相同的。 Linux的”线程”和”进程”实际上处于一个调度层次,共享一个进程标识符空间,这种限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。Linux进程的创建是非常迅速的。内核设计与实现一书中甚至指出Linux创建进程的速度和其他针对线程优化的操作系统(Windows,Solaris)创建线程的速度相比,测试结果非常的好,也就是说创建速度很快。由于异步信号是内核以进程为单位分发的,而LinuxThreads的每个线程对内核来说都是一个进程,且没有实现”线程组”,因此,某些语义不符合POSIX标准,比如没有实现向进程中所有线程发送信号,README对此作了说明。LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。LinuxThreads 的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得Linux上的线程应用一直保持在比较低的水平。在Linux社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有两个项目,一个是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另一个则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重起炉灶全新设计的。 综上所述的结论是在Linux下编程多用多进程编程少用多线程编程。 IBM有个家伙做了个测试,发现切换线程context的时候,windows比linux快一倍多。进出最快的锁(windows2k的 critical section和linux的pthread_mutex),windows比linux的要快五倍左右。当然这并不是说linux不好,而且在经过实际编程之后,综合来看我觉得linux更适合做high performance server,不过在多线程这个具体的领域内,linux还是稍逊windows一点。这应该是情有可原的,毕竟unix家族都是从多进程过来的,而 windows从头就是多线程的。 如果是UNIX/linux环境,采用多线程没必要。 多线程比多进程性能高?误导! 应该说,多线程比多进程成本低,但性能更低。 在UNIX环境,多进程调度开销比多线程调度开销,没有显著区别,就是说,UNIX进程调度效率是很高的。内存消耗方面,二者只差全局数据区,现在内存都很便宜,服务器内存动辄若干G,根本不是问题。 多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。 多线程是平面交通系统,造价低,但红绿灯太多,老堵车。 我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。 高性能交易服务器中间件,如TUXEDO,都是主张多进程的。实际测试表明,TUXEDO性能和并发效率是非常高的。TUXEDO是贝尔实验室的,与UNIX同宗,应该是对UNIX理解最为深刻的,他们的意见应该具有很大的参考意义 1. 散沙 2010年7月10日08:43 回复 | 引用 | #1 文章很有深度,我们把握一个尺度就可以了,在windows下使用线程,unix下则使用进程就可以了 2. rjoo 2010年9月9日13:49 回复 | 引用 | #2 错的太多了,博主,应该看看新资料了。 现在都2010年了,NPTL早就取代了老的Linux thread。而且通常多线程有性能优势,但是多进程更稳定,并且通常性能瓶颈不在于是进程模型还是线程模型而在于IO。 3. rjoo 2010年9月9日13:56 回复 | 引用 | #3 关于那个critical section和pthread_mutex_t,critical section本质上是一个自旋锁,短期锁当然快,不知道你说的那个IBM的哥们怎么比的,要比也该是和pthread_spinlock_t比。 4. admin 2010年9月9日17:28 回复 | 引用 | #4 rjoo挺热心的,呵呵,这篇文章不是我写的,但有几个地方我可以解答一下: 1. Linux下没有线程的概念,pthread线程实质是通过轻量级进程实现的。你说瓶颈在IO,这一点我很赞同你的意见,作者如果能再写个IO操作的文章来的话就会更好了。 2. mutex和critical section的确是不能比的。一个涉及到内核,一个没有涉及到内核。呵呵,很佩服你对这些东西的掌握程度,有机会多交流。 ^_^ 5. 定时 2010年9月9日17:40 回复 | 引用 | #5 我们组的最近项目的经验告诉我们能用多进程不用多线程,多线程安全编程难,而且锁会早成效率很低,甚至不如单线程,你说的NPTL我知道,他只是多线程优化了并不能改变多线程安全编程的问题,锁的问题。谢谢指教,实践出真知。 @rjoo 6. 定时 2010年9月9日17:44 回复 | 引用 | #6 你说的锁,我确实不太了解,但是我们leader对它很了解,就是最近的一个项目,锁搞得他很郁闷,他也终于同意我的关键,尽可能不用多线程。 @rjoo 7. rjoo 2010年9月29日13:41 回复 | 引用 | #7 @admin Linux下没有线程的概念,pthread线程实质是通过轻量级进程实现的—这是2.4内核以前的情况(实际上是2.0时引入的,那可实在是太久了),2.4内核引入NGPL,2.6内核线程支持改为NPTL。NPTL实现的是1:1的线程模型(有资料说Win也是这种实现,虽然不太确定,但我觉得可能性很大),而NGPT虽然是理论上最先进的m:n线程模型,但最后实现出来的性能差NPTL一大截,最后被抛弃。看看文中说法就知道要么文章写的很早,要么作者看了一堆十年前的资料。 给个链接: http://www.kegel.com/c10k.html#threads.linuxthreads 8. finalday 2010年10月15日17:26 回复 | 引用 | #8 忍不住跳出来说,作者对并发编程的理解还不行。 比如说锁的问题,说得好像是多线程才需要的东西一样。如果一个应用多进程时完全不用锁,多线程也就多一个轻量级锁——锁一下,各回各家,每个线程用自己的专有存储,之后不就和多进程一样了?这样会被搞得很郁闷?当然不会。所以说明那个应用对于数据共享的需求不是这么简单,既然不是这么简单,多进程程序一样要加锁。多进程的加解锁代价可比多线程大得多了,共享数据和协作也麻烦多了。 多线程编程难不难?难,但这是由于并发本身的难度引起的。“锁”,“安全编程”不管是多线程还是多进程都一样会遇到。 多线程的最大优点是数据共享和协作方便。 多进程的最大优点是挂了一个进程不会影响其他进程,资源也不会泄露,故比较能容忍程序员犯错。 至于两者裸奔比性能,真的没啥意义。
LoadRunner常见测试结果分析 在测试过程中,可能会出现以下常见的几种测试情况:   一、当事务响应时间的曲线开始由缓慢上升,然后处于平衡,最后慢慢下降这种情形表明:   * 从事务响应时间曲线图持续上升表明系统的处理能力在下降,事务的响应时间变长;  * 持续平衡表明并发用户数达到一定数量,在多也可能接受不了,再有请求数,就等待;   * 当事务的响应时间在下降,表明并发用户的数量在慢慢减少,事务的请求数也在减少。   如果系统没有这种下降机制,响应时间越来越长,直到系统瘫痪。   从以上的结果分析可发现是由以下的原因引起:   1. 程序中用户数连接未做限制,导致请求数不断上升,响应时间不断变长;   2. 内存泄露;   二、CPU的使用率不断上升,内存的使用率也是不断上升,其他一切都很正常;   表明系统中可能产生资源争用情况;   引起原因:   开发人员注意资源调配问题。   三、 所有的事务响应时间、cpu等都很正常,业务出现失败情况;   引起原因:   数据库可能被锁,就是说,你在操作一张表或一条记录,别人就不能使用,即数据存在互斥性;   当数据量大时,就会出现数据错乱情况。 晏子出品--服务器压力测试软件 本文介绍了几个比较典型的服务器评测软件,无论什么评测工具,基本的技术都是利用线程技术模仿和虚拟用户,在这里主要的难点在于测试脚本的编写,每种工具使用的脚本都不一样,但是大多数工具都提供录制功能就算是不会编码的测试人员同样可以测试。众所周知,服务器是整个网络系统和计算平台的核心,许多重要的数据都保存在服务器上,很多网络服务都在服务器上运行,因此服务器性能的好坏决定了整个应用系统的性能。现在市面上不同品牌、不同种类的服务器有很多种,用户在选购时,仅仅从配置上判别是不够的,最好能够通过实际测试来筛选,下面就介绍一些较典型的测试工具:   (一)服务器整机系统性能测试工具   一台服务器系统的性能可以按照处理器、内存、存储、网络几部分来划分,而针对不同的应用,可能会对某些部分的性能要求高一些。   Iometer(www.iometer.org):存储子系统读写性能测试   Iometer是Windows系统下对存储子系统的读写性能进行测试的软件。可以显示磁盘系统的最大IO能力、磁盘系统的最大吞吐量、CPU使用率、错误信息等。用户可以通过设置不同的测试的参数,有存取类型(如sequential ,random)、读写块大小(如64K、256K),队列深度等,来模拟实际应用的读写环境进行测试。Iometer操作简单,可以录制测试脚本,可以准确有效的反映存储系统的读写性能,为各大服务器和存储厂商所广泛采用。   Sisoft Sandra(www.sisoftware.co.uk):WINDOWS下基准评测   SiSoft发行的Sandra系列测试软件是Windows系统下的基准评测软件。此软件有超过三十种以上的测试项目,能够查看系统所有配件的信息,而且能够对部分配件(如CPU、内存、硬盘等)进行打分(benchmark),并且可以与其它型号硬件的得分进行对比。另外,该软件还有系统稳定性综合测试、性能调整向导等附加功能。Sisoft Sandra软件在最近发布的Intel bensley平台上测试的内存带宽性能并不理想,不知道采用该软件测试的FBD内存性能是否还有参考价值,或许软件应该针对FBD内存带宽的测试项目做一个升级。   Iozone(www.iozone.org):linux下I/O性能测试   现在有很多的服务器系统都是采用linux操作系统,在linux平台下测试I/O性能可以采用iozone。 iozone是一个文件系统的benchmark工具,可以测试不同的操作系统中文件系统的读写性能。可以测试Read, write, re-read, re-write, read backwards, read strided, fread, fwrite, random read, pread ,mmap, aio_read, aio_write 等等不同的模式下的硬盘的性能。测试所有这些方面,生成excel文件,另外, iozone还附带了用gnuplot画图的脚本。该软件用在大规模机群系统上测试NFS的性能,更加具有说服力。   Netperf(www.netperf.org):网络性能测试   Netperf可以测试服务器网络性能,主要针对基于TCP或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/reponse)模式。Netperf测试结果所反映的是
Linux性能优化是指通过对系统的各方面进行调整和优化,以提高系统的运行效率和响应速度,减少资源的浪费和消耗,提升整个系统的性能表现。下面将从几个方面介绍Linux性能优化: 1. 内存管理优化:通过合理设置Linux内存管理参数,提高内存利用率和内存性能。例如,调整交换区(swap)的大小,合理设置内存页缓存(page cache)和文件系统缓存(buffer cache)的大小等。 2. 文件系统优化: Linux文件系统采用不同的文件系统类型,如ext4、XFS等。根据系统运行情况和需求,选择合适的文件系统类型,并进行相关的优化设置,提高文件系统的IO性能。 3. 网络优化:配置操作系统中的网络参数,如TCP窗口大小、拥塞控制等,以提高网络传输速度和稳定性,降低网络延迟。 4. CPU调度优化:调整Linux内核中的CPU调度算法参数,以确保各个进程和线程在多核环境下能够得到充分的CPU资源,提高系统的并行处理能力。 5. 硬件优化:对硬件进行优化,如调整服务器的BIOS设置,配置RAID等,减少硬件延迟和故障率,提高系统的稳定性和可靠性。 总之,Linux性能优化是一个综合性的工作,需要对系统的各个方面进行深入了解和调整。通过合理的配置和调优,可以提高Linux系统的运行效率和响应速度,提升整个系统的性能表现。在优化过程中,需要结合具体的应用场景和需求,进行有针对性的优化操作,才能取得最佳的性能提升效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值