性能之巅:洞悉系统、企业与云计算——文件系统

  • 术语
  • 模型
    • 文件系统接口
    • 文件系统缓存
    • 二级缓存
  • 概念
    • 文件系统延时
    • 缓存
    • 随机与顺序 I/O
    • 预取
    • 预读
    • 写回缓存
    • 同步写
      • 单次同步写
      • 同步提交已写内容
    • 裸 I/O 和直接 I/O
    • 非阻塞 I/O
    • 内存映射文件
    • 元数据
      • 逻辑元数据
      • 物理元数据
    • 逻辑 I/O VS 物理 I/O
      • 无关
      • 间接
      • 缩小
      • 放大
    • 操作并非不平等
    • 特殊文件系统
    • 访问时间戳
    • 容量
  • 架构
    • 文件系统 I/O 栈
    • VFS
    • 文件系统缓存
      • 缓冲区高速缓存
      • 页缓存
      • 目录项缓存
      • inode 缓存
    • 文件系统特性
      • 块和区段
      • 日志
      • 写时复制
      • 檫洗
    • 文件系统种类
      • FFS
      • UFS
      • ext3
      • ext4
      • ZFS
      • btrfs
    • 卷和池
  • 方法
    • 磁盘分析
    • 延时分析
      • 事务分析
    • 负载特征归纳
      • 高级负载特征归纳/检查清单
      • 性能特征归纳
    • 性能监控
    • 事件跟踪
    • 静态性能调优
    • 缓存调优
    • 负载分离
    • 内存文件系统
      • /tmp
    • 微型基准测试

研究应用程序 I/O 性能时会发现,文件系统性能能比磁盘性能更为重要。文件系统通过缓存、缓冲以及异步 I/O 等手段来缓和磁盘(或者远程系统)的延时对应用程序的影响。尽管如此,性能分析和可用工具集一直集中在磁盘性能方向。

术语

  • 文件系统:一种把数据组织成文件和目录的存储方式,提供了基于文件的存取接口,并通过文件权限控制访问。另外,还包括一些表示设备、套接字和管道的特殊文件类型,以及包含文件访问时间戳的元数据。
  • 文件系统缓存:主存(通常是DRAM)的一块区域,用来缓存文件系统的内存,可能包含各种数据和元数据。
  • 操作:文件系统的操作是对文件系统的请求,包括 read()、write()、open()、close()、stat()、mkdir() 以及其他操作。
  • I/O:输入/输出。文件系统I/O 有好几种定义,这里仅仅指直接读写(执行I/O)的操作,包括read()、write()、stat()和mkdir()。I/O 不包括open() 和close()。
  • 逻辑I/O:由应用程序发给文件系统的I/O。
  • 物理I/O:有文件系统直接发给磁盘的I/O(或者通过裸I/O)。
  • 吞吐量:当前应用程序和文件系统之间的数据传输率,单位是B/s。
  • inode:一个索引节点(inode)是一种含有文件系统对象元数据的数据结构,其中有访问权限、时间戳以及数据指针。
  • VFS:虚拟文件系统,一个为了抽象与支持不同文件系统类型的内核接口。
  • 卷管理器:灵活管理物理存储设备的软件,在设备上创建虚拟卷提供操作系统使用。

模型

文件系统接口

下图从接口的角度展示了文件系统的基本模型:

研究文件系统性能的方法里,有一种方法是把它当成一个黑盒子,只关注对象操作的时间延时。

文件系统缓存

下图描绘了在响应读操作的时候,存储在主存里的普通文件系统缓存情况。

读操作从缓存返回(缓存命中)或者从磁盘返回(缓存未命中)。未命中的操作被存储在缓存中,并填充缓存(热身)。

文件系统缓存可能也用来缓冲写操作,使之延时写入(刷新)。不同文件系统有不同的实现这种机制的手段。

二级缓存

二级缓存可能是各种存储介质,下图的例子是闪存。这种类型的缓存首见与ZFS。

概念

文件系统延时

文件系统延时是文件系统性能一项主要的指标,指的是一个文件系统逻辑请求开始到结束的时间。它包括了消耗在文件系统、内核磁盘 I/O 子系统以及等待磁盘设备——物理 I/O 的时间。应用程序的线程通常在请求时阻塞,等待文件系统请求的结束。这种情况下,文件系统的延时与应用程序的性能有着直接和成正比的关系。

有些条件下应用并不受文件系统的直接影响,例如非阻塞 I/O,或者I/O 由一个异步线程发起(例如一个后台刷新线程)。如果应用程序提供了足够详细的文件系统使用指标,有可能失败出这种情况。否则,一般的做法是使用内核跟踪工具打印出用户层发起文件系统逻辑 I/O 的调用栈,通过研究调用栈可以看出,应用程序哪个函数产生了 I/O。

文件系统一直以来未开放查看文件系统延时的接口,相反提供了磁盘设备级别的指标信息。但是很多情况这些指标和应用程序并无直接的关系,即便并非豪无关系,也是难以理解的。例如,文件系统会在后台刷新一些要写入的数据到磁盘上,看上去可能像突然爆发的高延时磁盘 I/O。从磁盘设备的指标来看,这值得引起警惕,然而,没有任何一个应用程序在等待这些操作完成。

缓存

文件系统启动之后会使用主存(RAM)当缓存以提高性能。对于应用程序这是透明的:他们的逻辑 I/O 延时小了很多,因为可以直接从主存返回而不是从慢得多磁盘设备返回。

随着时间的流逝,缓存大小不断增长而操作系统的空余内存不断减小,这会影响新用户,不过这再正常不过了。原则如下:如果还有空闲内存,就用来存放有用的内容。当应用程序需要更多的内存时,内核应该迅速从文件系统缓存中释放一些以备使用。

文件系统用缓存(caching)提高读性能,而用缓存(buffering)(在缓存中)提高写性能。文件系统和块设备子系统一般使用多种类型的缓存。下表中有一些具体的例子。

缓存 示列
页缓存 操作系统页缓存
文件系统主存 ZFS ARC
文件系统二级缓存 ZFS L2ARC
目录缓存 目录缓存,DNLC
inode 缓存 inode 缓存
设备缓存 ZFS vdev
块设备缓存 块缓存(buffer cacghe)

随机与顺序 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,这种情况被称为碎片化。

文件系统可以测量逻辑 I/O 的访问模式,从中识别出顺序 I/O,然后通过预取或者预读来提供性能。

预取

大量的文件顺序读 I/O,例如文件系统备份,是常见的文件系统负载。这种数据量可能太大了,放不进缓存,或者只读一次而在缓存里留不住(取决于缓存的回收策略)。这样的负载下,由于缓存命中率偏低,系统性能较差。

预取是文件系统解决这个问题的通常做法。通过检查当前和上一个 I/O 的文件偏移量,可以检查出当前是否是顺序读负载,并且做出预测,在应用程序请求前向磁盘发出读命令,以填充文件系统缓存。这样如果应用程序真的发出了读请求,就会命中缓存(需要的数据已经在缓存里)。下面例子中,缓存一开始是空的:

  1. 一个应用程序对某个文件调用read(),把控制权交给内核
  2. 文件系统发起磁盘读操作
  3. 将上一次文件偏移量指针和当前地址进行比对,如果发现是顺序的,文件系统就会发起额外的读请求
  4. 第一次的读取结束返回,内核把数据和控制权交还给应用程序
  5. 额外的读请求也结束返回,并填进缓存,以备将来应用程序的读取

同意的场景在下图中也有描绘。图中应用程序读了1号地址,接着对2号地址的读取触发了接下来的三个地址的预取。

预取的预测一旦准确,应用程序的读性能将会有显著提示,磁盘在应用程序请求前就把数据读出来的。而一旦预测不准,文件系统会发起应用程序不需要的I/O,不仅污染了缓存,也消耗了磁盘和I/O 的传输资源。文件系统一般允许对预取参数进行调优。

预读

预取一直被认为是预读。Linux采用了”预读“ 这个词作为一个系统调用,readahead(2),允许应用程序显式地预热文件系统缓存。

写回缓存

写回缓存广泛地应用于文件系统,用来提高写性能。它的原理是,当数据写入主存后,就认为写入已经结束并返回,之后再异步地把数据刷入磁盘。文件系统写入”脏“数据的过程称为刷新(flushing)。例如:

  1. 当应用程序发起一个文件的write() 请求,把控制权交给内核
  2. 数据从应用程序地址空间复制到内核空间
  3. write() 系统调用被内核视为已经结束,并把控制权还给应用程序
  4. 一段时间后,一个异步的内核任务定位到要写入的数据,并发起磁盘的写请求。

这期间牺牲了可靠性。基于DRAM的主存是不可靠的,”脏“数据会在断电的情况下丢失,而应用程序确认为写入已经完成。并且,数据可能被非完整写入,这样磁盘上的数据就是在一种破坏(corrupted)的状态。

如果文件系统的元数据遭到破坏,那可能无法加载。到了这一步,只能从系统备份中还原,造成长时间的宕机。更糟糕的是,如果损坏蔓延到了应用程序读写的文件内容,那业务就会受到严重冲击。

为了平衡系统对于速度和可靠性的需求,文件系统默认采用写回缓存策略,但同时也提供一个同步写的选项绕过这个机制,把数据直接写在磁盘上。

同步写

同步写完成的标志是,所有的数据以及必要的文件系统元数据被完整地写入到永久存储介质(如磁盘设备)中、由于包含了磁盘I/O 延时,所以肯定比异步协(写回缓存)慢得多。有些应用程序,例如数据库写日志,因完全不能承担异步写带来的数据损坏风险,而使用了同步写。

同步写有两种形式:单次 I/O 的同步写,和一组已写 I/O 的同步提交。

单次同步写

当使用O_SYNC标志,或者其他变体,如O_DSYNC和O_RSYNC 打开一个文件后,这个文件的写I/O 即为同步。一些文件系统接收加载选项,可以强制所有文件的所有写I/O 为同步。

同步提交已写内容

一个应用程序可能在检查点使用fsync() 系统调用,同步提交之前异步写入的数据,通过合并同步写提高性能。

还有其他情况会提交之前的写入,例如关闭文件句柄,或者一个文件里有过多未提交缓冲。前者在解开含有很多文件的打包档案时较为明显,特别是在NFS挂载的文件系统上。

裸 I/O 和直接 I/O

应用程序可能会用到的其他I/O 种类如下:

  • 裸 I/O:绕过了整个文件系统,直接发给磁盘地址。有些应用程序使用了裸 I/O(特别是数据库),因为他们能比文件系统更好地缓存自己的数据。其缺点在于难以管理,即不能使用常用文件系统工具执行备份/恢复和监控。
  • 直接 I/O:允许应用程序绕过缓存是用文件系统。这有点像同步写(但缺少O_SYNC选项提供的保证),而且在读取时也能用。它没有裸 I/O 那么直接,文件系统仍然会把文件地址映射到磁盘地址,I/O 可能会被文件系统重新调整大小以适应文件系统在磁盘上的块大小(记录尺寸)。不仅仅是读缓存和写缓冲,预取可能也因此失效,具体取决于文件系统的实现。

直接 I/O 可用于备份文件系统的应用程序,防止只读一次的数据污染文件系统缓存。裸 I/O 和直接 I/O还可以用于那些在进程堆里自建缓存的应用程序,避免了双重缓存的问题。

非阻塞 I/O

一般而言,文件系统 I/O 要么立刻结束(如从缓存返回),要么需要等待(比如等待磁盘设备 I/O)。如果需要等待,应用程序线程会被阻塞并让出CPU,在等待期间给其他线程执行的机会。虽然被阻塞的线程不能执行其他工作,但问题不大,多线程的应用程序会在有些线程被阻塞的情况下创建额外的线程来执行任务。

某些情况下,非阻塞 I/O 正合适,因此可以避免线程创建带来的额外性能和资源开销。在调用open() 系统调用时,可以传入O_NONBLOCK 或者O_NDELAY选项使用非阻塞I/O。这样读写时就会返回错误EAGAIN,让应用程序过一会再重试,而不是阻塞调用。

内存映射文件

对于某些应用程序和负载,可以通过把文件映射到进程地址空间,并直接存取内存地址的方法来提供文件系统 I/O 性能。这样可以避免调用read() 和write() 存取文件数据时产生的系统调用和上下文切换开销。如果内核支持直接复制文件数据缓冲到进程地址空间,那么还能防止复制数据两次。

元数据

如果说数据对应了文件和目录的内容,那元数据则对应了有关它们的信息。元数据可能是通过文件系统接口(POSIX)读出的信息,也可能是文件系统实现磁盘布局所需的信息。前者被称为逻辑元数据,后者被称为物理元数据。

