大家好我是小义同学,这是大厂面试拆解——项目实战系列的第6篇文章,

”走暗路、耕瘦田、进窄门、见微光”   告诉我  面试关键就 深入理解自己项目 这个才是最考察基本功的地方。

知识地图:KV存储引擎—IO栈

本文 主要描述 read/write为例子普通文件 IO过程是什么?

分析这个问题关键思路在哪里?

如果您觉得阅读本文对您有帮助, 请点一下“点赞,转发” 按钮, 您的“点赞,转发” 将是我最大的写作动力!

上篇文章 新一代存储引擎BlueStore,需四步

在创建一个文件时候,Ceph的BlueStore ,

  1. 将文件的数据直接写块设备
  2. 将文件的元数据写RocksDB
  3. BlueStore IO流程 先写数据,还是先写元数据顺序不同 ,提供不同策略。
  4. Ceph IO 软件栈开销原因 无实现低于 0.5 毫秒的随机读取延迟和低于 1 毫秒的随机写入延迟

普通文件写是否区分元数据 和 数据,这些请求最后怎么写入磁盘 呢?

因为内容太多,分批梳理。

大纲如下:

(1) 预备知识:

了解目前有哪些文件系统

(2) 通过工具 了解IO栈 IO栈 (3) 性能优化

性能优化

一. 准备环境

1.1 机器配置

✅ 购买2C2G云主机 成本一年不超过100元

✅ 创建1G的大文件充代替块设备,因为没有额外的磁盘

📌 例子:用 Loop 设备模拟一个 ext4 文件系统

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 1. 创建一个 1GB 的普通文件文件

dd if=/dev/zero of=/root/temp/virtual_disk.img bs=1M count=1024

# 2. 绑定到 Loop 设备(让 Linux 认为它是一个磁盘)

losetup /dev/loop0 /root/temp/virtual_disk.img

ls -l /dev/loop0

brw-rw---- 1 root disk 7, 0 3月  24 13:51 /dev/loop0。


b 表示块设备文件,

s 表示套接字 socket 文件,

l 表示符号链接

# 3. 在 /dev/loop0 上创建 ext4 文件系统

mkfs.ext4 /dev/loop0


# 4. 挂载到 /mnt 目录

mkdir /mnt/icfs

		mount  /dev/loop0 /mnt/icfs  #/ordered
		
mount -o nodelalloc,data=writeback /dev/loop0 /mnt/icfs 


## 日记(journal)、 顺序(ordered)和 回写(writeback)
##  ext4挂载参数:delallocExt4文件系统的一个新特性——Delay Allocation 禁用

# 5. 如何查看当前的日志模式

5 1 通过dmesg命令查看Linux系统的内核日志

dmesg | grep  "mounted filesystem"
EXT4-fs (vda1): mounted filesystem with ordered data mode. Opts: (null)


5.2 现磁盘的永久挂载  开机启动 /etc/fstab file 检查

 /dev/loop0  /mnt/icfs  ext4 data=writeback 0 0

5.3  has_journal
 
dumpe2fs /dev/vda1 >1

Filesystem features:      
1 has_journal 
2 ext_attr 
3 resize_inode 
4 dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum


1. 查看IO调度器

cat /sys/block/loop0/queue/scheduler
[mq-deadline] 
kyber 

bfq :
​特性​​:
按进程分配 I/O 带宽,保障多任务公平性
完全公平队列调度器

none 
特性​​:无调度策略,直接传递请求到硬件层
适用场景​:高速存储设备(如 NVMe SSD)或已由宿主机管理 I/O 的虚拟化环境

划重点:参数含义说明

文件系统支持的三种日志模式 是什么

  • ext4挂载参数: data
Journal​​特性​ Journal Ordered(默认) Writeback
​数据记录​ 元数据+数据均记录日志 仅元数据日志 仅元数据日志
​数据写入顺序​ 严格同步 数据先于元数据写入 无强制顺序
​性能​ 最低 中等 最高
​崩溃恢复​ 完全一致 数据可能部分丢失 数据可能大量丢失
​典型场景​ 数据库、关键业务 服务器、日常办公 高性能计算
  • If data=writeback, dirty data blocks are not flushed to the disk before the metadata are written to disk through the journal.
  • 内核有专门的机制负责将页缓存中的数据异步地写入磁盘,这个过程称为写回(writeback)
