目录
案例背景
磁盘结构
盘面
磁道
柱面
启停区或着陆区(LandingZone)
扇区
读写过程
磁盘碎片的产生(1)
希捷硬盘读写性能测试报告
文件碎片
文件碎片定义
产生 原因
文件碎片和连续文件读取性能对比(100M相同内容的文件)
解决方案
避免随机读 写
随机读写危害
随机读写解决方案
异步刷盘和同步刷盘
同步刷盘
异步刷盘
小文件性能影响及解决方案
小文件性能影响
小文件解决方案
并发写提高数据入盘性能
广义并发写
狭义并发写
读写分离
读写分离的必要性
读写分离的解决方案
业界优秀案例
K afka的log文件系统
topic中partition存储分布
采用优化策略
数据过期删除及log大小固定策略
小文件合并增大吞吐量
目前影响文件读写性能的点在于文件碎片,大量小文件,文件刷盘方式,随即读写等。基于上述几类瓶颈点给于解决方案。以便解决业务线现场大量的语音文件接入。
硬盘在逻辑上被划分为磁道、柱面以及扇区.如下图所示:
数据的读/写按柱面进行,而不按盘面进行。也就是说,一个磁道写满数据后,就在同一柱面的下一个盘面来写,一个柱面写满后,才移到下一个扇区开始写数据。读数据也按照这种方式进行,这样就提高了硬盘的读/写效率。
硬盘的盘片一般用铝合金材料做基片,高速硬盘也可能用玻璃做基片。硬盘的每一个盘片都有两个盘面(Side),即上、下盘面,一般每个盘面都会利用,都可以存储数据,成为有效盘片,也有极个别的硬盘盘面数为单数。每一个这样的有效盘面都有一个盘面号,按顺序从上至下从“0”开始依次编号。在硬盘系统中,盘面号又叫磁头号,因为每一个有效盘面都有一个对应的读写磁头。硬盘的盘片组在2~14片不等,通常有2~3个盘片,故盘面号(磁头号)为0~3或 0~5。
磁盘在格式化时被划分成许多同心圆,这些同心圆轨迹叫做磁道(Track)。磁道从外向内从0开始顺序编号。硬盘的每一个盘面有300~1 024个磁道,新式大容量硬盘每面的磁道数更多。信息以脉冲串的形式记录在这些轨迹中,这些同心圆不是连续记录数据,而是被划分成一段段的圆弧,这些圆弧的角速度一样。由于径向长度不一样,所以,线速度也不一样,外圈的线速度较内圈的线速度大,即同样的转速下,外圈在同样时间段里,划过的圆弧长度要比内圈 划过的圆弧长度大。每段圆弧叫做一个扇区,扇区从“1”开始编号,每个扇区中的数据作为一个单元同时读出或写入。一个标准的3.5寸硬盘盘面通常有几百到几千条磁道。磁道是“看”不见的,只是盘面上以特殊形式磁化了的一些磁化区,在磁盘格式化时就已规划完毕。
所有盘面上的同一磁道构成一个圆柱,通常称做柱面(Cylinder),每个圆柱上的磁头由上而下从“0”开始编号。数据的读/写按柱面进行,即磁 头读/写数据时首先在同一柱面内从“0”磁头开始进行操作,依次向下在同一柱面的不同盘面即磁头上进行操作,只在同一柱面所有的磁头全部读/写完毕后磁头 才转移到下一柱面(同心圆的再往里的柱面),因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。电子切换相当快,比在机械上磁头向邻近磁道移动快得多,所以,数据的读/写按柱面进行,而不按盘面进行。也就是说,一个磁道写满数据后,就在同一柱面的下一个盘面来写,一个柱面写满后,才移到下一个扇区开始写数据。读数据也按照这种方式进行,这样就提高了硬盘的读/写效率。一块硬盘驱动器的圆柱数(或每个盘面的磁道数)既取决于每条磁道的宽窄(同样,也与磁头的大小有关),也取决于定位机构所决定的磁道间步距的大小。
磁头靠近主轴接触的表面,即线速度最小的地方,是一个特殊的区域,它不存放任何数据
操作系统以扇区(Sector)形式将信息存储在硬盘上,每个扇区包括512个字节的数据和一些其他信息。一个扇区有两个主要部分:存储数据地点的标识符和存储数据的数据段。
即一次访盘请求(读/写)完成过程由三个动作组成:
1)寻道(时间):磁头移动定位到指定磁道 ,经验值在3~15ms
2)旋转延迟(时间):等待指定扇区从磁头下旋转经过 ,60*1000/7200/2 = 4.17ms(7200rpm硬盘)
3)数据传输(时间):数据在磁盘与内存之间的实际传输(200M/S,希捷4TB硬盘)
因此在磁盘上读取扇区数据(一块数据)所需时间:
Ti/o=tseek +tla + n *twm
其中:
tseek 为寻道时间
tla为旋转时间
twm 为传输时间
俗话说一图胜千言,先用一张ACSII码图来解释为什么会产生磁盘碎片。
这里所说的方法二就像是我们的windows系统的存储方式,每个文件都是紧挨着的,但如果其中某个文件要更改的话,那么就意味着接下来的数据将会被放在磁盘其他的空余的地方。
如果这个文件被删除了,那么就会在系统中留下空格,久而久之,我们的文件系统就会变得支离破碎,碎片就是这么产生的。
Linux文件碎片基本很少,Linux的ext2,ext3,ext4文件系统——ext4是Ubuntu和目前大多发行版所采用的文件系统——会以一种更加智能的方式来放置文件。Linux的文件系统会将文件分散在整个磁盘,在文件之间留有大量的自由空间,而不是像Windows那样将文件一个接一个的放置。当一个文件被编辑了并且变大了,一般都会有足够的自由空间来保存文件。如果碎片真的产生了,文件系统就会尝试在日常使用中将文件移动来减少碎片,所以不需要专门的碎片整理程序。
硬盘规格:希捷新酷鱼2TB硬盘(3碟版)的HD Tune读取
上述出现原因,从磁盘结构上看,角速度一致,线速度由于半径越来越小,导致读写速度下降。
文件碎片是因为文件被分散保存到整个磁盘的不同地方,而不是连续地保存在磁盘连续的簇中形成的
(1)在文件操作过程中,Windows系统可能会调用虚拟内存来同步管理程序,这样就会导致各个程序对硬盘频繁读写,从而产生文件碎片。
(2)还有一种情况就是当中间的一个扇区内容被删除后,新写入一个较小的文件,这样在这个文件两边就会出现一些空间,这时候再写入一个文件,两段空间的任意一部分都不能容纳该文件,这时候就需要将文件分割成两个部分,碎片再次产生了。
(3)最常见的就是下载电影之类的大文件,这期间大家一般都会处理一下其它事情,而下载下来的电影文件被迫分割成若干个碎片存储于硬盘中。因此下载是产生碎片的一个重要源头。还有就是经常删除、添加文件,这时候如果文件空间不够大,就会产生大量的文件碎片,随着文件的删改频繁,这种情况会日益严重。
从上图看出,在采用nio读取文件的时候,buffer的大小设置为512kb,读取性能最高,文件碎片读取为80ms,连续文件读取60ms,得知,文件碎片严重影响读写性能。
在我们目前开发的文件存储系统,由于各种原因,很难避免文件碎片的产生。基于迅雷下载思想的启发,我们在从小文件读取数据写入大文件的时候,可以事先分配好大文件的尺寸,占据固定大小的连续的磁盘分区。这样在采用修改的方式,把每个字节修改成指定的数据,会得到连续完整的文件,避免磁盘碎片的产生。
分配尺寸方式:RandomAccessFile raf = new RandomAccessFile(file, “rw”);
Raf.setLength(1024*1024*1024);
这样可以分配1G大小的文件,经测试1ms左右的时间可以完成上述分配。
由磁盘结构一节可知,数据写入和读取时间由寻址,磁头旋转,数据传输组成,如果随即读写的话,每一次数据读写时间=寻址(12ms)+磁头旋转(3ms)+数据传输(1ms,小文件),如果是顺序读写时间=数据传输。
磁盘分区循环写入数据,先将一块磁盘分成3个分区,1分区写完,写2分区,2分区写完,写3分区,3分区写完,将1分区快速格式化,然后将数据写入1分区。
采用零拷贝的方式,采用nio的强制刷盘策略,等待文件真正落盘后,才会继续代码流程,小文件性能大概在2~3M/s左右。
javaIo的普通刷盘方式,就是一种异步刷盘,当调用文件IO的flush的方式时,其实是向内核发送一条刷盘指令,直接返回。小文件性能在13M/S左右。
文件读写时间=寻址+旋转延迟+数据传输,小文件读写的性能瓶颈在寻址上。
小文件200kb:寻址时间=12ms
旋转延迟=3ms
数据传输=1ms
上述读写性能在0.2M * (1/0.016)=12.5M/S,IOPS=62次/s
大文件 20GB: 寻址时间=12ms
旋转延迟=3ms
数据传输=20*1024/200=102400ms
上述读写性能在 20*1024M *(1/102415) = 200M/s,基本可以发挥文件读写极致,瓶颈在数据传输上,这是我们想要的效果。
将小文件合并成大文件进行存储,这样读取性能就会大大提高,目前kafka的log存储和读取原理采用的就是此种方式。
数据块定义:起始字符,版本标识,chunkSize,subChunkId, subChunkSize,Data,结束标记。
存储:存储小文件的时候,首先抽取一个可存入大文件路径, 封装小文件的元数据和数据信息,追加到大文件的尾部。返回一个加密后的ID,这个id包含小文件的位置和基本信息。
读取:读取根据id进行读取,也可遍历读取。
删除:在元数据中加标记,不真正删除文件,避免文件碎片, 后期统一格式化处理。
一块磁盘启用多个线程进行写数据,根据硬盘知识可知,一块硬盘有很多磁头,但是只有一个有效磁头在运作,多个线程会造成磁头争夺,导致写入性能下降。
一台linux机器可以多块硬盘,比如4块,为了避免争夺磁头,每块硬盘最多只能由一个线程操作,这样就可以同时写入4个数据分别落到不同的硬盘中,实现方式架构如下图:
Queue:数据硬盘目录,四块硬盘,四个目录,阻塞队列
线程池:根据硬盘数量分配线程池线程数量,四块硬盘,四个线程。
流程:当数据准备入库,获取线程,线程从队列中请求入库硬盘,根据请求地址入库。
根据硬盘知识可知,一块硬盘有很多磁头,但是只有一个有效磁头在运作,如果同时有数据进行读和写操作,就会造成磁头争夺,导致性能下降。
目前同一进程读写分离可以实现,不同进程的读写分离无法实现。
Kafka集群只有一个broker,xxx/message-folder为数据文件存储根目录,在Kafka broker中server.properties文件配置(参数log.dirs=xxx/message-folder),例如创建2个topic名 称分别为report_push、launch_info, partitions数量都为partitions=4
存储路径和目录规则为:
xxx/message-folder
|--report_push-0
|--report_push-1
|--report_push-2
|--report_push-3
|--launch_info-0
|--launch_info-1
|--launch_info-2
|--launch_info-3
在Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。
kafka是一个高吞吐量分布式消息系统,并且提供了持久化。其高性能的有两个重要特点:1、并发,2、连续读写性能远高于随机读写。
优化着重于并发策略,下面举两个案例说明:
一个topic分成多个patition,磁盘总数是一块,这样多个分区会往同一块磁盘写入数据,造成广义并发写,磁头争夺,性能急剧下降。
一个topic分成多个patition,磁盘总数是多块,这样每一个分区会写一块硬盘,造成狭义并发写,性能得到保障。
Kafka并未提供删除数据的api,可以设置过期时间,kafka根据过期时间自动删除。
Kafka的log大小固定,所以一块盘中的log数据基本上都是一样大,根据文件写入规则,基本上不会产生文件碎片。
众所周知,kafka 是一个消息中间件,存储采用固定大小的log系统,当我们将小文件数据或者是一条条日志存储到kafka中,都会被写到指定的log文件,这样小文件就会被组合成大文件。
每个log entry格式为"4个字节的数字N表示消息的长度" + "N个字节的消息内容";每个日志都有一个offset来唯一的标记一条消息,offset的值为8个字节的数字,表示此消息在此partition中所处的起始位置..每个partition在物理存储层面,有多个log file组成(称为segment).segment file的命名为"最小offset".kafka.例如"00000000000.kafka";其中"最小offset"表示此segment中起始消息的offset.
获取消息时,需要指定offset和最大chunk尺寸,offset用来表示消息的起始位置,chunk size用来表示最大获取消息的总长度(间接的表示消息的条数).根据offset,可以找到此消息所在segment文件,然后根据segment的最小offset取差值,得到它在file中的相对位置,直接读取输出即可。