逻辑元数据

逻辑元数据是应用程序读取或者写入的信息。

  • 显式:读取文件统计信息(stat()),创建和删除问题(creat()、unlink())及目录(mkdir()、rmdir())。
  • 隐式:文件系统存取时间戳的更新,目录修改时间戳的更新。
    ”元数据密集“的负载通常指那些频繁操作逻辑元数据的行为,甚至过了文件内容的读取。例如Web服务器用stat() 查看文件,确保文件在缓存后没有修改。

物理元数据

为了记录文件系统的所有信息,有一部分元数据与磁盘布局相关,这是物理元数据。物理元数据的类型依赖于文件系统类型,可能包括了超级块、inode、数据块指针(主数据、从数据,等等)以及空闲链表。

逻辑 I/O VS 物理 I/O

尽管看似违背常理,应用程序向文件系统发起的I/O(逻辑I/O)与磁盘I/O(物理 I/O)可能并不相称,原因很多。

文件系统的工作不仅仅是在永久存储介质(磁盘)上提供一个基于文件的接口那么简单。它们缓存读、缓冲写,发起额外的 I/O 维护磁盘上与物理布局相关的元数据,这些元数据记录数据存储的位置。这样的结果是,与应用程序I/O相比,磁盘 I/O 有时显得无关、间接、放大或者缩小。

无关

以下因素可能造成磁盘 I/O与应用程序无关:

  • 其他应用程序:磁盘 I/O 来源于其他用于程序
  • 其他租户:磁盘 I/O 来源于其他租户(可在虚拟化技术的帮助下通过系统工具查看)
  • 其他内核任务:例如内核在重建一个软RADI卷或者执行异步文件系统效验验证时

间接

以下因素可能造成应用程序 I/O 与磁盘之间没有直接对应关系:

  • 文件系统预取:增加额外的 I/O,这些 I/O应用程序可能用得到,也可能用不到
  • 文件系统缓冲:通过写回缓存技术推迟和归并写操作,之后再一并刷入磁盘。有些文件系统可能会缓冲数十秒后一起写入,造成偶尔的突发的大 I/O。

缩小

以下因素可能造成磁盘 I/O 小于应用程序 I/O,甚至完全消失:

  • 文件系统缓存:直接从主存返回,而非磁盘。
  • 文件系统写抵消:在一次性写回到磁盘之前,同一地址被修改了多长。
  • 压缩:减少了从逻辑 I/O 到物理 I/O 的数据量。
  • 归并:在向磁盘发 I/O 前合并连续 I/O。
  • 内存文件系统:也许永远不需要写入到磁盘的内容(如tmpfs)

放大

以下因素可能造成磁盘 I/O 大于应用程序 I/O。

  • 文件系统元数据:增加了额外的I/O。
  • 文件系统记录尺寸:向上对齐的I/O大小(增加了字节数),或者被打散的I/O(增加了I/O数量)。
  • 卷管理器奇偶校验:读—改—写的周期会增加额外的I/O。

操作并非不平等

有些操作可能从文件系统缓存中返回,直逼主存的速度;而其他可能从磁盘返回,慢上好几个数量级。其他关键的因素包括,操作是随机还是连续的、读取还是写入的、同步写还是异步写、I/O大小、是否包含其他操作类型,以及CPU执行消耗。

特殊文件系统

文件系统的目的通常是持久地存储数据,但有些特殊的文件系统也有着其他用途,比如临时文件(/tmp)、内核设备路径(/dev) 和系统统计信息(/proc)。

访问时间戳

许多文件系统支持访问时间戳,可以记录下每个文件和目录被访问(读取)的世界,这会造成读取文件时需要更新元数据,读取变成了消耗磁盘 I/O 资源的写负载。

有些文件系统对访问时间戳做了优化,合并及推迟这些写操作,以减少对有效负载的干扰。

容量

当文件系统装满时,性能会因为数个原因有所下降。当写入新数据时,需要花更多时间来寻找磁盘上的空闲块,而寻找过程本身也消耗计算和 I/O 资源。磁盘上的空闲空间变得更小更分散,而更小或随机的 I/O 则影响了文件系统的性能。

这些具体对文件系统的影响有多大,取决于文件系统的类型、磁盘上的数据布局和存储设备。

架构

文件系统 I/O 栈

下图刻画了文件系统 I/O 栈的一般模型。具体的模块和层次依赖于使用的操作系统类型、版本以及文件系统。

这张图展现了 I/O 穿过内核的路径。从系统调用直接调用磁盘设备子系统的是裸 I/O。穿过VFS 和文件系统 I/O,包括绕过了文件系统缓存的直接I/O。

VFS

VFS(虚拟文件系统接口,virtual file system inerface)给不同类型的文件系统提供了一个通用的接口。下图演示了它的位置。

文件系统缓存

下图展示了Linux文件系统缓存的概览,包括了其中标准文件系统之间通用的一些缓存。

缓冲区高速缓存

缓冲区高速缓存被存在了页缓存中(因此下图里对应边框是虚线)。这防止了双重缓存和同步的开销。缓冲区高速缓存的功能依然健在,提升了块设备 I/O 的性能。

缓冲区高速缓存的大小是动态的,可以从/proc里查看。

页缓存

页缓存缓存了虚拟内存的页面,包括文件系统的页面,提升了文件和目录的性能。页缓存大小是动态的,它会不断增长消耗可用的内存,并在应用程序需要的时候释放。

文件系统使用的内存脏页面由内核线程写回到磁盘上。如果系统的内存不足,另一个内核线程,页面换出后台程序(kswapd,又被称为页面扫描器),会定位并安排把脏页面写入到磁盘上,腾出可重用的内存页面。为了查看方便,kswapd和写回线程都可以通过操作系统性能工具,以内核任务的形式看到。

目录项缓存

目录项缓存(Dcache)记录了从目录项(struct dentry)到VFS inode的映射关系,和早期UNIX的DNILC很相似。它提高了路径名查找(例如通过open())的性能:当遍历一个路径名时,查找其中每一个名字都可以先检查Dcache,直接得到inode的映射,而不用到目录里一项项地翻查。Dcache中的缓存项存在一张哈希表里,以进行快速的、可扩展的查找(以父目录项加上目录项名作为键值)。

