illx10000

1. 背景与目的

在工作中,很多时候会遇到一些性能相关的问题。例如

因此熟知

是必不可缺的。

通常我们评估机器的性能,会分为如下几个方面:

尤其是对于我们业务(支付金融类)来说,了解存储(因为需要落地,需要事务等等)性能至关重要,包括Mysql,KeyValue存储等

2. 了解存储性能

我们从存储使用的各个层级了解其中的原理,包括其局限,应对方法等。

主要从下面几个层面来看(自底向上):

2.1 硬盘

硬盘最主要的功能就是数据存储,也就是将电信号持久化到某些物理介质。

目前主流的硬盘分为两种架构,一种是基于电磁转换原理(磁盘),一种是基于电子存储介质进行数据存储和读取的磁盘(SSD磁盘)

2.1.1 磁盘

以下内容主要参考:

几句话概括:

2.1.2 SSD盘

以下内容主要参考:

固态盘中,在存储单元晶体管的栅(Gate)中,注入不同数量的电子,通过改变栅的导电性能,改变晶体管的导通效果,实现对不同状态的记录和识别。

同时SSD磁盘基于mapping table,维护逻辑地址到物理地址的映射。每次读写时,可以通过逻辑地址直接查表计算出物理地址,与传统的机械磁盘相比,省去了寻道时间和旋转时间。

需要注意的地方是:

2.2 操作系统(Linux)的IO操作

以下内容主要参考:

Linux的整体IO分为7层 业务流程

  1. VFS层:虚拟文件系统层。由于内核要跟多种文件系统打交道,而每一种文件系统所实现的数据结构和相关方法都可能不尽相同,所以,内核抽象了这一层,专门用来适配各种文件系统,并对外提供统一操作接口。
  2. 文件系统层:不同的文件系统实现自己的操作过程,提供自己特有的特征
  3. 页缓存层:负责针对page的缓存。
  4. 通用块层:由于绝大多数情况的io操作是跟块设备打交道,所以Linux在此提供了一个类似vfs层的块设备操作抽象层。下层对接各种不同属性的块设备,对上提供统一的Block IO请求标准。
  5. IO调度层:因为绝大多数的块设备都是类似磁盘这样的设备,所以有必要根据这类设备的特点以及应用的不同特点来设置一些不同的调度算法和队列。以便在不同的应用环境下有针对性的提高磁盘的读写效率,这里就是大名鼎鼎的Linux电梯所起作用的地方。针对机械硬盘的各种调度方法就是在这实现的。
  6. 块设备驱动层:驱动层对外提供相对比较高级的设备操作接口,往往是C语言的,而下层对接设备本身的操作方法和规范。
  7. 块设备层:这层就是具体的物理设备了,定义了各种真对设备操作方法和规范。

其中,块设备和块设备驱动层接触的比较少,与硬件设备关系比较紧密,暂时不写。 其中,通用块层和VFS层都是一层封装,为了对上游提供统一的接口,降低复杂度,只选取其中VFS层讲述一下。 因此针对Linux操作系统着重介绍下面的几个部分内容


2.2.1 IO调度层(IO Scheduler)

以下内容主要参考:

IO调度层的目的是为了提升机械硬盘的读写性能。具体的实现方式是:

每个块设备或者块设备的分区,都对应有自身的请求队列(request_queue),而每个请求队列都可以选择一个I/O调度器来协调所递交的request。I/O调度器的基本目的是将请求按照它们对应在块设备上的扇区号进行排列,以减少磁头的移动,提高效率。每个设备的请求队列里的请求将按顺序被响应。实际上,除了这个队列,每个调度器自身都维护有不同数量的队列,用来对递交上来的request进行处理,而排在队列最前面的request将适时被移动到请求队列中等待响应。

内核中实现的调度器有下面几种:

2.2.2 页面缓存(Page Cache)

2.2.2.1 预读和回写

以下内容主要参考:

PageCache有两个非常重要的思想需要学习

预读其实就是利用了局部性原理,具体过程是:对于每个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面(通常是三个页面),这时的预读称为同步预读。对于第二次读请求,如果所读页面不在Cache中,即不在前次预读的页中,则表明文件访问不是顺序访问,系统继续采用同步预读;如果所读页面在Cache中,则表明前次预读命中,操作系统把预读页的大小扩大一倍,此时预读过程是异步的,应用程序可以不等预读完成即可返回,只要后台慢慢读页面即可,这时的预读称为异步预读。任何接下来的读请求都会处于两种情况之一:第一种情况是所请求的页面处于预读的页面中,这时继续进行异步预读;第二种情况是所请求的页面处于预读页面之外,这时系统就要进行同步预读。

