I/O优化,单车变摩托

2022年05月16日 1,454次浏览

雷义芳(智深)

背景

  1. 从监控来看,大数据离线计算的服务器,在业务计算高峰期,磁盘IOPS只有200多:

    可以看到这里只有250,这个IOPS偏低。

  2. 大数据架构师反馈,在平安的时候,离线大数据集群的单机的IOPS可以跑到400多,甚至500多,从经验来判断,我们服务器的磁盘IOPS 太低。

  3. HDFS的map io情况:
    可以看到整体在3M/S浮动,业界最佳值,阿里云的可以跑到15M/S,我们的偏低。

  4. 造成的影响:
    因为大数据集群的数据量整体在230TB左右,单台服务器承载数据量在20T左右,如果单机出现故障同步数据到其他服务器耗时长。

  5. 在大数据离线计算集群中,已经出现两起ext4 文件系统损坏,虽然没有产生故障,但是是一个很大的隐患。
    目前公司的大数据集群,如果其中一台服务器的I/O变得很慢,将会影响到整个集群的收敛,整个集群的I/O都会变慢。
    文件大小,平均大小6.1M,其他文件大小的规格是:1M以下,1~32M,64M~128M

  6. 疑问点:
    我们购买的服务器都是高配置的DeLL服务器,单台单价在10万左右,但是IOPS的性能表现不尽人意,有高配低能的问题。

是服务器选型选配没有做好导致的问题,还是系统问题,文件系统问题?

为什么会这样?

分析这个问题,我们可以从下往上逐步分析:

  1. 是物理磁盘性能太差导致IOPS性能差吗?

    可以看到大数据离线计算服务器使用的磁盘是10k/分钟(10000转/分钟)的SAS磁盘,物理性能上不差,还有7200转/分钟和5400转/分钟的磁盘,我们选用的磁盘性能在大容量磁盘里是最高速。

后续我们可以通过IO的测试验证。

Linux 磁盘I/O是怎么工作的

磁盘

第一类,机械磁盘,也称为硬盘驱动器(Hard Disk Driver),通常缩写为 HDD。

机械磁盘主要由盘片和读写磁头组成,数据就存储在盘片的环状磁道中。

在读写数据前,需要移动读写磁头,定位到数据所在的磁道,然后才能访问数据。显然,如果 I/O 请求刚好连续,那就不需要磁道寻址,自然可以获得最佳性能,大数据使用的HDFS是顺序读写。

这其实就是我们熟悉的,连续 I/O 的工作原理。与之相对应的,当然就是随机 I/O,它需要不停地移动磁头,来定位数据位置,所以读写速度就会比较慢。

第二类,固态磁盘(Solid State Disk),通常缩写为 SSD,由固态电子元器件组成。

固态磁盘不需要磁道寻址,所以,不管是连续 I/O,还是随机 I/O 的性能,都比机械磁盘要好得多。

对机械磁盘来说,我们刚刚提到过的,由于随机 I/O 需要更多的 磁头寻道和盘片旋转,它的性能自然要比连续 I/O 慢。

而对固态磁盘来说,虽然它的随机性能比机械硬盘好很多,但同样存在“ 先擦除再写入 ”的限制。

随机读写会 导致大量的垃圾回收,所以相对应的,随机 I/O 的性能比起连续 I/O 来,也还是差了很多。

此外,连续 I/O 还可以通过预读的方式,来减少 I/O 请求的次数,这也是其性能优异的一个原因。很多性能优化的方案,也都会从这个角度出发,来优化 I/O 性能。

此外,机械磁盘和固态磁盘还分别有一个最小的读写单位。
机械磁盘的 最小读写单位是扇区,一般大小为 512 字节
而固态磁盘的 最小读写单位是页,通常大小是 4KB、8KB 等。

来看一段动画,直观理解磁盘I/O操作:
https://www.youtube.com/watch?v=svhIPM2VT8U
(需要科学上网查看)

是Linux系统中关于通用块层的配置差导致IOPS差吗?

判定配置是否合理的前提是,我们清楚整体的Linux的文件系统IO操作的原理和流程。

那么我们来看看Linux的文件系统之下的磁盘I/O是怎么工作的。

通用块层:
为了减小不同块设备的差异带来的影响,Linux 通过一个统一的通用块层,来管理各种不同的块设备。

通用块层,其实是处在文件系统和磁盘驱动中间的一个块设备抽象层。它主要有两个功能 。

第一个功能跟虚拟文件系统的功能类似。

向上,为文件系统和应用程序,提供访问块设备的标准接口;

向下,把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序。

第二个功能,通用块层还会给文件系统和应用程序发来的 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率。

对 I/O 请求排序的过程,也就是我们熟悉的 I/O 调度。事实上,Linux 内核支持四种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine。

NONE:
更确切来说,并不能算 I/O 调度算法(也就是None,啥都不干)。

因为它完全不使用任何 I/O 调度器,对文件系统和应用程序的 I/O 其实不做任何处理,常用在虚拟机中(此时磁盘 I/O 调度完全由物理机负责)。

NOOP :
是最简单的一种 I/O 调度算法。它实际上是一个 先入先出的队列,只做一些 最基本的请求合并,常用于 SSD 磁盘

CFQ(Completely Fair Scheduler):
也被称为完全公平调度器,是现在很多发行版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并 按照时间片来均匀分布每个进程的 I/O 请求

类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度,所以它适用于运行大量进程的系统,像是桌面环境、多媒体应用等。

DeadLine:
调度算法,分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保达到最终期限(deadline)的请求被优先处理

DeadLine 调度算法,多用在 I/O 压力比较重的场景,比如数据库等,也适用于大数据场景。

这样来看,我们服务器应该配置I/O调度算法为DeadLine。

# 查看
cat /sys/block/sdb/queue/scheduler
noop [deadline] cfq

可以看到配置的deadline调度算法,这个配置是合理的。

I/O栈:
可以把 Linux 存储系统的 I/O 栈,由上到下分为三个层次,分别是文件系统层、通用块层和设备层。这三个 I/O 层的关系如下图所示,这其实也是 Linux 存储系统的 I/O 栈全景图。