多年来目录项缓存性能得到很大的提示,包括了读—拷贝—更新变量(RCU遍历)算法。该算法可以遍历路径名,而不更新目录项的引用计数,否则在多CPU系统上由于频繁的缓存同步,会有扩展性上的问题。如果碰到目录项不在缓存里,RCU遍历会自动降为较慢的引用计数遍历法,因为在文件系统的查找和阻塞时,有必要采用引用计数。在忙负载下,目录项很有可能被缓存,RCU遍历也能派上用场。

目录项缓存也可反向缓存,记录缺失目录项的查找。反向缓存提高了失败查找的性能,这在库路径查找中经常发生。

目录项缓存动态增长,而当系统需要更多内存时,按照LRU原则缩小。它的大小可以通过/proc查看。

inode 缓存

这个缓存的对象 VFS inode(struct inode),每个都描述了文件系统一个对象的属性。这些属性很多可以通过 stat() 系统调用获的,并被操作系统操作频繁访问。

indoe缓存动态增长,保存了至少所有被目录项缓存映射的inode。当系统内存紧张时,inode缓存会释放未与目录项关联的inode以缩小内存占用。它的大小可以通过/proc查看。

文件系统特性

块和区段

基于块的文件系统把数据存储在固定大小的块里,被存储在元数据块里的指针所引用。对于大文件,这种方法需要大量的块指针和元数据块,而且数据块的摆放可能会变得零零碎碎,造成随机I/O。有些基于块的文件系统尝试通过把块连续摆放,来解决这个问题。另一个办法是使用变长的块大小,随着文件的增加采用更大的数据块,也能减小元数据的开销。

基于区段的文件系统预先给文件(区段)分配了连续的恐惧,并随需增长。虽然带来了额外的空间开销,但提高了连续数据流的性能,也由于更高的文件数据本地化效应,提高了随机I/O 的性能。

日志

文件系统日志(或者记录)记录了文件系统的更改,这样在系统宕机时,能原子地回放更改——要么完全成功,要么完全地失败。这让文件系统能够迅速恢复到一致的状态。如果与同一个更新相关的数据和元数据没有被完整地写入,没有日志保护的文件系统在系统宕机时可能会损坏。从宕机中恢复需要遍历文件系统所有的结构,大的文件系统可能需要数小时。

日志被同步地写入磁盘,有些文件系统还会把日志写到单独的设备上。部分文件系统日志记录了数据和元数据,这样所有的I/O都会写两次,带来额外的 I/O 资源开销。而其他文件系统日志里只有元数据,通过写时负载的技术来保护数据。

有一种文件系统全部由日志构成——日志结构文件系统。在这个系统里所有的数据和元数据更新被写到一个连续的循环日志当中。这非常有利于写操作,因为写总是连续的,还能被一起合并成大 I/O。

写时复制

写时复制的文件系统从不覆写当前使用中的块,而是按照以下步骤完成写入:

  1. 把数据写到一个新块(新的拷贝)
  2. 更新引用指向新块
  3. 把老块放到空闲链表中

在系统宕机时这能够有效地维护文件系统的完整性,并且通过随机写变成连续写,改善了写入性能。

檫洗

这项文件系统特性在后头读出文件系统里所有的数据块,验证效验和。赶在RAID还能恢复数据之前,在第一时间检测出坏盘。但是,檫洗操作的读 I/O 会严重影响性能,因此只能以低优先级发出。

文件系统种类

FFS

FFS的设计目的是解决最初UNIX文件系统的问题。许多文件系统正是基于FFS的。

最初的UNIX文件系统磁盘上数据结构由一张inode表、512B的存储块和一个存放了资源分配信息的超级块组成。inode表和存储块把磁盘分成两个部分,这样一起存取这两种数据时就会有性能问题。另一个问题是固定块的尺寸过小,仅512B。这不仅限制了吞吐量,而且在大文件的情况下还增加了元数据(指针)。

FF是通过把磁盘分区划分为多个柱面组以提高性能。文件inode和数据尽量放到一个柱面组里,减少磁盘寻道,参见下图。其他相关的数据也放到附近位置,包括目录的inode和目录项。inode 的设计则较为类似。

块大小增加到了最小4KB,提高了吞吐量。存储一个文件需要的数据块减少了,因此需要引用这些数据块的间距块也减少了。不仅如此,由于间接块体积增加,需要的数量进一步减少。为了提高小文件的存储效率,每个块可以划分成1KB大小的片段。

FFS的另一项性能特性是块交叉:连续地摆放磁盘上的块,但中间留一个或几块的间隔。这个几个块的间隔留给内核和处理器一些时间。以发起下一个连续的文件读请求。在此期间,磁盘由它们直接控制。如果没有交叉,下一块可能在发起连续请求时已经越过了磁头。等数据块转回来,已经过了将近一圈的时间,造成延时。

UFS

和FFS一样,也是诞生与1984年并引入SunOS。接下来的二十年内,各种特性被加进 SunOS:I/O 聚蔟、文件系统扩容、多TB容量支持、日志、直接I/O、快照、访问控制列表和扩展属性、Linux现在还支持读取UFS,不过不支持写入。但Llnux支持另一个类 UFS 文件系统(ext3)的读写。

关键的UES性能特性如下:

  • I/O 聚蔟:这项技术把磁盘上的数据库聚集起来,知道写入一个簇被填满了才写入。这样数据就会按顺序摆放。当检测到连续负载时,UFS会在读这些簇时执行预取(也叫预读)。
  • 日志:仅针对元数据、日志提高了系统宕机后的启动速度。因为回放日志避免了fsck(文件系统检查,file system check )。由于合并了一些元数据的写操作。它还可能提高某些负载的性能、
  • 直接 I/O:绕过了页缓存,避免某些应用会双重缓存,如数据库。

ext3

基于最初的UNIX文件系统的Linux扩展文件系统(extended file system, ext),作为Linux及其VFS的首个文件系统被开发出来。

关键性能特性如下:

  • 日志:一种顺序模式,仅针对元数据,另一种是日志模式,针对数据和元数据。日志提高了系统宕机后的启动速度。避免了fsck。由于合并了一些元数据的写操作,它有可能提高某些写负载的性能
  • 日志设备:允许使用外部使用日志设备,避免日志负载与读负载相互竞争
  • Orlov块分配器:这项技术把顶层目录散布到各个柱面组,这样目录和其中的内容更有可能被放到一起,减少随机 I/O。
  • 目录索引:在文件系统里引入哈希 B 树,提高目录查找速度。

ext4