延迟分配​​场景​ ​推荐选项​ ​原因​
​高吞吐量顺序写入​ delalloc 利用批量分配优化连续写入性能(如日志服务器、大数据处理)
​低延迟关键业务​ nodelalloc 避免单次写入延迟波动(如数据库事务、实时系统)

2.1 块设备IO跟踪

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 安装blktrace包
sudo yum install blktrace

# blktrace包安装后有blktrace、blkparse、btt、blkiomon这4个命令
#blktrace负责采集I/O事件数据,
# blkparse负责将每一个I/O事件数据解析为纯文本方便阅读,
## btt、blkiomon负责统计分析

# blktrace依赖debugfs,需要挂载它  
sudo mount -t debugfs none /sys/kernel/debug
## debugfs 是 Linux 内核提供的一个专用文件系统,动态创建、无需重新编译内核
验证
ls /sys/kernel/debug/
mount | grep debugfs 

debugfs on /sys/kernel/debug type debugfs (rw,relatime)

dd if=/dev/zero of=/mnt/icfs/test bs=1k count=16

blktrace -d /dev/loop0 -o - |blkparse -i -


一、字段含义与事件链分析

  1. ​字段解析​​(参考示例行:7,0 1 226 52.043115656 3815652 Q WS 1104864 +8 [jbd2/loop0-8]
    • ​7,0​​:设备号(主设备号:次设备号),表示 /dev/loop0(虚拟块设备)。
    • ​1​​:CPU 核编号(此处为 CPU 1)。
    • ​226​​:事件序列号。
    • ​52.043115656​​:时间戳(秒级精度)。
    • ​3815652​​:进程 PID(jbd2 内核线程)。
    • ​Q/WS​​:事件类型(Q=请求进入队列,WS=写同步操作)。
    • ​1104864 +8​​:起始块号 1104864,操作大小 8 个块(通常 1 块=4KB,即 32KB 写操作)。
    • ​[jbd2/loop0-8]​​:进程名,表示 jbd2 线程管理 /dev/loop0 设备的日志功能。
  2. ​事件链分析​​(关键阶段)
    • ​Q→G 阶段​​(请求生成):
      示例:Q WS 1104864 +8 → G WS 1104864 +8
      表示 I/O 请求进入块层后分配 request 结构体(耗时约微秒级)。
    • ​I 阶段​​(插入调度器队列):
      多行 I WS 事件显示请求被插入 I/O 调度器队列(如 mq-deadline)。
    • ​D 阶段​​(下发到驱动):
      D WS 表示请求被发送至设备驱动层,进入物理设备处理。
    • ​U/FN 阶段​​:
      U N 表示队列解绑操作;D FN 可能涉及屏障(Barrier)或刷新操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  
jbd2 进程负责 ext4 文件系统的日志提交操作。

echo 1 > /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
echo 1 > /sys/kernel/debug/tracing/events/jbd2/jbd2_commit_flushing/enable

     barad_agent-621691  [000] .... 16365334.481433: ext4_sync_file_enter: dev 253,1 ino 922856 parent 927477 datasync 1 

     jbd2/vda1-8-367     [000] .... 16365334.482411: jbd2_commit_flushing: dev 253,1 transaction 23610801 sync 0

     barad_agent-621691  [001] .... 16365334.485209: ext4_sync_file_enter: dev 253,1 ino 922856 parent 927477 datasync 1 

     barad_agent-621691  [001] .... 16365334.485976: ext4_sync_file_enter: dev 253,1 ino 923617 parent 927477 datasync 1 

     barad_agent-621691  [001] .... 16365334.488730: ext4_sync_file_enter: dev 253,1 ino 928166 parent 927477 datasync 1 

     jbd2/vda1-8-367     [001] .... 16365334.489345: jbd2_commit_flushing: dev 253,1 transaction 23610802 sync 0

     barad_agent-621691  [001] .... 16365334.492786: ext4_sync_file_enter: dev 253,1 ino 928166 parent 927477 datasync 1 

     barad_agent-621691  [001] .... 16365334.493294: ext4_sync_file_enter: dev 253,1 ino 923617 parent 927477 datasync 1 

     barad_agent-621691  [001] .... 16365334.494829: ext4_sync_file_enter: dev 253,1 ino 922856 parent 927477 datasync 1 

     jbd2/vda1-8-367     [000] .... 16365334.495552: jbd2_commit_flushing: dev 2


strace -c -f -e trace=file,sync,fsync,fdatasync -p 20949

jbd2 线程与 Linux 通用块层紧密协作:
从构造 BIO、

调用 `submit_bio()`、

依赖块层的合并与调度,
到接收 `bio_end_io` 回调,

形成了一个端到端的、具备高优先级保障的日志写入通道,
确保 ext4/ext3 文件系统在崩溃恢复时能够快速且可靠地回放与提交事务。

2.3从块层面 IO优化

二. IO栈全景图

image.png

为了掌握IO栈必须了解的基本知识

  • 什么是虚拟内存,与磁盘有什么关系? 这个是了解 块设备bio层基础

—个 VM 系统是如何使用主存作为缓存 它将主存看成是一个存储在磁盘上的地址空间的 高速缓存, 在主存中只保存活动区域, 并根据需要在磁盘和主存之间来回传送数据, 通过 这种方式,它高效地使用了主存

在任意时刻,虚拟页面的集合都分为三个不相交的子集: •未分配的:VM 系统还未分配(或者创建)的页。 未分配的块没有任何数据和它们相 关联,因此也就不占用任何磁盘空间。 •缓存的:当前已缓存在物理内存中的已分配页。 •未缓存的:未缓存在物理内存中的已分配页

  • 地址空间(address space)是一个非负整数地址的有序集合【地址和数据关系】

  • —个 VM 系统是如何使用主存作为缓存

  • 在虚拟内存的习惯说法中,DRAM 缓存不命中称为缺页(page fault)

  • 在磁盘和内存之间传送页的活 动叫做交换(swapping)或者页 面调度(paging)。页从磁盘换入(或者页面调入)DRAM 和从 DRAM 换出(或者页面调出)磁盘。

  • Linux kernel Block I/O Layer

三、IO性能优化采用手段

参考第一手资料

[1] Introduction to Perf

[2 ] etcd 在超大规模数据场景下的性能优化

  • 英文:https://www.cncf.io/blog/2019/05/09/performance-optimization-of-etcd-in-web-scale-data-scenario/
  • 摘要:当 etcd 存储数据量超过 40GB 后,经过一次 compact(compact 是 etcd 将不需要的历史版本数据删除的操作)后发现 put 操作的延时激增
  • etcd 存储层可以看成由两部分组成,一层在内存中的基于 btree 的索引层,一层基于 boltdb 的磁盘存储层。这里我们重点介绍底层 boltdb 层,因为和本次优化相关
  • https://www.qiyacloud.cn/2021/10/2021-10-08/
  • https://www.qiyacloud.cn/2021/10/2021-10-21/
  • 由一次 slow-request 浅谈 Ceph scrub 原理
  • https://www.infoq.cn/article/Z9m5xkLYPlp95wyFtksm
  • 可以看到rocksdb正在进行compacting,说明业务写请求比较多。 所以可确定本次slow-request的原因:大量的用户写入操作导致rocksdb进行compacting,加上deep-scrub进一步引发底层IO资源的竞争,最终导致用户请求超时

3. CRUSH 如何实现均匀分布

与一致性哈希(Consistent Hashing)的区别

特性 CRUSH 一致性哈希
拓扑感知 支持多层级故障域(机架、机房等),确保副本在不同域分布 通常只在逻辑环上分布,不关心物理拓扑

【4】 ceph

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Messenger *ms_cluster = Messenger::create(g_ceph_context, cluster_msg_type,

entity_name_t::OSD(whoami), "cluster", nonce);

AsyncMessenger::AsyncMessenger(CephContext *cct, entity_name_t name,

const std::string &type, std::string mname, uint64_t _nonce)

: SimplePolicyMessenger(cct, name),

dispatch_queue(cct, this, mname),

nonce(_nonce)

{

std::string transport_type = "posix";

if (type.find("rdma") != std::string::npos)

transport_type = "rdma";

else if (type.find("dpdk") != std::string::npos)

transport_type = "dpdk"


  1. ​Compaction优先级控制困难​

    • ​问题本质​​:RocksDB基于LSM-Tree(Log-Structured Merge Tree)设计,数据通过逐层合并(Compaction)实现有序化。但​​不同层级(L0-Lmax)的Compaction优先级无法精细控制​​,导致高优先级业务数据(如元数据)可能被低优先级数据阻塞。
    • ​典型案例​​:元数据(如Bluestore的onode)写入后频繁触发L0→L1 Compaction,而用户数据Compaction抢占资源,导致元数据访问延迟抖动。
  2. ​元数据总量增长引发性能劣化​