(图片来自 https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram

文件系统层:
包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序,提供标准的文件访问接口;对下会通过通用块层,来存储和管理磁盘数据。

通用块层:
包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。

设备层:
包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。

存储系统的 I/O ,通常是整个系统中最慢的一环。所以, Linux 通过多种缓存机制来优化 I/O 效率。

为了优化文件访问的性能(VFS),会使用页缓存、索引节点缓存、目录项缓存等多种缓存机制,以减少对下层块设备的直接调用。

为了优化块设备的访问效率(通用块层),会使用缓冲区,来缓存块设备的数据。

文件系统是不是不适合我们这种大数据的使用场景导致IOPS差?

可以从网上搜索到很多的针对大数据场景的I/O优化的文档,其中很多文章都会提到xfs文件系统比ext4文件系统更适合大数据场景,这个优化项是可行的吗?

网上教程说明xfs比ext4更适合大数据文件系统的原因如下:

1.通过B+树来索引inode和数据块,而ext4使用的链表,虽然目录索引采用了Hash Index Tree, 但是依然限制高度为2,理论上来说随着文件数量的增加ext4的查找,创建文件,也就是读文件和写文件的性能都会下降,但是xfs没有这个问题。

做过实际测试
Ext4的单个目录文件超过200W个,性能下降的就比较厉害。

[root@pre-hadoop-slave002 ~]# df -Th | grep alidata
/dev/sdb       ext4      1.5T  897G  505G  64% /alidata
/dev/sdd       ext4     1008G  459G  498G  48% /alidata2

可以看到我们大数据服务器使用的文件系统是ext4的,是不是因为这个导致的I/O性能差呢?

要判断这个说法是否正确,我们可以先看看Linux文件系统是怎么工作的,然后通过测试来验证理论,通常就能得出结论。

Linux文件系统是怎么工作的:
Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。

索引节点:
简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。

目录项:
简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。

索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。

索引节点和目录项纪录了文件的元数据,以及文件间的目录关系。

磁盘读写的最小单位是扇区,然而扇区只有 512B 大小,如果每次都读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。

目录项:
本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据。为了协调慢速磁盘与快速 CPU 的性能差异,文件内容会缓存到 **页缓存 Cache **中。

索引节点自然也会缓存到内存中,加速文件的访问。

磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块、索引节点区和数据块区。其中:

  • 超级块,存储整个文件系统的状态。
  • 索引节点区,用来存储索引节点。
  • 数据块区,则用来存储文件数据。

虚拟文件系统
目录项、索引节点、逻辑块以及超级块,构成了 Linux 文件系统的四大基本要素。不过,为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽象层,也就是虚拟文件系统 VFS(Virtual File System)。

VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互就可以了,而不需要再关心底层各种文件系统的实现细节。

通过这张图,在 VFS 的下方,Linux 支持各种各样的文件系统,如 Ext4、XFS、NFS 等等。按照存储位置的不同,这些文件系统可以分为三类。

第一类是基于磁盘的文件系统,也就是把数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等,都是这类文件系统。

第二类是基于内存的文件系统,也就是我们常说的虚拟文件系统。这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的 /proc 文件系统,其实就是一种最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要向用户空间导出层次化的内核对象。

第三类是网络文件系统,也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等。

文件系统 I/O
VFS 提供了一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。

cat 命令来说,它首先调用 open() ,打开一个文件;然后调用 read() ,读取文件的内容;最后再调用 write() ,把文件内容输出到控制台的标准输出中:

int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

文件读写方式差异:

第一种,根据是否利用标准库缓存,可以把文件 I/O 分为缓冲 I/O 与非缓冲 I/O。

缓冲 I/O,是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。

非缓冲 I/O,是指直接通过系统调用来访问文件,不再经过标准库缓存。

注意,这里所说的“缓冲”,是指标准库内部实现的缓存。比方说,很多程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存了起来。

无论缓冲 I/O 还是非缓冲 I/O,它们最终还是要经过系统调用来访问文件。系统调用后,还会通过页缓存,来减少磁盘的 I/O 操作。

第二,根据是否利用操作系统的页缓存,可以把文件 I/O 分为直接 I/O 与非直接 I/O。

直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。

非直接 I/O 正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘。

想要实现直接 I/O,需要你在系统调用中,指定 O_DIRECT 标志。如果没有设置过,默认的是非直接 I/O。

过要注意,直接 I/O、非直接 I/O,本质上还是和文件系统交互。在数据库等场景中,跳过文件系统读写磁盘的情况,也就是我们通常所说的裸 I/O。

第三,根据应用程序是否阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O:

阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务。

非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。

访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访问;而如果不做任何设置,默认的就是阻塞访问。

第四,根据是否等待响应结果,可以把文件 I/O 分为同步和异步 I/O:

同步 I/O,是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应。

异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。

在操作文件时,如果你设置了 O_SYNC 或者 O_DSYNC 标志,就代表同步 I/O。如果设置了 O_DSYNC,就要等文件数据写入磁盘后,才能返回;而 O_SYNC,则是在 O_DSYNC 基础上,要求文件元数据也要写入磁盘后,才能返回。

在访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。

性能观测:
缓存:
页缓存大小:
free 输出的 Cache,是页缓存和可回收 Slab 缓存的和,你可以从 /proc/meminfo ,直接得到它们的大小:

free -m
              total        used        free      shared  buff/cache   available
Mem:          32012         830       14180           0       17000       30714
Swap:             0           0           0
 
cat /proc/meminfo | grep -E "SReclaimable|Cached"
Cached:         15480848 kB
SwapCached:            0 kB
SReclaimable:    1494732 kB

目录项和索引节点缓存:
内核使用 Slab 机制,管理目录项和索引节点的缓存。/proc/meminfo 只给出了 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 这个文件。

cat /proc/slabinfo | grep -E "^#|dentry|inode"
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
ext4_inode_cache   49113  49476   1032   31    8 : tunables    0    0    0 : slabdata   1596   1596      0
mqueue_inode_cache     36     36    896   36    8 : tunables    0    0    0 : slabdata      1      1      0
hugetlbfs_inode_cache     26     26    608   26    4 : tunables    0    0    0 : slabdata      1      1      0
sock_inode_cache     825    825    640   25    4 : tunables    0    0    0 : slabdata     33     33      0
shmem_inode_cache   2175   2304    680   24    4 : tunables    0    0    0 : slabdata     96     96      0
proc_inode_cache  403428 403560    656   24    4 : tunables    0    0    0 : slabdata  16815  16815      0
inode_cache        31374  31374    592   27    4 : tunables    0    0    0 : slabdata   1162   1162      0
dentry            4210279 4210500    192   42    2 : tunables    0    0    0 : slabdata 100250 100250      0
selinux_inode_security  11159  11679     80   51    1 : tunables    0    0    0 : slabdata    229    229      0

dentry 行表示目录项缓存,inode_cache 行,表示 VFS 索引节点缓存,其余的则是各种文件系统的索引节点缓存。

实时查看占用内存最多的缓存类型:

slabtop
 
# 按下c按照缓存大小排序,按下a按照活跃对象数排序
Active / Total Objects (% used)    : 8302900 / 8324586 (99.7%)
 Active / Total Slabs (% used)      : 211565 / 211565 (100.0%)
 Active / Total Caches (% used)     : 69 / 94 (73.4%)
 Active / Total Size (% used)       : 1537408.68K / 1540997.50K (99.8%)
 Minimum / Average / Maximum Object : 0.01K / 0.18K / 8.00K
 
  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
4210500 4210460  99%    0.19K 100250       42    802000K dentry
2966379 2964804  99%    0.10K  76061       39    304244K buffer_head
403560 403484  99%    0.64K  16815       24    269040K proc_inode_cache
215136 201311  93%    0.12K   6723       32     26892K kmalloc-128
157182 156980  99%    0.04K   1541      102      6164K ext4_extent_status
 72744  72696  99%    0.57K   2598       28     41568K radix_tree_node
 49476  49113  99%    1.01K   1596       31     51072K ext4_inode_cache
 48064  44450  92%    0.06K    751       64      3004K kmalloc-64
 31374  31374 100%    0.58K   1162       27     18592K inode_cache
 25344  25233  99%    0.11K    704       36      2816K sysfs_dir_cache
 13090  13090 100%    0.05K    154       85       616K shared_policy_node
 11679  11159  95%    0.08K    229       51       916K selinux_inode_security

从这个结果你可以看到,在我的系统中,目录项和buffer_head节点占用了最多的 Slab 缓存。加起来1,106 MB 左右。

磁盘性能指标:
磁盘性能的衡量标准,必须要提到五个常见指标,也就是我们经常用到的,使用率、饱和度、IOPS、吞吐量以及响应时间等。这五个指标,是衡量磁盘性能的基本指标。

  • 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
  • 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
  • IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
  • 吞吐量,是指每秒的 I/O 请求大小。
  • 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。

这里要注意的是,使用率只考虑有没有 I/O,而不考虑 I/O 的大小。换句话说,当使用率是 100% 的时候,磁盘依然有可能接受新的 I/O 请求。

不要孤立地去比较某一指标,而要结合读写比例、I/O 类型(随机还是连续)以及 I/O 的大小,综合来分析。

举个例子,在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的整体性能;

而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能。

磁盘I/O观测:
第一个要观测的,是每块磁盘的使用情况。

iostat 是最常用的磁盘 I/O 性能观测工具,它提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,这些指标实际上来自 /proc/diskstats。iostat 的输出界面如下。

yum -y install sysstat
# -d -x表示显示所有磁盘I/O的指标
iostat -d -x 1
 
[root@prod-hadoop-slave011 ~]# iostat -d -x 1
Linux 3.10.0-957.el7.x86_64 (prod-hadoop-slave011)      08/09/2021      _x86_64_        (96 CPU)
 
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sdq               0.00     9.36    0.09   19.34     1.63  4698.64   484.05     0.14    7.08    4.01    7.10   1.14   2.21
sdl               0.02   100.65   21.00   47.04  2012.74  6894.55   261.82     0.13    1.84   18.19    4.07   1.94  13.18
sdm               0.02    98.87   21.08   45.91  2041.40  6924.47   267.69     0.32    4.82   18.24    8.43   1.93  12.91
sdo               0.02    97.92   21.19   45.71  2090.27  6948.25   270.23     0.24    3.65   17.89    6.86   1.94  12.98
sdn               0.02   100.28   21.15   47.33  2034.64  6805.74   258.18     0.07    0.96   18.11    2.77   1.91  13.08
sdp               0.02   101.32   21.11   47.51  2046.12  6989.55   263.35     0.09    1.26   18.57    3.01   1.97  13.52
sdi               0.02    94.99   20.51   44.92  1978.91  6864.81   270.33     0.18    2.81   17.92    5.89   1.92  12.55
sdd               0.02    98.49   21.01   46.34  1993.63  6760.27   259.92     0.27    3.98   17.91    7.34   1.90  12.77
sda               0.02   101.82   20.59   53.85  1903.05 11011.56   346.95     0.38    5.17   21.55    7.23   2.09  15.56
sdj               0.02   101.02   20.98   52.22  1960.18  9737.03   319.61     0.14    1.95    0.24    2.63   2.03  14.86
sdc               0.02   100.31   20.74   46.27  1956.05  6984.54   266.83     0.18    2.71   17.71    5.68   1.93  12.94
sdb               0.02   103.22   21.32   46.50  2040.28  7048.35   268.01     0.41    5.99   17.86    0.54   1.98  13.42
sdg               0.02   100.37   20.73   46.83  1870.52  6887.23   259.24     0.04    0.66   18.52    2.32   1.93  13.04
sdh               0.02     8.08   13.39   11.70  2028.46  3096.56   408.67     0.43   17.01   15.64   18.57   2.43   6.10
sde               0.02   102.60   21.18   47.93  2088.87  6877.32   259.49     0.09    1.32   18.42    3.12   1.94  13.38
sdk               0.02   101.09   21.09   48.10  1998.88  6878.76   256.63     0.20    2.94   19.01    5.22   1.93  13.36
sdf               0.02   101.47   20.87   47.40  1956.94  6868.25   258.56     0.38    5.60   18.13    0.08   1.93  13.19

  • %util ,就是我们前面提到的磁盘 I/O 使用率;
  • r/s+ w/s ,就是 IOPS;
  • rkB/s+wkB/s ,就是吞吐量;
  • r_await+w_await ,就是响应时间。

从 iostat 并不能直接得到磁盘饱和度。事实上,饱和度通常也没有其他简单的观测方法,不过,可以把观测到的,平均请求队列长度或者读写请求完成的等待时间,跟基准测试的结果(比如通过 fio)进行对比,综合评估磁盘的饱和情况。

进程I/O观测:
上面提到的 iostat 只提供磁盘整体的 I/O 性能数据,缺点在于,并不能知道具体是哪些进程在进行磁盘读写。

要观察进程的 I/O 情况,可以使用 pidstat 和 iotop 这两个工具。

pidstat -d 1
Linux 3.10.0-957.el7.x86_64 (prod-hadoop-slave011)      08/09/2021      _x86_64_        (96 CPU)
 
05:13:47 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
05:13:48 PM     0     19743      0.00     88.07      0.00  jbd2/sdq2-8
05:13:48 PM     0     27406      0.00   1948.62      0.00  jbd2/sdf-8
05:13:48 PM     0     27408      0.00  26488.07      0.00  jbd2/sdm-8
05:13:48 PM     0     27765      0.00   1834.86      0.00  jbd2/sdj-8
05:13:48 PM   986     34478      0.00   2066.06      0.00  java
05:13:48 PM   993     39313  10190.83   1768.81      0.00  java
05:13:48 PM   986     45220      0.00      7.34      0.00  java
05:13:48 PM   986    155889      0.00      7.34      0.00  java
05:13:48 PM   986    173769      0.00      3.67      0.00  java
05:13:48 PM   986    189825      0.00      3.67      0.00  java
05:13:48 PM   986    200413      0.00      3.67      0.00  java
05:13:48 PM     0    259950      3.67      0.00      0.00  kworker/u898:1
05:13:48 PM   986    300636      0.00     14.68      0.00  java
05:13:48 PM   986    386405      0.00  12242.20   6612.84  java
05:13:48 PM   986    396501      0.00   9544.95   4403.67  java
05:13:48 PM   986    417307      0.00  16183.49   8803.67  java
05:13:48 PM   986    426943      0.00   8154.13   4407.34  java
05:13:48 PM   986    434931      0.00     11.01      0.00  java
05:13:48 PM   986    437415      0.00   8055.05   4403.67  java
05:13:48 PM   986    437438      0.00  12091.74   6605.50  java
05:13:48 PM   986    456277      0.00      7.34      0.00  java

从 pidstat 的输出你能看到,它可以实时查看每个进程的 I/O 情况,包括下面这些内容。

  • 用户 ID(UID)和进程 ID(PID)
  • 每秒读取的数据大小(kB_rd/s) ,单位是 KB
  • 每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB
  • 每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB

除了可以用 pidstat 实时查看,根据 I/O 大小对进程排序,也是性能分析中一个常用的方法。

iotop。它是一个类似于 top 的工具,你可以按照 I/O 大小对进程排序,然后找到 I/O 较大的那些进程。iotop 的输出如下所示:

yum -y install iotop
 
iotop
 
Total DISK READ :       8.14 M/s | Total DISK WRITE :    1291.43 K/s
Actual DISK READ:       8.87 M/s | Actual DISK WRITE:      16.76 M/s
   TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 53036 be/4 root        0.00 B/s    0.00 B/s  0.00 % 13.51 % [kworker/u897:1]
259950 be/4 root        0.00 B/s    0.00 B/s  0.00 %  4.50 % [kworker/u898:1]
 27467 be/3 root        0.00 B/s   77.78 K/s  0.00 %  3.09 % [jbd2/sdp-8]
181395 be/4 yarn        0.00 B/s    0.00 B/s  0.00 %  1.65 % java -Dproc_nodemanager -Xmx1000m -Djava~adoop.yarn.server.nodemanager.NodeManager
 27736 be/3 root        0.00 B/s   22.01 K/s  0.00 %  1.25 % [jbd2/sdn-8]
 27765 be/3 root        0.00 B/s  148.22 K/s  0.00 %  1.18 % [jbd2/sdj-8]
 27716 be/3 root        0.00 B/s   30.82 K/s  0.00 %  1.16 % [jbd2/sdo-8]
 27406 be/3 root        0.00 B/s  277.36 K/s  0.00 %  1.15 % [jbd2/sdf-8]
 27866 be/3 root        0.00 B/s  167.30 K/s  0.00 %  1.13 % [jbd2/sdd-8]
 27880 be/3 root        0.00 B/s   36.69 K/s  0.00 %  0.99 % [jbd2/sdk-8]
 27771 be/3 root        0.00 B/s   71.91 K/s  0.00 %  0.06 % [jbd2/sde-8]

从这个输出,你可以看到,前两行分别表示,进程的磁盘读写大小总数和磁盘真实的读写大小总数。

因为缓存、缓冲区、I/O 合并等因素的影响,它们可能并不相等。

剩下的部分,则是从各个角度来分别表示进程的 I/O 情况,包括线程 ID、I/O 优先级、每秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I/O 的时钟百分比等。

在有了上面的理论知识后,我们来实战看I/O:
只能凌晨1:30左右进行分析,白天时间段大数据服务器的IOPS并没有压力。

  1. 先查看服务器基本压力情况:

服务器的CPU的1分钟负载也比较高,72C的CPU,负载到了58.69.达到81%。

从这里可以看到,服务器的部分cpu线程的iowait值(等待输入输出的CPU时间百分比,通常需要低于15%)是偏高的。

free -m
total used free shared buff/cache available
Mem: 257344 96983 720 4243 159640 154986
Swap: 0 0 0

内存相对空闲,还有154G可用。

  1. 分析磁盘的IO情况:
iostat -x -d 1
 
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sdo               0.00     0.00    0.00    1.00     0.00    32.00    64.00     0.00    0.00    0.00    0.00   0.00   0.00
sdg               0.00     0.00  116.00    0.00  9452.00     0.00   162.97     2.26   19.46   19.46    0.00   0.95  11.00
sdm              11.00     0.00  111.00  547.00 25384.00 138648.00   498.58   137.10  208.07  136.30  222.63   1.52 100.10
sda               0.00     0.00    1.00  123.00     4.00 30584.00   493.35     3.89   31.36   17.00   31.48   1.38  17.10
sdi               0.00     0.00   63.00    0.00 13448.00     0.00   426.92     0.50    7.94    7.94    0.00   1.44   9.10
sdl               0.00     0.00    8.00    0.00  2048.00     0.00   512.00     0.28   34.62   34.62    0.00   5.88   4.70
sdk               0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
sdd               0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
sdn               0.00     0.00   17.00    0.00  4096.00     0.00   481.88     0.05    2.82    2.82    0.00   0.88   1.50
sdh               0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
sde               0.00    66.00    2.00    3.00   128.00   276.00   161.60     0.03    5.00    4.50    5.33   4.00   2.00
sdj               0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
sdf               0.00     1.00    4.00  661.00   140.00 168712.00   507.83   127.10  163.33   67.50  163.91   1.50 100.00
sdb               2.00     1.00   42.00  220.00  8280.00 55376.00   485.92    41.04   99.24   43.90  109.80   1.19  31.20
sdc               0.00     0.00   60.00    0.00 12532.00     0.00   417.73     0.54   11.92   11.92    0.00   1.32   7.90

可以看到,有两块磁盘的IO使用率已经是100%,分别是:sdm和sdf

基础数据解读:

磁盘使用率IOPS吞吐量响应时间
sdm100%658164,032kB/s 164mB358ms
sdf100%665168,852kB/s 168mB230ms

可用看到这两块磁盘使用率都已经到100%,已经到达磁盘性能瓶颈。

IOPS也都达到了650.

吞吐量,也达到164m

响应时间已经比较长,超过200ms.

综上所述,这两块磁盘已经饱和。

其他磁盘,除了sdb外,整体来看还是比较健康。

  1. 观察进程的IO情况:
pidstat -d 1
 
Average:      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
Average:        0     10689      0.00      1.31      0.00  jbd2/sdo5-8
Average:        0     13117      0.00     90.49      0.00  jbd2/sdj-8
Average:        0     13155      0.00    242.62      0.00  jbd2/sdi-8
Average:        0     13160      0.00    828.85      0.00  jbd2/sdh-8
Average:        0     13255      0.00     82.62      0.00  jbd2/sdc-8
Average:        0     13308      0.00    225.57      0.00  jbd2/sdm-8
Average:        0     13312      0.00    626.89      0.00  jbd2/sdg-8
Average:        0     13335      0.00     61.64      0.00  jbd2/sdf-8
Average:        0     13348      0.00    253.11      0.00  jbd2/sde-8
Average:        0     13377      0.00   7062.30      0.00  jbd2/sdl-8
Average:        0     13392      0.00    605.90      0.00  jbd2/sdd-8
Average:        0     15470      0.00      6.56      0.00  auditd
Average:        0     51790      2.62      0.00      0.00  kworker/u897:4
Average:      977     54312      0.00      3.93      0.00  java
Average:      983    116732      0.00      1.31      0.00  bash
Average:      983    116785      0.00    135.08      1.31  java
Average:      990    117398      9.18      0.00      0.00  du
Average:      983    163972      0.00    236.07    121.97  java
Average:        0    207059      0.00      9.18      0.00  rsyslogd
Average:        0    403042      3.93      0.00      0.00  kworker/u897:2
Average:      983    412440      0.00   1231.48      0.00  java
Average:      990    423113  62405.25  14553.44    640.00  java

可以看到java进程423113读取量最大,达到了62M,写入达到14M。

  1. 分析进程详细的读写情况:
yum -y install strace
yum -y install gdb
 
strace -f -T -tt -p 423113
strace: Process 423113 attached with 438 threads
[pid 213217] 01:50:16.207786 epoll_ctl(1348, EPOLL_CTL_DEL, 1134, 0x7fee7e9aa3d0 <unfinished ...>
[pid 213211] 01:50:16.207885 write(1134, "\0\1\0\0\0\31\t\0\nz\0\0\0\0\0\21{\0\0\0\0\0\0\0\30\0%\0\376\0\0\371"..., 65563 <unfinished ...>
[pid 213139] 01:50:16.207920 epoll_wait(1288,  <unfinished ...>
[pid 209857] 01:50:16.208139 epoll_wait(1479,  <unfinished ...>
[pid 206824] 01:50:16.208155 futex(0x7feeb439d254, FUTEX_WAIT_PRIVATE, 865, NULL <unfinished ...>
[pid 206806] 01:50:16.208207 epoll_wait(1330,  <unfinished ...>
[pid 206728] 01:50:16.208222 futex(0x7feeb52a0d54, FUTEX_WAIT_PRIVATE, 1091, NULL <unfinished ...>
[pid 206727] 01:50:16.208271 epoll_wait(1455,  <unfinished ...>
[pid 206401] 01:50:16.208286 futex(0x7feeb4725354, FUTEX_WAIT_PRIVATE, 857, NULL <unfinished ...>
[pid 206400] 01:50:16.208324 epoll_wait(1358,  <unfinished ...>
[pid 206306] 01:50:16.208341 epoll_wait(1267,  <unfinished ...>
[pid 206304] 01:50:16.208356 epoll_wait(1341,  <unfinished ...>
[pid 206291] 01:50:16.208373 futex(0x242c554, FUTEX_WAIT_PRIVATE, 1335, NULL <unfinished ...>
[pid 206290] 01:50:16.208395 epoll_wait(1205,  <unfinished ...>
[pid 205887] 01:50:16.208412 epoll_wait(1477,  <unfinished ...>
[pid 205884] 01:50:16.208426 epoll_wait(1370,  <unfinished ...>
[pid 205807] 01:50:16.208443 futex(0x7feeb4ecf054, FUTEX_WAIT_PRIVATE, 619, NULL <unfinished ...>
[pid 205805] 01:50:16.208461 epoll_wait(1335,  <unfinished ...>
[pid 205450] 01:50:16.208480 futex(0x7feeb4084954, FUTEX_WAIT_PRIVATE, 1087, NULL <unfinished ...>
[pid 205449] 01:50:16.208518 epoll_wait(1406,  <unfinished ...>
[pid 186647] 01:50:16.208533 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 123216] 01:50:16.208554 epoll_wait(1309,  <unfinished ...>
[pid 123211] 01:50:16.208570 epoll_wait(1230,  <unfinished ...>
[pid 123031] 01:50:16.208589 epoll_wait(1343,  <unfinished ...>
[pid 123029] 01:50:16.208610 epoll_wait(1304,  <unfinished ...>
[pid 122897] 01:50:16.208625 futex(0x7feeb41c9a54, FUTEX_WAIT_PRIVATE, 4653, NULL <unfinished ...>
[pid 122877] 01:50:16.208643 epoll_wait(1386,  <unfinished ...>
[pid 121607] 01:50:16.208788 epoll_wait(1189,  <unfinished ...>
[pid 86882] 01:50:16.208809 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 51130] 01:50:16.208824 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 416157] 01:50:16.208947 futex(0x7feeb4740c54, FUTEX_WAIT_PRIVATE, 4783, NULL <unfinished ...>
[pid 416151] 01:50:16.208966 epoll_wait(1379,  <unfinished ...>
[pid 58659] 01:50:16.209062 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 300324] 01:50:16.209082 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 262493] 01:50:16.209344 futex(0x7feeb4648f44, FUTEX_WAIT_PRIVATE, 35, NULL <unfinished ...>
[pid 28397] 01:50:16.209365 epoll_wait(1054,  <unfinished ...>
[pid  6182] 01:50:16.209395 epoll_wait(1051,  <unfinished ...>
[pid 287706] 01:50:16.210001 epoll_wait(946,  <unfinished ...>
[pid 262860] 01:50:16.210027 epoll_wait(943,  <unfinished ...>
[pid 241208] 01:50:16.210045 epoll_wait(940,  <unfinished ...>
[pid 213729] 01:50:16.210063 epoll_wait(937,  <unfinished ...>
[pid 212726] 01:50:16.210083 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 178454] 01:50:16.210099 epoll_wait(1183,  <unfinished ...>
[pid 21464] 01:50:16.211778 epoll_wait(637,  <unfinished ...>
[pid  1387] 01:50:16.211802 epoll_wait(634,  <unfinished ...>
[pid 454917] 01:50:16.211817 futex(0x7feeb40d34b4, FUTEX_WAIT_PRIVATE, 2403, NULL <unfinished ...>
[pid 449864] 01:50:16.211835 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 449862] 01:50:16.211855 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 449855] 01:50:16.211873 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 449845] 01:50:16.211896 futex(0x7feebc0ff5d4, FUTEX_WAIT_PRIVATE, 12213239, NULL <unfinished ...>
[pid 449814] 01:50:16.211913 futex(0x7feeb40ed704, FUTEX_WAIT_PRIVATE, 12215805, NULL <unfinished ...>
[pid 449806] 01:50:16.211933 futex(0x7feeb43adbe4, FUTEX_WAIT_PRIVATE, 12209489, NULL <unfinished ...>
[pid 449802] 01:50:16.211953 futex(0x7feed3c68704, FUTEX_WAIT_PRIVATE, 12213583, NULL <unfinished ...>
[pid 448495] 01:50:16.211968 futex(0x7feeb4b05774, FUTEX_WAIT_PRIVATE, 2405, NULL <unfinished ...>
[pid 441954] 01:50:16.211986 futex(0x7feeb4340eb4, FUTEX_WAIT_PRIVATE, 2405, NULL <unfinished ...>
[pid 439884] 01:50:16.212001 epoll_wait(631,  <unfinished ...>
[pid 434867] 01:50:16.212018 futex(0x7feed35468c4, FUTEX_WAIT_PRIVATE, 2405, NULL <unfinished ...>
[pid 431057] 01:50:16.212039 epoll_wait(1140,  <unfinished ...>
[pid 431054] 01:50:16.212054 futex(0x7feed377da54, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
[pid 431006] 01:50:16.212071 futex(0x7feeb4353974, FUTEX_WAIT_PRIVATE, 495, NULL <unfinished ...>
[pid 430999] 01:50:16.212091 epoll_wait(628,  <unfinished ...>
[pid 430915] 01:50:16.212106 epoll_wait(625,  <unfinished ...>
[pid 428185] 01:50:16.212121 futex(0x7feebc1c1ef4, FUTEX_WAIT_PRIVATE, 2405, NULL <unfinished ...>
[pid 424538] 01:50:16.212137 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 424067] 01:50:16.216158 epoll_wait(1063,  <unfinished ...>
[pid 424066] 01:50:16.216209 epoll_wait(1069,  <unfinished ...>
[pid 424065] 01:50:16.216253 accept(176,  <unfinished ...>
[pid 424064] 01:50:16.216324 accept(175,  <unfinished ...>
[pid 424063] 01:50:16.216376 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 424062] 01:50:16.216424 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 424016] 01:50:16.216475 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 424015] 01:50:16.216523 epoll_wait(1066,  <unfinished ...>
[pid 423958] 01:50:16.216568 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423954] 01:50:16.216613 epoll_wait(193,  <unfinished ...>
[pid 423891] 01:50:16.216660 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423857] 01:50:16.216704 epoll_wait(190,  <unfinished ...>
[pid 423848] 01:50:16.216751 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423832] 01:50:16.216800 restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 423820] 01:50:16.216846 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423760] 01:50:16.216891 futex(0x7feed112fb54, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
[pid 423665] 01:50:16.216944 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423602] 01:50:16.216992 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423601] 01:50:16.217045 futex(0x7feed0c12854, FUTEX_WAIT_PRIVATE, 6597, NULL <unfinished ...>
[pid 423600] 01:50:16.217090 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 423599] 01:50:16.217135 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>

Futex 是 Fast Userspace muTexes 的缩写,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率,和磁盘IO没有直接的关系

epoll_xxx()本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

restart_syscall():刚才的系统调用没有完成的时候被迫去做别的

accept():系统调用用于基于连接的套接字类型(SOCK_STREAM,SOCK_SEQPACKET)。提取完成连接队列中的第一个连接请求,创建一个新的连接套接字,并返回一个新的文件描述符,指该套接字。新创建的套接字处于监听状态。原始套接字 sockfd 不受此调用。

为了确保 accept() 从未阻塞,通过套接字sockfd中需要有O_NONBLOCK标志设置.

read():函数从打开的设备或文件中读取数据,读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

write():函数向打开的设备或文件中写数据,写常规文件是不会阻塞的,而向终端设备或网络写则不一定

lseek():移动文件的读写位置,每一个已打开的文件都有一个读写位置, 当打开文件时通常其读写位置是指向文件开头, 若是以附加的方式打开文件(如O_APPEND), 则读写位置会指向文件尾. 当read()或write()时, 读写位置会随之增加,lseek()便是用来控制该文件的读写位置.

通常的读文件流程:

  1. 进程调用库函数向内核发起读文件请求;
  2. 内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
  3. 调用该文件可用的系统调用函数read()
  4. read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
  5. 在inode中,通过文件内容偏移量计算出要读取的页;
  6. 通过inode找到文件对应的address_space;
  7. 在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
  8. 如果页缓存命中,那么直接返回文件内容;
  9. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6步查找页缓存;
  10. 文件内容读取成功。

通常的写文件
前5步和读文件一致,在address_space中查询对应页的页缓存是否存在:
6. 如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。
7. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。
8. 一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:

  1. 手动调用sync()或者fsync()系统调用把脏页写回
  2. pdflush进程会定时把脏页写回到磁盘

同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放

可以看到,从2:05:50.469275到02:06:20.638712,约30秒里:

注: 上面的原始进程跟踪内容有删除,因为太长,只保留了一部分.

1.都是使用read()和write()函数进行系统调用,没有使用fopen(),fread()到标准库的缓存,只使用到了页缓存和缓冲区。

优化建议:
C 标准库提供的 fopen、fread 等库函数,都会利用标准库的缓存,减少磁盘的操作。直接使用 open、read 等系统调用时,就只能利用操作系统提供的页缓存和缓冲区等,而没有库函数的缓存可用。

在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。

在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC。

yum -y install lsof
 
lsof -p 43379

可以看到hdfs也是写pipe为主,读本地和pipe为辅,但是由写日志的情况,这个能否分离出来?

这里可以看到有大量的读写,但是并没有集中在读写某个文件,而是一批文件,这里并不能看出来问题。

通过bcc来跟踪内核中文件的读写情况,并输出线程ID,读写大小,读写类型以及文件名称。

yum -y install bcc
 
cd /usr/share/bcc/tools/
 
./filetop -C
02:50:57 loadavg: 66.27 73.17 76.12 44/9573 27533
 
TID    COMM             READS  WRITES R_Kb    W_Kb    T FILE
27048  java             0      334    0       21209   R blk_2370813858
119124 java             0      321    0       15094   R attempt_1610954855077_4866041...
27412  java             0      1979   0       7914    R tmp_hive-exec-core.jar
26272  java             0      100    0       6350    R blk_2370813808
17214  java             0      73     0       4635    R blk_2370813527
27412  java             0      1070   0       4278    R tmp_hbase-server.jar
12580  java             63     0      4246    0       R map_23.out.merged
424485 java             381    0      1524    0       R blk_2239530298
424487 java             381    0      1524    0       R blk_2340545115
24795  java             1545   0      1367    0       R job.jar
24785  java             1531   0      1358    0       R job.jar
24792  java             1422   0      1250    0       R job.jar
24784  java             1283   0      1171    0       R job.jar
24792  java             1197   0      1161    0       R hadoop-hdfs-2.6.0-cdh5.13.2.jar
24784  java             1081   0      1043    0       R hadoop-hdfs-2.6.0-cdh5.13.2.jar
24785  java             1075   0      1034    0       R hadoop-hdfs-2.6.0-cdh5.13.2.jar
424492 java             254    0      1016    0       R blk_2138811993
424490 java             254    0      1016    0       R blk_2055035875
424478 java             254    0      1016    0       R blk_2021470467
424486 java             254    0      1016    0       R blk_2105449077
 
02:50:58 loadavg: 66.27 73.17 76.12 79/9871 28631
 
TID    COMM             READS  WRITES R_Kb    W_Kb    T FILE
27048  java             0      413    0       26225   R blk_2370813858
26272  java             0      184    0       11684   R blk_2370813808
28005  java             72     0      10528   0       R libjvm.so
28002  java             72     0      10528   0       R libjvm.so
28003  java             72     0      10528   0       R libjvm.so
28011  java             72     0      10528   0       R libjvm.so
28005  java             783    0      3128    0       R libzip.so
28002  java             783    0      3128    0       R libzip.so
28003  java             783    0      3128    0       R libzip.so
28011  java             783    0      3128    0       R libzip.so
28003  java             1684   0      2788    0       R rt.jar
28011  java             1682   0      2787    0       R rt.jar
28005  java             1678   0      2782    0       R rt.jar
28002  java             1680   0      2779    0       R rt.jar
12580  java             38     0      2625    0       R map_23.out.merged
28346  java             1006   0      1894    0       R rt.jar
17214  java             0      29     0       1831    R blk_2370813527
424490 java             254    0      1016    0       R blk_2055035875
424478 java             254    0      1016    0       R blk_2021470467
424489 java             254    0      1016    0       R blk_2289713741
 
02:50:59 loadavg: 66.27 73.17 76.12 36/9908 29764
 
TID    COMM             READS  WRITES R_Kb    W_Kb    T FILE
119128 java             0      367    0       18792   R attempt_1610954855077_4866041...
2378   java             0      200    0       12119   R shuffle_6_139_0.data.e5f892e1...
453088 java             0      200    0       12096   R shuffle_6_138_0.data.0f90f3f3...
2375   java             0      200    0       12082   R shuffle_6_125_0.data.444af3f4...
26272  java             0      166    0       10541   R blk_2370813808
28005  java             72     0      10528   0       R libjvm.so
28002  java             72     0      10528   0       R libjvm.so
28003  java             72     0      10528   0       R libjvm.so
28011  java             72     0      10528   0       R libjvm.so
27048  java             0      139    0       8703    R blk_2370813858
12580  java             47     0      3149    0       R map_23.out.merged
28011  java             1636   0      2860    0       R rt.jar
28002  java             1620   0      2813    0       R rt.jar
28005  java             1616   0      2806    0       R rt.jar
28003  java             1580   0      2728    0       R rt.jar
424489 java             381    0      1524    0       R blk_2289713741
424485 java             254    0      1016    0       R blk_2239530298
424492 java             254    0      1016    0       R blk_2138811993
424477 java             254    0      1016    0       R blk_2289813709
424490 java             254    0      1016    0       R blk_2055035875
 
Detaching...
 
# 可以看到,这里并没有特别突出的线程在一直大量的读写。
 
#opensnoop同属于 bcc 软件包,可以动态跟踪内核中的 open 系统调用。这样,我们就可以找出这些 txt 文件的路径。
./opensnoop
PID    COMM               FD ERR PATH
423113 java              831   0 /alidata1/data/dfs/dn/current/BP-2115797078-172.16.50.2-1                                                                                                551852626078/current/finalized/subdir75/subdir180/blk_2370548964_1708797563.meta
423113 java              831   0 /alidata1/data/dfs/dn/current/BP-2115797078-172.16.50.2-1                                                                                                551852626078/current/finalized/subdir75/subdir180/blk_2370548964_1708797563.meta
423113 java              831   0 /alidata1/data/dfs/dn/current/BP-2115797078-172.16.50.2-1                                                                                                551852626078/current/finalized/subdir75/subdir180/blk_2370548964_1708797563.meta
423113 java             1103   0 /alidata1/data/dfs/dn/current/BP-2115797078-172.16.50.2-1                                                                                                551852626078/current/finalized/subdir75/subdir180/blk_2370548964
423113 java             1146   0 /alidata13/data/dfs/dn/current/BP-2115797078-172.16.50.2-                                                                                                1551852626078/current/finalized/subdir75/subdir179/blk_2370548688_1708797264.meta
423113 java             1146   0 /alidata13/data/dfs/dn/current/BP-2115797078-172.16.50.2-                                                                                                1551852626078/current/finalized/subdir75/subdir179/blk_2370548688_1708797264.meta
423113 java             1146   0 /alidata13/data/dfs/dn/current/BP-2115797078-172.16.50.2-                                                                                                1551852626078/current/finalized/subdir75/subdir179/blk_2370548688_1708797264.meta
423113 java             1172   0 /alidata13/data/dfs/dn/current/BP-2115797078-172.16.50.2-                                                                                                1551852626078/current/finalized/subdir75/subdir179/blk_2370548688
423113 java              900   0 /alidata10/data/dfs/dn/current/BP-2115797078-172.16.50.2- 

从这里来看,没有体现出来问题,整体来看是健康的。

总结:

1.hisee中的数据不是很准确(因为是每15秒取一个监控点,如果IOPS的峰值时间小于15秒,那就无法取到峰值的IOPS),实际的IOPS,比hisee中高,可以达到650的IOPS。

所以也就回答了上面的问题,真实的IOPS不是250,而是可以达到650,磁盘的IOPS并不差,这里就答复第一个问题,服务器选型选配没有问题,服务器的磁盘I/O性能是合理的.

2.整体是使用read()和write()函数进行读写,没有用到标准库的缓存,吞吐量和IOPS有进一步优化的空间。

3.有日志读写和业务数据读写混杂的情况,考虑把日志的读写分离出来到单独的磁盘中,减轻业务读写的IO压力.

怎么解决?

目标:
把目前大数据离线计算服务器的单盘IOPS从峰值的250,提升到300甚至更高。

I/O性能优化:
思路:
根据下图,从应用程序,文件系统和磁盘三个角度进行优化。

因为应用层是大数据同学负责,暂时还不具备这块的优化能力,先做好文件系统和磁盘层面的优化。

文件系统优化:
XFS相比ext4 优势:

1.通过B+树来索引inode和数据块,而ext4使用的链表,虽然目录索引采用了Hash Index Tree, 但是依然限制高度为2,理论上来说随着文件数量的增加ext4的查找,创建文件,也就是读文件和写文件的性能都会下降,但是xfs没有这个问题。

做过实际测试
Ext4的单个目录文件超过200W个,性能下降的就比较厉害。

B+树:
现场演示B+树的生成过程:

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

现场演示链表的生成过程:

https://www.cs.usfca.edu/~galles/visualization/StackLL.html

1.更换ext4文件系统为xfs文件系统:
理论上xfs比ext4文件系统更适合大数据的文件系统,但是是否适合我们公司目前的大数据情况,还需要经过测试验证。

[root@prod-hadoop-slave015 ~]# df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/sdq3      xfs        60G  1.6G   59G   3% /
devtmpfs       devtmpfs  221G     0  221G   0% /dev
tmpfs          tmpfs     221G     0  221G   0% /dev/shm
tmpfs          tmpfs     221G   19M  221G   1% /run
tmpfs          tmpfs     221G     0  221G   0% /sys/fs/cgroup
/dev/sdq2      xfs       194M  108M   87M  56% /boot
/dev/sdq1      vfat      200M   12M  189M   6% /boot/efi
tmpfs          tmpfs      45G     0   45G   0% /run/user/0
/dev/sda       xfs       2.2T  529M  2.2T   1% /alidata
/dev/sdj       xfs       2.2T   33M  2.2T   1% /alidata1
/dev/sdb       xfs       2.2T   33M  2.2T   1% /alidata2
/dev/sdl       xfs       2.2T   33M  2.2T   1% /alidata3
/dev/sdd       xfs       2.2T   33M  2.2T   1% /alidata4
/dev/sde       xfs       2.2T   33M  2.2T   1% /alidata5
/dev/sdf       xfs       2.2T   33M  2.2T   1% /alidata6
/dev/sdk       xfs       2.2T   33M  2.2T   1% /alidata7
/dev/sdg       xfs       2.2T   33M  2.2T   1% /alidata8
/dev/sdm       xfs       2.2T   33M  2.2T   1% /alidata9
/dev/sdo       xfs       2.2T   33M  2.2T   1% /alidata10
/dev/sdc       xfs       2.2T   33M  2.2T   1% /alidata11
/dev/sdn       xfs       2.2T   33M  2.2T   1% /alidata12
/dev/sdp       xfs       2.2T   33M  2.2T   1% /alidata13
/dev/sdi       xfs       2.2T   33M  2.2T   1% /alidata14
/dev/sdh       xfs       2.2T   33M  2.2T   1% /alidata15
/dev/sdq4      xfs       164G   33M  164G   1% /alidata-ssd
tmpfs          tmpfs      45G     0   45G   0% /run/user/1001

但是鉴于ext4文件系统出现过两起故障,所以xfs文件系统可以保留在线上,作为稳定性对比,如果后续稳定性表现好于ext4,那么我们可以逐步替换为xfs.

因为上层有vfs,应用层并不会感知到底层文件系统的差异.

2.文件系统参数优化:

  1. 根据大数据文件大小和分布情况,后续离线和实时分离后,大文件偏多。

调大元数据inode 的size,避免容量不够,写入block,损失性能。

调大block size,因为大数据集群整体的文件大小是32M,64M,128M的文件居多,默认的4k的block size 要做大量的分组写入,效率低。

  1. 大数据集群单机的文件数量是230万~580万,频繁的变更,这个对于文件系统的超级块技术计数器带来很大压力。

优化超级块技术方法。

使用更多的CPU,提升每秒处理的数据量。

  1. 大数据离线计算集群,hdfs,hbase会有经常的数据跨服务器拷贝,做数据均衡,以及下半年的小文件治理,会有大批量的小文件删除。

优化拷贝文件的速度和删除文件的速度。

# 安装xfs管理工具
yum install xfsprogs -y
 
xfs_info -V
xfs_info version 4.5.0
 
# 创建xfs格式分区
fdisk /dev/vdc
# 格式化为xfs分区mkfs.xfs -f -i size=2048  -b size=32k  -l size=128m,lazy-count=1   /dev/xxx
 
#示例
mkfs.xfs -f -i size=2048 -b size=32k -l size=128m,lazy-count=1 /dev/vdc1
meta-data=/dev/vdc1              isize=2048   agcount=4, agsize=819192 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=32768  blocks=3276768, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=32768  ascii-ci=0 ftype=1
log      =internal log           bsize=32768  blocks=4096, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=32768  blocks=0, rtextents=0
 
对比测试两块磁盘顺序读写的IOPS和吞吐量性能:
 
/dev/sdj        2.2T   33M  2.2T   1% /alidata1  #优化后
/dev/sdb        2.2T   33M  2.2T   1% /alidata2  #优化前
  
# /alidata2 没有做优化的格式化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache
# /alidata1 做了优化的格式化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache

通过这个测试来看提升很小,在这数据量的情况下,看不出来差距,后续在线上环境,可以看看数据差距。

文件系统使用方式优化:

  1. 元数据

xfs文件系统会把inode存储在磁盘最开始的这1T空间里,如果这部分空间被完全填满了,那么就会出现磁盘空间不足的错误提示了。解决办法就是在挂载时,指定 inode64 选项

  1. 日志记录
    增大logbsize,更多的使用内存作为日志缓存,提升性能。

  2. 使用模式

因为大数据集群是3副本,同时服务器都是双电源接入,在T3+标准的机房中,所以意外掉电的概率极低,就是6条电源线都要断电才会导致数据丢失问题。

去掉数据写入前的栅栏步骤。

# 说明
mount -o inode64 -o logbsize=256k -o noatime -o nobarrier /dev/xxx /xxx
 
# 示例:
mount -t xfs -o inode64 -o logbsize=256k -o noatime -o nobarrier /dev/vdc1 /xfs
 
# 开机挂载/etc/fstab:
/dev/vdc1                                 /xfs                    xfs     defaults,inode64,noatime,nobarrier,logbsize=256k 0 0
 
# 手动挂载测试:
mount -a
 
# 查看挂载结果
df -Th | grep xfs
/dev/vdc1      xfs       100G   33M  100G   1% /xfs
 
# 重启测试,能否实现开机自动挂载
init 6

# /alidata2 没有做挂载优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-no-mount-optimization
# /alidata1 做了挂载优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-mount-optimization

可以看到做了挂载的优化后,还是有IOPS和吞吐量的性能提升,IOPS提升了69,吞吐量提升4M。

Linux系统内核与文件系统相关参数优化:

  1. 增大内存作为磁盘写入文件缓存
  2. 增加脏数据写入磁盘频率,避免过长的iowait 使用整体的io更平稳。

这里的优化思路是,那多的空闲内存做缓存,提升速度。

# 查看当前的默认值
 sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 30
vm.dirty_writeback_centisecs = 500
  
# 调大脏页占比,用内存作为写入缓存
echo "vm.dirty_ratio = 50" >> /etc/sysctl.conf
echo "vm.dirty_background_ratio = 20" >> /etc/sysctl.conf
echo "vm.dirty_expire_centisecs = 1500" >> /etc/sysctl.conf
echo "vm.dirty_writeback_centisecs = 200" >> /etc/sysctl.conf
echo "vm.vfs_cache_pressure = 50" >> /etc/sysctl.conf
  
# 让配置永久生效
sysctl -p
 
# 检查是否生效
sysctl -a | egrep "dirty|vfs"
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 20
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 1500
vm.dirty_ratio = 50
vm.dirty_writeback_centisecs = 200
vm.vfs_cache_pressure = 50
 
# 回滚配置:
sed -i 's#vm.dirty_ratio = 50#vm.dirty_ratio = 30#g' /etc/sysctl.conf
sed -i 's#vm.dirty_background_ratio = 20#vm.dirty_background_ratio = 10#g' /etc/sysctl.conf
sed -i 's#vm.dirty_expire_centisecs = 1500#vm.dirty_expire_centisecs = 3000#g' /etc/sysctl.conf
sed -i 's#vm.dirty_writeback_centisecs = 200#vm.dirty_writeback_centisecs = 500#g' /etc/sysctl.conf
sed -i 's#vm.vfs_cache_pressure = 50#vm.vfs_cache_pressure = 100#g' /etc/sysctl.conf
 
# 让配置永久生效
sysctl -p
# 优化前后的压测对比
 
# /alidata1 做了挂载优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-mount-optimization
 
# /alidata1 做了挂载和系统内核优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-mount-optimization

注意:做了这些系统的内核参数优化后,性能并没有提升,反而有变差的情况,在上线的时候,需要逐个参数调整上线逐步观察

磁盘优化:

  1. 为读写分别创建不同的I/O队列,提高读写性能。
  2. 增大磁盘队列长度,接受更多的读写请求数,提升硬盘的吞吐量。
  3. 顺序读的大数据场景中,增大磁盘的预读数据,提升读的性能。
  4. 针对应用程序的磁盘隔离,数据盘和日志盘分离。

调整 /dev/sdb 的预读大小。

调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为 KB。使用 blockdev 工具设置,比如 blockdev --setra 8192 /dev/sdb,注意这里的单位是 512B(0.5KB),所以它的数值总是 read_ahead_kb 的两倍。

可以优化内核块设备 I/O 的选项。

可以调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)。

以/alidata1对应的磁盘为例子,测试:

# 查看当前的调度算法
cat /sys/block/sdj/queue/scheduler
noop [deadline] cfq
 
# 已经是deadline 不用修改
 
# 调整磁盘队列长度
cat /sys/block/sdj/queue/nr_requests
128
 
# nr_requests的大小设置至少是/sys/block//device/queue_depth的两倍,所以,修改nr_requtests的时候要注意。
cat /sys/block/sdj/device/queue_depth
254
 
echo "512" > /sys/block/sdj/queue/nr_requests
 
# 查看默认大小
cat /sys/block/sdj/queue/read_ahead_kb
128
 
# 改为8192
echo "8192" > /sys/block/sdj/queue/read_ahead_kb
cat /sys/block/sdj/queue/read_ahead_kb
8192
 
# SSD的系统盘修改IO调度算法
cat /sys/block/sdq/queue/scheduler
noop [deadline] cfq
 
 
echo "noop" > /sys/block/sdq/queue/scheduler
cat /sys/block/sdq/queue/scheduler
[noop] deadline cfq
 
# 要加入到开机启动项中
echo 'echo "noop" > /sys/block/sdq/queue/scheduler' >> /etc/rc.local

注意:

可以看到IOPS稍微有所提升,但是吞吐量变差。在上到线上环境的时候,逐项修改和观察。

如果不使用xfs文件系统,针对ext4文件系统可以进行的优化:

因为优化是一个持续的过程,我们在初步阶段是同时对xfs和ext4两种不同的文件系统的服务器进行优化。

在不更改ext4文件系统为xfs文件系统的情况下,也可以做一些优化。

ext4服务器:

prod-hadoop-slave016
prod-hadoop-slave017

文件系统使用参数优化

# 查看现在的默认挂载
cat /proc/mounts
 
# 改进挂载选项
/dev/sdj     /alidata1 ext4 defaults,nobarrier 0 0
# /alidata2 没有做挂载优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-no-mount-optimization
# /alidata1 做了挂载优化
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-nodiskcache-mount-optimization

结论:

可以看到IOPS提升了77,吞吐量提升了5M/s.

这个优化是有必要做的。

系统内核参数优化:
这里的优化思路是,那多的空闲内存做缓存,提升速度。

# 查看当前的默认值
 sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
   
# 调大脏页占比,用内存作为写入缓存
echo "vm.dirty_ratio = 50" >> /etc/sysctl.conf
echo "vm.dirty_background_ratio = 20" >> /etc/sysctl.conf
echo "vm.dirty_expire_centisecs = 1500" >> /etc/sysctl.conf
echo "vm.dirty_writeback_centisecs = 200" >> /etc/sysctl.conf
echo "vm.vfs_cache_pressure = 50" >> /etc/sysctl.conf
   
# 让配置永久生效
sysctl -p
  
# 检查是否生效
sysctl -a | egrep "dirty|vfs"
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 20
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 1500
vm.dirty_ratio = 50
vm.dirty_writeback_centisecs = 200
vm.vfs_cache_pressure = 50
  
# 回滚配置:
sed -i 's#vm.dirty_ratio = 50#vm.dirty_ratio = 20#g' /etc/sysctl.conf
sed -i 's#vm.dirty_background_ratio = 20#vm.dirty_background_ratio = 10#g' /etc/sysctl.conf
sed -i 's#vm.dirty_expire_centisecs = 1500#vm.dirty_expire_centisecs = 3000#g' /etc/sysctl.conf
sed -i 's#vm.dirty_writeback_centisecs = 200#vm.dirty_writeback_centisecs = 500#g' /etc/sysctl.conf
sed -i 's#vm.vfs_cache_pressure = 50#vm.vfs_cache_pressure = 100#g' /etc/sysctl.conf
  
# 让配置永久生效
sysctl -p
# 测试,没有设置dirty_ratio
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-no-dirty-ratio
 
# 设置了dirty_ratio
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-dirty-ratio
结论:

这些内核参数在fio的压测下没有太大的改善,根源是我们服务器内存448,这里压测1G的数据量,根本到不了服务器的内存缓存的阈值,所以这里的压测是没有意义的,只能是业务系统上线后,逐个更改,再观察结果。

磁盘优化:

以/alidata1对应的磁盘为例子,测试:

# 查看当前的调度算法
cat /sys/block/sdj/queue/scheduler
noop [deadline] cfq
 
# 已经是deadline 不用修改
 
# 调整磁盘队列长度
cat /sys/block/sdj/queue/nr_requests
128
 
# nr_requests的大小设置至少是/sys/block//device/queue_depth的两倍,所以,修改nr_requtests的时候要注意。
cat /sys/block/sdj/device/queue_depth
254
 
echo "512" > /sys/block/sdo/queue/nr_requests
 
# 查看默认大小
cat /sys/block/sdj/queue/read_ahead_kb
128
 
# 改为8192
echo "8192" > /sys/block/sdo/queue/read_ahead_kb
cat /sys/block/sdo/queue/read_ahead_kb
8192
 
# SSD的系统盘修改IO调度算法
cat /sys/block/sdq/queue/scheduler
noop [deadline] cfq
 
 
echo "noop" > /sys/block/sdq/queue/scheduler
cat /sys/block/sdq/queue/scheduler
[noop] deadline cfq
 
# 要加入到开机启动项中
echo 'echo "noop" > /sys/block/sdq/queue/scheduler' >> /etc/rc.local
# 没有调整nr_request内核参数
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-no-kernel-nr-requests
 
# 调整nr_request内核参数后
echo "512" > /sys/block/sdo/queue/nr_requests
 
# 没有调整read_ahead_kb内核参数
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-no-kernel-read-ahead-kb
 
# 调整了read_ahead_kb内核参数
fio -name=rw -direct=1 -iodepth=64 -rw=rw  -bs=64k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=rw-iotest-kernel-read-ahead-kb

vim disk-optimization.sh

#!/bin/bash
# This script is used to set ssd disk use noop.
# Author: zhishen
# History: 20210914 second release.
 
# set disk use no nobarrier option
set_use_nobarrier_mount_option(){
    nobarrier_status=`grep "nobarrier" /etc/fstab | wc -l`
    if [ $nobarrier_status -eq 0 ]
    then
        sed -i 's#defaults#defaults,nobarrier#g' /etc/fstab
    fi
}
 
# set ssd disk use noop disk scheduler
set_ssd_use_noop(){
    str=`df | grep ssd | awk '{ print $1 }' | awk -F'/' '{ print $NF }'`
    ssd_drive_letter=${str:0:3}
    echo "noop" > /sys/block/$ssd_drive_letter/queue/scheduler
}
 
# set disk queue length
set_disk_queue_length(){
   disk_letter=`ls /sys/block/ | grep -v "sr0"`
   for letter in $disk_letter;
   do
      echo 512 > /sys/block/$letter/queue/nr_requests;
   done
}
 
 
 
# main function
main(){
    set_use_nobarrier_mount_option
    set_ssd_use_noop
    set_disk_queue_length
}
 
main

配置开机自启动:

# 设置开机自启脚本权限
cd /alidata/bin/
chmod +x disk-optimization.sh
 
# 设置开机自启动
cp disk-optimization.service /usr/lib/systemd/system
cd /usr/lib/systemd/system
chmod 754 disk-optimization.service
systemctl enable disk-optimization.service
 
# 查看开机自启
systemctl list-unit-files

设置开机启动文件disk-optimization.service:

[Unit]
Description=Set SSD use noop
After=network.target remote-fs.target nss-lookup.target
 
[Service]
Type=forking
ExecStart=/alidata/bin/disk-optimization.sh
 
[Install]
WantedBy=multi-user.target

系统内核部分的参数优化暂时还没有上,因为模拟压测的效果不理想,需要在线上的服务器逐步测试,最后形成终版。

结果

因为模拟出来一套完整的数据集群,成本太高,通过上面的自行压测看不出来性能提升,那么真实的把优化后的服务器加入到大数据集群中,性能表现是怎么样的呢?

1.xfs文件系统的IO情况:

峰值期间的IOPS:

可以看到凌晨的1点~2点之间,IOPS可以达到600.已经大幅超过了以前的200.

2.新上架的另外两台ext4文件系统服务器的IOPS情况:


可以看到IOPS只能到250左右。


可以看到IOPS只能到200左右。

疑问是不是slave015这台服务器的数据块更少,导致它的IOPS可以跑到更高呢?

第一阶段结论:

可以看到XFS文件系统,在大数据应用的场景下,IOPS可以达到600而ext4文件系统只能到250,提升的效果还是很明显。

关于xfs的文件系统的优化,暂时先告一段落,已经超出预期。

但是IOPS跑到598,其实磁盘使用率已经过载,会导致磁盘的读写延时变大,业务方的hdfs的IO看起来并没有提升。

要解决这个问题,我们还可以考虑通过服务器拆分,独立分出来local dir和日志磁盘,和把local dir替换为ssd磁盘来提升整体的速度。

这里的疑问:

使用xfs文件系统的服务器,IOPS和读写延迟都要小于ext4文件系统的服务器,但是map io却并没有提升,暂时还不清楚其中的原因。

但是从运维层面能够提升IOPS的目的达到了。

新方案一:
可以看到上面我们单台xfs的服务器,虽然磁盘的IOPS测试下来有明显提升,但是map io并没有提升,能够提升map io吗?

使用以下方案试试。

1.方案测试:

把服务器的16块磁盘,拿3块磁盘那出来做中间状态盘,剩下的13块盘作为数据盘,日志写入到系统盘中。

对比没有做拆分的IOPS情况以及mapping IO的情况。

注意:日志写入到系统盘,无法实现,因为系统盘的磁盘容量不够

因为初步测试下来看,无法继续下去,原因是,如果拿出来三块磁盘作为中间状态盘,会导致单机的存储空间不够,因为只有13块磁盘来做数据存储,会触发hdfs的存储红线。

所以这个思路无法继续测试下去。

因为我们IDC离线大数据集群要迁移到阿里云的大数据集群,所以磁盘I/O优化没有继续下去,所以对于map io提升的测试和上线没有继续推进。

新方案二:
可以看到,hadoop 2.0版本底层的hdfs对于磁盘I/O使用并不是很优,16块磁盘热点磁盘只会有1到2块(具体是哪块磁盘默认是随机),剩余的磁盘IO却很闲。

如果能够把IO平均分派到这16块磁盘,那么IO将会有大幅的提升

但是升级hadoop 从2.0到3.0也是一个大工程,大数据组可能并没有时间来做。

在这个前提下,我们还能继续优化map io吗?

我们可以考虑通过虚拟化的技术把大服务器拆为小服务器,然后通过磁盘直通挂载到虚拟机的模式实现,这样不损失磁盘IO,同时能够把hdfs的热点盘提升一倍

16块磁盘的热点盘从1到2块,可以提升到2到4块,这样热点磁盘数量提升了一倍,那么是否IO也会有接近一倍的提升呢?因为没有环境,我们无法测试验证,但是可以保留想法。

思路如下:

看到阿里云和腾讯云专门为大数据配置的服务器中,默认也是配置的8块磁盘,初步来看这个思路可行。

引用内容

https://time.geekbang.org/column/article/76876

https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux/6/html/performance_tuning_guide/main-fs#idm140329763851008

https://support.huaweicloud.com/tuningtip-kunpenggrf/kunpengtuning_12_0041.html

https://www.bookstack.cn/read/transoflptg/25.md

https://blog.csdn.net/gochenguowei/article/details/79510786

https://zh.wikipedia.org/wiki/B%2B%E6%A0%91