Linux ext4文件系统对ext3 进行了各种功能扩展和性能提升:区段、大容量、通过fallocate() 预分配、延时分配、日志效验和、更快的fsck、多块分配器、纳秒时间戳以及快照。

关键性能特性如下:

  • 区段:区段提高了数据的连续性,减少了随机 I/O,提高了连续I/O的大小。
  • 预分配:通过fallocate() 系统调用,让应用程序预分配一些可能连续的空间,提高之后的写性能。
  • 延时分配:块分配推迟到写入磁盘时,方便合并写请求(通过多块分配器),降低碎片化。
  • 更快的fsck:标记未分配的块和inode项,减少fsck时间。

ZFS

ZFS把文件系统、卷管理器以及许多企业级特性整合在一起:池存储、日志、写时复制(COW)、自适应替换缓存(ARC)、大容量支持、变长块、动态条带、多预取流、快照、克隆、压缩、檫洗和128位效验和。之后的更新包括了更大的功能,有:热备、双重奇偶效验RAID、gzip压缩、SLOG、L2ARC、用户和组配额、三重奇偶效验 RAID、数据消重、混合RAID 分配以及加密。这些组合使ZFS成为文件服务器(filer)有竞争力的选择。

关键性能特性如下:

  • 池存储:所有的存储设备放到一个池里,文件系统则从池里创建。这样所有的设备都能够同时用到,最大化了吞吐量和IOPS。可以使用不同的RAID类型创建池:0、1、10、Z(基于 RAID-5)、Z2(双重奇偶效验)和Z3(三重奇偶效验)。
  • COW:合并写操作并顺序写入。
  • ARC:自适应替换缓存通过使用几种缓存算法提高缓存的高命中,算法包括了最近使用(most recently used,MRU)和最常使用(most frequently used,MFU)。内存在两种算法之间平衡,根据模拟某个算法完全主导内存时系统的性能,做出相应的调整。这种模拟需要消耗额外的元数据(幽林列表,ghost list)。
  • 变长块:每个文件系统都可根据相应的负载选择一个可配置的最大块大小(记录尺寸)。小文件用小尺寸
  • 动态条带:为了达到最大吞吐量,条带横跨了所有的存储设备,并在添加额外设备时能自动增长。
  • 智能预取:ZFS对不同类型的数据用相匹配的预取策略,分别针对元数据,znode(文件内容)以及vdev(虚拟设备)
  • 多预取流:由于文件系统来回寻道(UFS的问题),一个文件的多个读取流会产生近似随机I/O的负载。ZFS跟踪每一个预取流,允许加入新的流,有效地发起 I/O。
  • 快照:COW的架构使得快照能瞬间建立,按需复制新数据块。
  • ZIO 流水线:设备 I/O 由一个分步骤的流水线处理,每个步骤都有一个线程池服务以提高性能。
  • 压缩:ZFS 支持多种算法,但CPU 开销对性能有所影响。其中,轻量级的lzji (Lempel-Ziv Jeff Bonwick)可以通过少量消耗CPU 来减少I/O 负载(由于数据的压缩),提高存储性能。
  • SLOG:独立的ZFS 意图日志(intent log)使日志可以同步写入单独的设备,避免和磁盘池的负载竞争。SLOG 写入的数据在系统宕机时仅只读以供回放。这些措施极大地提高了同步写的性能。
  • L2ARC:二级ARC 是主存之外的第二级缓存,通过闪存的固态硬盘(SSD)缓存随机地读负载。L2ARC不用于缓冲写负载,仅仅包含已经写入存储磁盘池里的干净数据。L2ARC拓展了系统缓存的边界,防止在负载超出主存能力的情况下出现性能断崖。不过由于填充的速度低于主存,延时不可避免。另外,缓存里还有一些长期的数据拷贝。如果主缓存被突然的I/O 爆发污染了,主存还可以从L2ARC中快速回复热点数据。
  • vdev缓存:和最初的缓冲区高速缓存类似,ZFS给每个虚拟设备分配了单独的vdev缓存,并支持LRU和预读。(在有些操作系统可能被禁用)
  • 数据消重:这是一个文件系统级别的特性,避免把一份相同的数据存放多份。这项功能对性能有很大的影响,可能有利(减低设备I/O),也可能有弊(当内存不够容纳哈希表时,设备I/O可能会被大大放大)。

L2ARC和SLOG 是ZFS 混合存储池(Hybrid Storage Pool,HSP)模型的一部分,目的是为了有效使用ZFS 存储池里针对读和写优化的 SSD。读优化的 SSD 在内存和磁盘间有很好的性价比,很适合充当额外的缓存层。

其他一些较小的性能特性还包括:“别管缺口”,即在合适的时候发起更大的读请求,即便并不需要其中一些块(缺口),以及支持多策略存储池的混合RAID。

ZFS在有些情况下性能略逊于其他文件系统:ZFS默认向存储设备发起缓存写回命令,确保写入已经完成以防断电。这是ZFS完整性的一个特性,但这也有代价:有效ZFS操作必须等待缓存写回完成,因此引发了延时,造成某些负载下ZFS的性能差于其他文件系统。ZFS也可以做出调整,关闭缓存写回以提高性能。然而,如果和其他文件系统一样,碰上断电就会有数据部分写入和损坏的风险,具体取决于使用的存储设备。

btrfs

B 树文件系统(btrfs)是基于写时复制的 B树。和ZFS 类似,它采用了结合文件系统和卷管理器的现代架构,预计提供的功能和ZFS基本相同。当前的特性包括了池存储、打容量支持、区段、写时复制、卷扩容和缩容、子卷、块设备添加和删除、快照、克隆、压缩已经CRC-32C效验和。

关键的性能特性如下:

  • 池存储:存储设备被放在一个卷里,在此上可以建立文件系统。这样所有的设备都能同时用到。最大化了吞吐量和IOPS。可以使用不同的RAID类型建池。即0、1、10。
  • COW:合并写操作并连续写入。
  • 在线平衡:把对象在存储设备间挪动以平衡负载。
  • 区段:提高数据的连续摆放以提升性能。
  • 快照:COW的架构使得快照能瞬间建立、按需复制新数据快。
  • 压缩:支持zlib和LZO。
  • 日志:对每个子卷建立相应的日志树,以应对同步的COW日志负载。

卷和池

文件系统一直以来建立在一块磁盘或者一个磁盘分区上。卷和池使文件系统可以建立在多块磁盘上,并可以使用不同的RAID策略。

卷把多块磁盘组合成一块虚拟磁盘,在此之上可以建立文件系统。在整块磁盘上建文件系统时(不是分片或者分区)。卷能够隔离负载,降低竞争,缓和性能问题、

卷管理软件包括Linux的逻辑卷管理器(Logical Volume Manager,LVM)。卷或虚拟磁盘可以由硬件RAID控制器提供。

池存储把多块磁盘放到一个存储池里,池上可以建立多个文件系统。下图体现了这种差异。池存储比卷存储更灵活,文件系统可以增长或者缩小而不牵涉下面的设备。这种方法被现代文件系统采用。包括ZFS和btrfs、

池存储可以让所有的文件系统使用所有磁盘,以提高性能。负载并未隔离,有些情况下可以牺牲一些灵活性,使用多个池来隔离负载,这是因为磁盘设备在一开始必须被加入某一个池。

有关使用软件卷管理器还是池存储的其他性能考虑如下:

  • 条带宽度:与负载相匹配。
  • 观察性:虚拟设备的使用率可能不准确,需要检查对应的物理设备。
  • CPU 开销:尤其是在进行 RAID 奇偶校验的计算时。不过随着更快的现代CPU 的使用,这个问题逐渐消失。
  • 重建:又名重新同步(resilvering),当一块空磁盘加入到RAID组里,它被填入必要的数据以加入组。由于消耗I/O 资源长达几个小时甚至几天,可能严重影响性能。

方法

文件系统性能研究方法

方法 类型
磁盘分析 观察分析
延时分析 观察分析
负载特征归纳 观察分析,容量规划
性能监控 观察分析,容量规划
事件跟踪 观察分析
静态性能调优 观察分析,容量规划
缓存调优 观察分析,调优
负载分离 调优
内存文件系统 调优
微基准测试 实验分析

这些方法可以单独使用。也可以组合使用。建议,按顺序使用以下策略:延时分析、性能监控、负载特征归纳、微型基准测试、静态性能调优和事件跟踪。也可以根据我们的环境给出最适合的组合和顺序。

磁盘分析

以前通常的策略是关注磁盘性能而忽视文件系统。这假定了 I/O 瓶颈在磁盘,因此通过分析磁盘,能方便地在假定罪魁祸首上集中火力。

如果文件系统较为简单并且缓存较小,这可能还可行。如今这个方法容易让人误入歧途,且错失了一整类问题。

延时分析

延时分析从测量文件系统操作的延时开始。这应该包括所有的对象操作,而不限于I/O(比如包括sync())。

操作延时 = 时刻(完成操作)- 时刻(发起操作)

这些时间可以从下面四个相邻层里测量得到,下表有相应说明。

文件系统延时分析的目标(层)

优点 缺点
应用程序 文件系统延时对应用程序影响的第一手信息;能够查看应用程序的环境,确定延时是否发生在应用程序的关键功能里,或是异步 不同的应用程序以及不同的软件版本需使用不同的技术
系统调用接口 有详尽资料的接口。通常可以通过操作系统工具和静态跟踪进行观察 系统调用捕捉所有类型的文件系统,包括非存储型文件系统(统计、套接字),除非能过滤,否则会造成干扰。除此之外,一个文件系统函数可能有多个系统调用。例如读就有 read()、pread()、read64等,所有这些都需要测量
VFS 所有文件系统通用的标准接口,操作系统操作和调用一一对应(例如vfs_write()) VFS 跟踪所有类型的文件系统,包括非存储型文件系统,除非能过滤,否则会造成干扰
直接在文件系统上 只能跟踪和目标统一类型的文件系统,能获取内部环境上下文详情 特定于某种文件系统。不同版本的文件系统需要的跟踪技术不尽相同(虽然文件系统可能有一个不常变化的类VFS接口,与VFS接口一一对应)

选择哪层取决与可用的工具,看看下面几项:

  • 应用程序文档:有些应用程序已经提供了文件系统延时的指标,或者收集这些数据的方法
  • 操作系统工具:操作系统可能也提供了延时指标,理想情况下能对每个文件系统或者应用程序提供单独的统计信息。
  • 动态跟踪:如果系统支持动态跟踪,那所有层都可以通过自定义的脚本进行检查,无须重启。

延时可以表现如下:

  • 单位实际平均值:如每秒平均读延时
  • 全分布:如直方图或热图
  • 单位操作延时:列出每个操作

对于缓存命中率高(大于99%)的文件系统,单位时间平均值可能完全被缓存命中淹没。不幸的是,有些高延时(离群点)的个案虽然很重要,但很难从平均值里看出。检查全分布、每个操作的延时以及不同层延时的影响,包括文件系统缓存命中和未命中情况,可以帮助挑出这些离群点以供调查。

一旦找到了高延时,继续向下挖掘分析文件系统以找到问题根源。

事务分析

文件系统延时,还能以一个应用程序事务内(如一个数据库查询)等待文件系统的所有时间来表现:

文件系统消耗时间百分比 = 100 * 所有的文件系统阻塞延时 / 应用程序事务时间

文件系统操作的损耗因此得以从应用程序性能的角度量化,而性能改进也能被更准确地预测。测量的指标可以是一段时间内所有事务的均值,或者是单个事务。

下图展现了一个正在执行事务的应用程序线程的时间分布。这个事务发起了一个文件系统读请求,应用程序被阻塞等待完成,并让出CPU。这个情况下,总阻塞时间就是这个文件系统读所花费的时间。如果一个事务中有多个 I/O 被阻塞,那总时间就是它们的和。

负载特征归纳

归纳负载特征是容量规划、基准测试和负载模拟中一项重要的工作。这项工作可以通过指出并排除不需要的工作来获的最大的性能收益。

下面是文件文件系统负载需要归纳的几个基本属性:

  • 操作频率和操作类型
  • 文件 I/O 吞吐量
  • 文件 I/O大小
  • 读写比例
  • 同步写比例
  • 文件随机和连续访问比例

这些特征指标会随着时间的变化而变化,尤其是那些每隔一段时间执行的应用程序定时任务。为了更好地归纳出特征,除了平均值还有得到最大值。最好看看跨时段的全分布。

下面是一个负载描述样例,演示了如何把这些属性一起描述清楚:

一个金融交易系统的数据库,给文件系统产生了随机负载,频率为平均每秒18 000次读,平均读大小为2KB。总操作频率是21 000次/s,包括了读取、统计、打开、关闭和大概每秒200次的吸入。写频率相对于读较为稳定,后者高峰时能达到每秒39 000次。

这些特征即针对单个的文件系统,也可针对一个系统里所有的同类型文件系统。

高级负载特征归纳/检查清单

归纳特征还需要一些细节信息。下面这些问题需要好好考虑,在深度研究文件系统问题时也可以作为检查清单使用:

  • 文件系统缓存命中率是多少?未命中率是多少?
  • 文件系统缓存有多大?当前使用情况如何?
  • 现在还使用了其他什么缓存(目录、inode、高速缓冲区)和它们的使用情况?
  • 哪个应用程序或者用户在使用文件系统?
  • 哪些文件和目录正在被访问?是创建和删除吗?
  • 碰到了什么错误吗?是不是由于一些非法请求,或者文件系统自身的问题?
  • 为什么要发起文件系统I/O(用户程序的调用路径)?
  • 应用发起的文件系统 I/O 中同步的比例占多少?
  • I/O 抵达时间的分布是怎样的?

这些问题中很多可以针对某个应用程序或某个文件单独提出。另外任何问题都可以做一个跨时段分析,找到最大值和最小值,以及与时间相关的变化。

性能特征归纳

下面的问题刻画了负载的性能数据:

  • 文件系统操作的平均延时是多少?
  • 是否有高延时的离群点?
  • 操作延时的全分布是什么样的?
  • 是否拥有并开启了文件系统或磁盘 I/O 的系统资源流控?

性能监控

性能监控可以识别出当前存在的问题,以及跨时段的行为模式。主要的文件系统性能指标是:

  • 操作频率
  • 操作延时

操作频率是负载的最基本特征,而延时则是其性能结果。延时是好是差,取决于负载、环境和延时需求。如果不太清楚,可以针对已知好的和差的情况分布做微型基准测试,调查延时情况。

操作延时的指标可以每秒平均值为单位,也可以包含其他数据,如最大值和标准差。理想情况下,最好看看延时的全分布,如通过直方图和热度图以发现离群点或其他模式。

可以只记录单个操作类型(读、写、统计、打开、关闭,等等)的频率和延时数据。这对调查负载和性能变化有很大的帮助,因为可以找出不同操作类型之间的差异。

对于一些基于文件系统实现资源控制的系统(例如 ZFS I/O 限流),还需要包括一些统计信息表明是否有流控以及流控的时间。

事件跟踪

事件跟踪捕获文件系统每个操作的细节。这是观测分析最后的手段。由于捕获和保持信息到日志文件,这种方法增加了性能开销。这些日志信息可能包含了每个操作的下列信息。

  • 文件系统类型
  • 文件系统挂载点
  • 操作类型:读取、写入、统计、打开、关闭、建目录,等等
  • 操作大小(如果适用):字节数
  • 操作开始时间戳:向文件系统发起操作的时间
  • 操作结束时间戳:文件系统完成操作的时间
  • 操作完成状态:错误
  • 路径名
  • 进程ID
  • 应用程序名

有了开始和结束的时间戳,就可以计算操作的延时。许多跟踪框架允许一边跟踪一边计算,这样日志里就能包含延时数据。此外还可以过滤输出,只记录那些慢于某个阀值的操作。文件系统的操作频率有可能达到每秒百万次,正确地过滤将会很有帮组。

静态性能调优

静态性能调优主要关注问题发生的配置环境。对于文件系统性能,检查下面列出的静态配置情况:

  • 挂载并当前使用了多少个文件系统?
  • 文件系统记录大小?
  • 启用了访问时间戳吗?
  • 还启用了哪些文件系统选项(压缩、加密等)?
  • 文件系统缓存是怎么配置的?最大缓存大小是多少?
  • 其他缓存(目录、inode、高速缓冲区)是怎么配置的?
  • 有二级缓存吗?用了吗?
  • 有多少个存储设备?用了几个?
  • 存储设备是怎么配置的?用了RAID吗?
  • 用了哪种文件系统?
  • 用了哪个文件系统的版本(或者内核)?
  • 有什么需要考虑的文件系统 bug/补丁?
  • 启用文件系统 I/O 的资源控制了吗?

回答这些问题能够暴露一些被忽视的配置问题。有时按照了某种负载来配置文件系统,但后来却用在了其他的场景里。这个方法能让我们重新审视那些配置选项。

缓存调优

内核和文件系统会使用多种缓存,包括缓冲区高速缓存、目录缓存、inode缓存和文件系统(页)缓存。总体上来说,先检查有哪些缓存,接着看它们是否投入使用,使用的情况如何缓存大小,然后根据缓存调整负载,以及根据负载调整缓存。

负载分离

有些类型的负载在独占文件系统和磁盘设备时表现得更好。这个方法又被称为使用“单独转轴”,因为施加两种不同的负载会导致随机 I/O,这对旋转的磁盘特别不利。

例如,让日志文件和数据库文件拥有单独的文件系统和磁盘,提高数据库性能。

内存文件系统

另一个通过配置提高性能的方法是使用内存文件系统。文件内容放在内存里,这样能够以最快速度响应请求。不过由于许多应用程序在自己的进程内存里(可配置)有专用缓存,而访问专用缓存比经过文件和系统调用高效得多,因此这个办法一般用作临时方案。现代文件系统通常由很大的文件系统缓存,因此内存文件系统并不实用。

/tmp

标准的 /tmp 文件系统用来存储临时文件,通常基于内存。

微型基准测试

文件系统和磁盘(有很多)的基准测试工具可以用来测试多种类型文件系统的性能,或者某种负载下,同一文件系统不同设置下的性能。典型的测试参数如下:

  • 操作类型:读、写和其他文件系统操作的频率
  • I/O 大小:从1B 到1MB甚至更大
  • 文件偏移量模式:随机或者连续
  • 随机访问模式:统一的随机分布或者帕累托分布
  • 写类型:异步或同步(O_SYNC)
  • 工作集大小:文件系统缓存是否放得下
  • 并发:同时执行的 I/O 数,或者执行 I/O 的线程数
  • 内存映射:文件通过mmap() 访问而非 read() / write()。
  • 缓存状态:文件系统缓存是“冷的”(未填充)还是“热的”。
  • 文件系统可调参数:可能包括了压缩、数据消重等。

最常见的组合包括了随机读、连续读、随机写和连续写。

最重要的参数通常是工作集大小:基准测试时访问的数据量。这可能是当前使用文件的总大小,取决于基准测试的配置。一个较小的工作集会导致所有访问都从主存(DRAM)里的缓存中返回。而一个较大的工作集则可能使得访问大部分从存储设备(磁盘)返回。其间的性能差异可能达到几个数量级。

下表列出的不同基准测试的大致预期结果,其中包括了文件的总大小(工作集大小)

有些文件系统基准测试工具并不了解它们测试的对象,可能一边显示磁盘基准测试,一边使用较小的总文件大小,结果自然都从缓存中返回。

系统内存 文件总大小 基准测试 预期结果
128GB 10GB 随机读 100%缓存命中
128GB 1000GB 随机读 大部分是磁盘读,其中大约12%是缓存命中
128GB 10GB 连续读 100%是缓存命中
128GB 1000GB 连续读 兼有缓存命中(由于预读)和磁盘读
128GB 10GB 大部分是缓冲命中(缓冲),夹杂一些被阻塞的写,取决于文件系统行为
128GB 10GB 同步写 100磁盘写

有些基准测试工具通过文件系统的直接 I/O 接口发起,以避免了缓存和缓冲。文件系统增加了代码执行路径以及从文件映射到磁盘的开销,因此仍有稍许影响。有时候这是测试文件系统的一个巧办法:分析最差情况下的性能(0%缓存命中率)。随着系统内存越来越大,应用程序往往对缓存命中率有很高的预期,这个方法逐渐变得不太现实。

性能之巅:洞悉系统、企业与云计算——文件系统相关推荐

  1. 《性能之巅—洞悉系统、企业与云计算》读书笔记---第二章

    目录 第二章  方法 2.1术语 2.2模型 2.3概念 2.4视角 2.5方法 2.6建模 2.7容量规划 2.8统计 2.9监视 2.10可视化 第二章  方法 面对一个性能不佳且复杂的系统环境时 ...

  2. 性能之巅 洞悉系统、企业与云计算(完整版)

    性能之巅  洞悉系统.企业与云计算书籍,主要讲解大型网络.云计算.大数据和虚拟计算机系统的快速部署已经为性能优化带来了新的挑战.本书为此提供了解决方案.国际知名的性能优化专家Brendan Gregg ...

  3. 《性能之巅—洞悉系统、企业与云计算》读书笔记---第一章

    目录 第一章  绪论 1.1系统性能 1.2人员 1.3事情 1.4视角 1.5性能是充满挑战的 1.6延时 1.7动态跟踪 1.8云计算 1.9案例研究 第一章  绪论 1.1系统性能 1.2人员 ...

  4. 性能之巅——洞悉系统、企业与云计算 Brendan Gregg

    1.绪论 系统性能是对整个系统的研究,包括所有的硬件组件和整个软件栈.所有数据路径上和软硬件上所发生的的事情都包括在内,都可能影响性能 性能领域包含以下方面 设置性能目标和建立性能模型 基于软件或硬件 ...

  5. 性能之巅:Linux网络性能分析工具-netstat,ifconfig,nicstat,traceroute,tcpdump

    原文地址:http://www.infoq.com/cn/articles/linux-networking-performance-analytics 本文介绍基于Linux操作系统的网络性能分析工 ...

  6. 性能之巅:Linux网络性能分析工具

    编者按:InfoQ开设新栏目"品味书香",精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注.本文节选自格雷格著<性能之巅:洞悉系统.企业与云计算>中 ...

  7. 必看!2021年云计算行业五大趋势,云南昆明企业小型云计算平台搭建及解决方案

    2020年发生的众多事件让对2021年的大多数预测浮出水面.人工智能(AI)和物联网(IoT)等热门技术趋势仍将在明年重塑我们生活的方式.然而,最重要的用处是帮助我们在这个不断变化的时代下适应和生存. ...

  8. 《性能之巅》阅读总结

    文章目录 一.性能分析概念 性能定义与权衡要素 性能的扩展性(负载增加性能变化) 性能权衡三角关系 性能投资回报率 性能分析困难原因 产品性能领域典型事项 二.性能分析方法 性能分析思路视角 性能分析 ...

  9. 为什么越来越多的企业选择云计算?

    一.前言 1.当下企业信息化的痛点 企业信息化,这也算是一个老生常谈的话题了,整个中国业内前前后后应该喊了有十多年了.不过到目前为止,我国很多企业公司都还没真正形成一个完整的信息化框架,或者只是运用了 ...

最新文章

  1. 【Datawhale-Python】Task1
  2. 深入理解Java线程池:ThreadPoolExecutor
  3. 收藏 | 机器学习最全知识点汇总(万字长文)
  4. 强化学习、联邦学习、图神经网络,飞桨全新工具组件详解
  5. 为什么机器学习模型在生产中会退化?
  6. python中figsize什么意思_matplotlib 设置图形大小时 figsize 与 dpi 的关系
  7. 合作伙伴说 | 一人行快,众人行远,与网易共建万亿新生态
  8. linux 多线程实现倒计时,Linux用脚本实现“时分秒“倒计时功能
  9. 第十一节:WebApi的版本管理的几种方式
  10. 那些你不知道的程序员的多重身份
  11. 项目Wiki的选择和配置
  12. 报错,null [java.lang.IndexOutOfBoundsException,Index: 5, Size: 5]
  13. 数字电路基本概念 —— fan-in/fan-out
  14. 【luogu3368】模板 树状数组 2
  15. java案例代码5--编码的方式--密码
  16. 医疗行业缩写所表示含义
  17. G729调用方法及使用wavlib播放出现颤音的解决方法
  18. 华为云Hadoop与Spark集群环境搭建
  19. 分享一个ZPL指令在线测试网址
  20. css:好看的渐变色_CSS渐变:语法速成课程

热门文章

  1. 2022年中式烹调师(初级)模拟试题及中式烹调师(初级)模拟考试
  2. 仿写练习-京东商城导航条
  3. Prometheus 实战于源码分析之discovery
  4. 中鑫优配:大盘放量补缺,注意超跌股的补涨机会
  5. 分析一下 原型模式的 UML 类图 。 复制对象, 深浅拷贝 月经贴 ,请回避
  6. vue office在线编辑_多人协同、AI 协作……未来的 Office 有这些「黑科技」
  7. 【洛谷】P2713 罗马游戏
  8. Python 语法(一)
  9. mysql取消用户授权
  10. Android巴士倒闭了吗