第一章 硬盘硬件工作原理
1.1 机械硬盘物理结构
今天我以磁盘结构作为硬盘的开篇,来分享我这些年在这方面的思考。
磁盘结构
为了方便讨论,我们还是先从最基本的磁盘物理结构说起吧,对于常见的机械磁盘,分磁盘面、磁道、柱面和扇区。(注意本文只讨论机械磁盘,SSD先放一放再说)。机械硬盘拆开以后,结构如下。
[Image 39 on Page 4] [Image 40 on Page 4]
我们再用一个逻辑图看一下。
[Image 45 on Page 5]
可见有以下概念:
- 磁盘面:磁盘是由一叠磁盘面叠加组合构成,每个磁盘面上都会有一个磁头负责读写。
- 磁道(Track):每个盘面会围绕圆心划分出多个同心圆圈,每个圆圈叫做一个磁道。
- 柱面(Cylinders):所有盘片上的同一位置的磁道组成的立体叫做一个柱面。
- 扇区(Sector):以磁道为单位管理磁盘仍然太大,所以计算机前辈们又把每个磁道划分出了多个扇区。
所以,磁盘存储的最小组成单位就是扇区。
容量计算公式
单柱面的存储容量 = 每个扇区的字节数 × 每个柱面扇区数 × 磁盘面数
整体磁盘的容量就等于单柱面容量乘以总的柱面数字。
扇区与扇区之间其实不是紧挨着的,而是在每个扇区结尾其实还有一个存储纠错码的位置。假设某一个扇区读取时发生了错误,这样在扇区结尾的纠错码就能发现。磁头就会在磁盘下一圈转过来的时候再读取一遍。
动手实际查看
Linux相比较 windows 操作系统,一个优点就是对开发非常友好和透明。只要你愿意,你总能扒到你想要的信息。Linux上可以通过 fdisk 命令,来查看当前系统使用的磁盘的这些物理信息。
首先我们查看服务器上安装的硬盘数量以及大小,这需要借助 lsblk 这个命令。
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 20T 0 disk
`-sdb1 8:17 0 20T 0 part /search
sda 8:0 0 278.5G 0 disk
|-sda1 8:1 0 200M 0 part /boot
`-sda2 8:2 0 278.3G 0 part
|-vgroot-lvroot (dm-0) 253:0 0 10G 0 lvm /
|-vgroot-lvswap (dm-1) 253:1 0 8G 0 lvm [SWAP]
|-vgroot-lvvar (dm-2) 253:2 0 15G 0 lvm /var
|-vgroot-lvusr (dm-3) 253:3 0 10G 0 lvm /usr
`-vgroot-lvopt (dm-4) 253:4 0 136.7G 0 lvm /opt通过上面命令我们可以看到,笔者的服务器上装了两块硬盘,分别是 sda(278.5G)和 sdb(20T)。接下来我们再通过 fdisk 这个命令来查看硬盘更详细的信息:
# fdisk -l /dev/sda
Disk /dev/sda: 299.0 GB, 298999349248 bytes
255 heads, 63 sectors/track, 36351 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x00053169
Device Boot Start End Blocks Id System
/dev/sda1 * 1 26 204800 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2 26 36352 291785728 8e Linux LVM可以看出 sda 这块磁盘:
- 有 255 个 heads(磁头),也就是说共有 255 个盘面。
- 36351 个 cylinders,也就是说每个盘面上都有 36351 个磁道。
- 63 sectors/track 说的是每个磁道上共有 63 个扇区。
- 逻辑扇区大小是 512 bytes。
- 上面的 Units 说的是每个磁道的存储容量大小,8225280 bytes(= 255 盘面 × 63 扇区 × 逻辑扇区大小 512 字节)。
- 那么该磁盘的总大小 = 36351 cylinders × Units(8225280 bytes) = 299 GB。
关于 fdisk 结果中的几个疑问
问题1:每一个 units 的可存储的数据都是一样的,都是 8225280 字节?
按理说,磁道是一组同心圆,越是外圈的磁道周长会越长,存储的数据应该越多才对。这个问题的答案其实应该按时间来看:
在老式的磁盘里,确实是每个磁道数据都是一样的。这样越是内圈磁道的存储密度越大。目的就是为了访问方便,通过一个 CHS 地址:柱面地址(Cylinders)、磁头地址(Heads)、扇区地址(Sectors) 直接定位到存储数据所在的扇区。但是这产生的问题就是外圈磁道的数据密度没有充分发挥出来,造成磁盘存储容量很难提升。
现代的磁盘人们改用等密度结构生产硬盘,也就是说,外圈磁道的扇区比内圈磁道多。这种磁盘里扇区是线性编号的,即从 0 到某个最大值方式排列,并连成一条线。这种寻址模式叫做 LBA,全称为 Logic Block Address(即扇区的逻辑块地址)。磁盘内部是自己会通过磁盘控制器来完成 CHS 到 LBA 的转换,进而定位到具体的物理扇区。
问题2:为什么在 fdisk 命令的结果里,存在一个 physical Sector size 是 4096 bytes?
现在新的磁盘真正的扇区也不是 512 字节,真正磁盘的 I/O size 和 physical Sector size 都是 4096 bytes。但这时存在一个问题是扇区大小为 512 字节的假设已经贯穿于整个软件链,比如 BIOS,启动加载器,操作系统内核,文件系统代码,以及磁盘工具,等等。直接切换到 4096 byte 兼容性问题太大了,所以每个新的磁盘控制器将 4096 字节的物理扇区对应成了 8 个 512 字节的逻辑扇区,兼容各种老软件。
除了 fdisk -l 命令外,如下方式也可以查看物理/逻辑扇区大小:
# cat /sys/block/sda/queue/physical_block_size
# cat /sys/block/sda/queue/logical_block_size问题3:磁头真的有 255 个?
我们先来看一张从磁盘上拆下来的磁头的真实照片。
[Image 52 on Page 7]
上面的图片里只有几个磁头,如果硬盘里真的装下 255 个这样的磁头的话,很难想象磁盘得有多厚。而且磁头多了以后硬盘的可靠性就越差,因为多磁头出故障的几率总会比单磁头要高一些。所以 fdisk -l 里看到的 255 heads 其实和扇区一样,也是虚拟出来的。另外 cylinders 也一样,也是虚拟出来的。
1.2 固态硬盘结构
前面几篇文章,主要围绕的是机械硬盘来说的。目前 SSD 目前应用也越来越广了,值得我们花精力琢磨琢磨。SSD 硬盘是地地道道电子技术下的产品,因为不像机械硬盘 IO 时依赖两个耗时的机械轴行为:磁盘旋转,以及磁道寻道,SSD 硬盘的访问延迟要比机械硬盘低的多,在随机 IO 下的表现尤其明显。我们今天从最底层出发,看看 SSD 的几个内部机理。
SSD 的组成结构
机械硬盘和 SSD 虽然都同为硬盘,但底层实现技术却完全不一样,机械硬盘使用的是磁性材料记忆,而 SSD 用的是类似 u 盘的闪存技术。实现技术的不同,必然在硬盘内部结构上他们就有天壤之别。他们的果照对比如下图所示:
[Image 56 on Page 8] [Image 57 on Page 8]
不像机械硬盘里的一摞子圆形碟片,SSD 是由一些电路和黑色的存储颗粒构成。SSD 硬盘是基于 NAND Flash 存储技术的,属于非易失性存储设备,换成人类话说,就是掉电了数据不会丢。其中我们眼睛看见的黑色的存储颗粒叫一个 NAND Package,每个 Package 里面会包含多个 Die。我们“拆开”一个 Die 来看一下:
[Image 62 on Page 9]
每个 Die 有若干个 Plane,每个 Plane 有若干个 Block,每个 Block 有若干个 Page。Page 是磁盘进行读写的最小单位,一般为 2KB / 4KB / 8KB / 16KB 等。
SSD 里的扇区
前面我们介绍机械硬盘的时候,说到由于历史原因,操作系统等软件里,512B 扇区的概念是“根深蒂固”。新的机械硬盘虽然把物理扇区已经做到 4KB 的了,但没办法,为了兼容老系统还得整出个逻辑扇区的概念来适配。到了 SSD 里也一样,虽然每一个物理 Page 的大小为 2K 到 16K 不等,但是为了兼容性,也必须得整出个逻辑扇区才行。
SSD 控制器在逻辑上会把整个磁盘再重新划分成一个个的“扇区”,采用和新机械硬盘一样的 LBA 方式来进行编址(整个磁盘的扇区从 0 到某个最大值方式排列,并连成一条线)。当需要读取某几个”扇区”上数据的时候,SSD 控制器通过访问这个 LBA Map Table,再来找到要实际访问的物理 Page,如下图:
[Image 63 on Page 9]
不过 SSD 最小的读写单位就是 Page,他是没办法只扇区来进行读写的。
最底层的闪存单元
上面我们看到 SSD 是由一个个的 Page 组成的。而在每一个 Page 里,又包含了许许多多的闪存单元。现代的闪存单元有多种类型,目前主流的主要分为 SLC、MLC 和 TLC。
在 SLC 里,一个单元的电压只分成高低两种状态,所以只能表示 1bit 数据。到了 MLC,硬是把一个单元里的电压按照高低分成了四种状态,所以可以表示 2bit。到了 TLC,直接一个单元应拆分成 8 个电压高低不同的状态,为了表示 3bit。由于 TLC 在数据读写需要八种不同电压状态,而施加不同的电压状态、就需要更精确,也就需要更长的时间才能得以实现。另外由于电压状态多,出错的可能性也会更大。所以,以上三种闪存单元对比:从性能和稳定性角度来看,SLC 最好。从容量角度看,TLC 最大。这就是为什么日常我们看到的工业级的 SSD 要比笔记本 SSD 贵很多,其中一个很重要的原因就是工业级的盘往往采用的闪存单元是 SLC 或 MLC,而我们家庭用的笔记本一般都是 TLC,因为便宜嘛。我们用表格再对它们直观对比一下:
| 闪存类型 | 单 cell 电压变化 | 单 cell 表示 bit 数 | 速度 | 擦写次数 | 价格 |
|---|---|---|---|---|---|
| SLC(Single-Level Cell) | 两种 | 1bit | 快 | 约 10 万次 | 贵 |
| MLC(Multi-Level Cell) | 四种 | 2bit | 一般 | 约 3K 次 | 一般 |
| TLC(Trinary-Level Cell) | 八种 | 3bit | 慢 | 约 1K 次 | 便宜 |
目前个人 PC 上消费用的主流闪存类型大部分都是 TLC 的,因为价格便宜,容量大。
思考
假设某 SSD 的 Page 大小是 4KB,一个文件是 16KB。那么该文件是存在一个黑色的存储颗粒里,还是多个?
[Image 67 on Page 10]
我们先把 SSD 的逻辑结构用个直观一点的图来看:
假设只写在一个颗粒里,那么对该文件进行读取的时候,就只能用到一条 Flash 通道,这样速度就会比较慢。如果存在相邻的 4 个颗粒里,每个写入 4KB。这样多个 Flash 通道的带宽会充分发挥出来,传输速度也更快。所以,实际中是分散在多个。
1.3 RAID 硬盘阵列
我们使用的计算机的全称叫电子计算机,前面有电子两个字,这说的是整个计算机中的核心元器件基本上都是电子单元组成的。但机械硬盘却是一个特殊的例外,它更多是用机械技术做出来的一个产品。当把带有机械技术基因的磁盘搭到计算机,尤其是再应用到服务器领域的时候,暴露出了机械技术的两个严重问题:
- 速度慢。如果把内存和 CPU 的速度比作汽车和飞机的话,这个大哥毫秒级别的延迟几乎就是牛车级别的。
- 容易坏。经常听说谁谁的磁盘坏了,很少有听说过谁的内存条、CPU 坏了。笔者就有在读研期间实验室里正在拷贝资料,突然一个断电直接废了一块硬盘的经历。
要想保证服务器运转的稳定和高速,就必须解决硬盘从娘胎里带出来的这两个缺陷。
多硬盘连接
问题简单明了,我们要解决速度慢、容易坏两个问题。很自然,单兵作战不行,那我们就想到往上怼一个班,多块硬盘一起上。但问题是,假如给你 N 块硬盘,让你来出一个技术方案,你会怎么设计呢?
第一个方案就是,把一个文件分成 N 片,每一片都散列在不同的硬盘上。这样当文件进行读取的时候,就可以 N 块硬盘一起来工作,从而达到读取速度提高到 N 倍的效果,这就是 RAID 0。
不过这个方案没有解决容易坏的问题,任何一块硬盘坏了都会导致存储系统故障。
第二个方案就是,仍然把文件分片,但是所有的分片都存在一块硬盘上,其它的硬盘只存拷贝。这既提高了硬盘的访问速度,也解决了坏的问题。任意一块硬盘坏了,存储系统都可以正常使用,只不过速度会打一点折扣。
不过这个方案又带来了新的问题,那就是实现成本有点高了。假如我们用 256G 硬盘想实现 512G 的存储容量的话,最少得需要 4 块硬盘才能实现。
有没有折中一点的方案呢?有,而且很多。我们这里只说下最常见的 RAID 5。
RAID 5 同样要对文件进行分片,但是不对存储的数据进行备份,而是会再单独存一个校验数据片。
[Image 71 on Page 11]
假如文件分为 A1 A2 A3,然后需要再存一个校验片到别的磁盘上。这样不管 A1,A2 还是 A3 那一片丢失了,都可以根据另外两片和校验片合成出来。既保证了数据的安全性,又只用了一块磁盘做冗余存储。
[Image 72 on Page 11]
假如我们有 8 块 256GB 的硬盘,那么 RAID5 方案下的磁盘阵列从用户角度来看可用的存储空间是 7 × 256GB,只“浪费”了一块盘的空间,所以目前 RAID5 应用比较广泛。
RAID 卡缓存
硬盘延迟是毫秒级别的,即使是多块硬盘并行,也只能提升数倍而已,不能够达到量级的提升。和 CPU 内存的纳秒级别工作频率比起来,还是太慢了。
在计算机界,没有缓存解决不了的速度问题,如果有,那就再加一层。现代磁盘本身也基本都带了缓存,另外在一些比较新的 RAID 卡里,硬件开发者们又搞出来了一层“内存”,并且还自己附带一块电池,这就是 RAID 卡缓存。我们看下几款主流 RAID 卡的配置:
| RAID 卡型号 | 描述 | 缓存 | 支持的 RAID 级别 |
|---|---|---|---|
| PERC S120 | 入门软件阵列卡,主板集成 | 无缓存 | RAID 0, 1 |
| PERC H330 | 入门硬件 RAID 卡 | 无板载缓存 | RAID 0, 1, 5, 10, 50 |
| PERC H730 | 主流硬件 RAID 卡 | 1G 缓存 + 电池 | RAID 0, 1, 5, 6, 10, 50, 60 |
| PERC H730P | 高性能硬件 RAID 卡 | 2G 缓存 + 电池 | RAID 0, 1, 5, 6, 10, 50, 60 |
| PERC H830 | 同 H730P,没有内置接口,使用外置接口连接附加存储磁盘柜用 | — | —拿目前服务器端出镜率比较高的 H730 和 H730P 来看,他们分别带了 1G 和 2G 的缓存卡,并且自带电池。 电池的作用就是当发现主机意外断电的时候,能够快速把缓存中的数据写回到磁盘中去。对于写入,一般操作系统写到这个 RAID 卡里就完事了,所以速度快。对于读取也是,只要缓存里有,就不会透传到磁盘的机械轴上。 |
另外想再补充说一点的就是文件相关函数里设置 DIRECT I/O 仅仅只能绕开操作系统本身的 Page Cache,而 RAID 卡里的缓存,对于 Linux 来说,可以说算是一个黑盒。换句话说,就是操作系统并不清楚 RAID 卡是从缓存里吐的数据,还是真正从硬盘里读的。
动手查看你的 RAID 配置
了解了 RAID 基本原理以后,我们可以实际动手查看一下机器上的 RAID 情况。这里拿我手头的一台服务器举例。
通过 cat /proc/scsi/scsi ,我们可以查看到 RAID 卡的型号。
[Image 78 on Page 12]
Host: scsi10 Channel: 02 Id: 01 Lun: 00
Vendor: DELL Model: PERC H730 Mini Rev: 4.27
Type: Direct-Access ANSI SCSI revision: 05可以看到我的这台服务器 RAID 卡使用的是 PERC H730,这块 RAID 卡带有 1G 缓存和电池。
再看我们的硬盘阵列。
[Image 86 on Page 14]
# /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL
Virtual Drive: 1 (Target Id: 1)
Name :
RAID Level : Primary-5, Secondary-0, RAID Level Qualifier-3
Size : 1.633 TB
Sector Size : 512
Is VD emulated : No
Parity Size : 278.875 GB
State : Optimal
Strip Size : 128 KB
Number Of Drives : 7
Span Depth : 1RAID Level 列标明的就是当前 RAID 组的 RAID 级别。对照下面:
- RAID 1: Primary-1, Secondary-0, RAID Level Qualifier-0 → RAID 1
- RAID 0: Primary-0, Secondary-0, RAID Level Qualifier-0 → RAID 0
- RAID 5: Primary-5, Secondary-0, RAID Level Qualifier-3 → RAID 5
- RAID 10: Primary-1, Secondary-3, RAID Level Qualifier-0 → RAID 10
可以看到,本机 RAID Level 是 RAID 5。
另外 Strip Size 叫做条带大小,我机器上的配置是 128 KB。假如有一个 512 KB 的文件,它就会被分成 4 个条带,每个 128 KB。这些条带可能会分散在不同的磁盘上,如果一次性读取的话,多个硬盘就可以一起转动机械轴,读取速度就会提高到原来的数倍。不过要说明的是,如果文件小于这个条带大小,小于 128K,那么 RAID 下的多块硬盘对于该文件的读取耗时也不会有帮助。
继续查看组成 RAID 的所有磁盘的状况:
# /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL
......
Drive's position: DiskGroup: 1, Span: 0, Arm: 0
Device Id: 1
...
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]
Sector Size: 512
Logical Sector Size: 512
Physical Sector Size: 512
Inquiry Data: SEAGATE ST300MM0008 TT31S42310JR
...
...
...
Drive's position: DiskGroup: 1, Span: 0, Arm: 6
Device Id: 7
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]看到属于 DiskGroup 1 的总共有 7 块硬盘,每块的大小都是 278 GB 左右。6 块盘的总容量大小就是 1.6 T 左右,确实是只“浪费”了一块硬盘的容量来保障数据的安全性!
另外 Inquiry Data 也显示了硬盘的制造商以及型号。这些磁盘都是希捷的 300 G 的机械硬盘,经 Google 查询其转速为 1 万转每分钟。
小结
机械技术和电子技术比较起来,稳定性要差很多。所以机械背景出身的硬盘既慢又容易坏。为了解决这个问题,RAID 技术应运而生。通过一定的冗余原始存储或者校验数据提供安全性的保障,通过增加带电子基因的缓存,合理调度多块磁盘的机械轴,提高了磁盘 IO 的读写速度。在 Linux 下可以通过 MegaCli 工具来查看你的 RAID 组情况,强烈建议你实际动手查看一下!
GitHub:https://github.com/yanfeizhang/coder-kung-fu
欢迎关注个人公众号“开发内功修炼”,了解你的每一比特,用好你的每一纳秒。
有想继续加入知识星球的同学微信扫描下面的二维码即可加入。另外在公众号后台发送「星球优惠券」可以获取开发内功修炼读者的专属优惠券。