回写是通过暂时将数据存在Cache里,然后统一异步写到磁盘中。通过这种异步的数据I/O模式解决了程序中的计算速度和数据存储速度不匹配的鸿沟,减少了访问底层存储介质的次数,使存储系统的性能大大提高。Linux 2.6.32内核之前,采用pdflush机制来将脏页真正写到磁盘中,什么时候开始回写呢?下面两种情况下,脏页会被写回到磁盘:

回写开始后,pdflush会持续写数据,直到满足以下两个条件:

Linux 2.6.32内核之后,放弃了原有的pdflush机制,改成了bdi_writeback机制。bdi_writeback机制主要解决了原有fdflush机制存在的一个问题:在多磁盘的系统中,pdflush管理了所有磁盘的Cache,从而导致一定程度的I/O瓶颈。bdi_writeback机制为每个磁盘都创建了一个线程,专门负责这个磁盘的Page Cache的刷新工作,从而实现了每个磁盘的数据刷新在线程级的分离,提高了I/O性能。

回写机制存在的问题是回写不及时引发数据丢失(可由sync fsync解决),回写期间读I/O性能很差。

2.2.2.2 PageCache的产生

PageCache可以从下面几个方式产生PageCache

2.2.1 虚拟文件系统(VFS)

VFS(Virtual File System)虚拟文件系统是一种软件机制,更确切的说扮演着文件系统管理者的角色,与它相关的数据结构只存在于物理内存当中。它的作用是:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。正是因为有了这个层次,Linux中允许众多不同的文件系统共存并且对文件的操作可以跨文件系统而执行。 –磁盘I/O那些事

问:为什么要加一层抽象层,而不是直接使用原生文件系统? 答:为了让上游使用方更容易,业务开发方不需要适配各种文件系统接口就能读写对应文件系统上的文件。 这也体现了软件设计的重要方法之一:基于接口而非实现编程

VFS中包含着向物理文件系统转换的一系列数据结构,如VFS超级块、VFS的Inode、各种操作函数的转换入口等。Linux中VFS依靠四个主要的数据结构来描述其结构信息,分别为超级块、索引结点、目录项和文件对象。

VFS更多的是抽象,对于性能优化的不是特别多,值得学习其中抽象的思路。

2.2.2 Ext文件系统

以下主要参考下面的文章:

上文中提了几个概念:

后续所说的块都是指操作系统的块,称之为block

所以最终的组织结构如下:https://images2018.cnblogs.com/blog/733013/201807/733013-20180727160411876-443793371.png

2.3 应用软件层设计方法(举例)

上面可以看到由于磁盘的物理结构,导致磁盘 ** 随机读写慢,顺序读写快 **,操作系统使用了一些优化的技术手段 Cache/IO调度(重排)提升磁盘性能,但终归无法从本质上提升。一些优秀的开源软件在设计时考虑到磁盘的特性,也有一些特别的设计技巧值得学习,个人简单列举了一些自己了解的软件设计机制。(其实每一个设计技巧都值得一篇单独的文章来介绍和了解,本文只是简单的介绍)

2.3.1 MySQL(B+树索引)

以下内容主要参考:

MySQL(InnoDB引擎),通常使用B+树结构做为索引(索引分为主键索引和非主键索引,差别在于主键索引的叶子节点是真实的数据,非主键索引的叶子节点是指向主键索引位置的值,每一张表都只有一个主键索引)

B+树有下面几个特点:

2.3.2 LevelDB(LSM-Tree追加写)

以下内容主要参考:

LSM-Tree 核心的思路是通过预写日志(WAL),将所有的写入请求(写入,更新,删除)以Append的模式追加写实现写入高性能。再通过一些手段优化读性能来保证可用,例如 内存cache,合并数据,布隆过滤器等

2.3.3 Kakfa(追加写和sendfile)

以下内容主要参考:

使用Page Cache:

以上是我个人了解到的关于磁盘IO的一些流程和方法。

后面会补充如下内容: