BFQ I/O 调度器:多租户场景下的延迟控制工程实践
引言:为什么我们需要 I/O 调度器?
在云计算时代,一台物理机运行着数十甚至上百个虚拟机或容器,它们共享同一块 NVMe 磁盘。当一个租户执行大量 I/O 操作时,其他租户的读写请求可能被饿死,导致延迟飙升。Linux 内核提供了多种 I/O 调度器来应对这一问题,其中 BFQ (Budget Fair Queueing) 以其出色的延迟公平性成为生产环境的首选。
BFQ 最早在 Linux 4.12 中被标记为可选,到 5.x 趋于成熟。它不仅能保证各进程获得公平的 I/O 带宽分配,还能在交互场景下实现接近 noop 调度器的低延迟。本文将深入 BFQ 的内部机制,探讨其在多租户场景下的工程部署方案,并提供完整的调优指南。
BFQ 的核心算法
Budget 机制:超越 CFQ 的公平性
BFQ 的核心创新在于引入了 Budget 概念。不同于 CFQ 简单地按时间片旋转分配,BFQ 为每个进程分配一个 Budget(以扇区数为单位),进程消耗完 Budget 后才让出磁盘。
┌─────────────────────────────────────────────────────────────┐
│ BFQ Scheduling Model │
│ │
│ Process A (Budget=32) ████░░░░░░░░░░░░░░░░░░░ │
│ Process B (Budget=16) ░░░░████░░░░░░░░░░░░░░░ │
│ Process C (Budget=24) ░░░░░░░░████░░░░░░░░░░░ │
│ │
│ "Budget 耗尽 → 切换 → 重新分配 Budget" │
│ 避免了时间片轮转下"IO大小不公平"的问题 │
└─────────────────────────────────────────────────────────────┘
Budget 的大小不是固定的,而是根据进程的 I/O 模式自适应调整。频繁发出小 I/O 的进程累计会有更多调度机会,而发出大 I/O 的进程每次能处理更多数据。这种机制天然适合混合工作负载。
调度层级:树形分组结构
BFQ 的调度结构分为三层:
| 层级 | 作用 | 数据结构 |
|---|---|---|
| 服务树 (Service Tree) | 管理所有活跃进程的调度实体 | 红黑树 + 时间戳排序 |
| 权重分配 | 按优先级权重计算 Budget | 线性映射 |
服务树始终选择 最小 vtime(虚拟时间)的进程进行调度,这类似于 CFS 的 vruntime 机制,保证了长期公平性。
内核实现深度解析
数据结构核心
// include/linux/bfq.h / kernel/block/bfq-*
struct bfq_entity {
struct rb_node rb_node; // 红黑树节点
u64 finish; // 虚拟完成时间
u64 start; // 调度开始时间
unsigned int weight; // 调度权重
unsigned int budget; // 当前预算 (扇区数)
// 相关联的调度实体和服务树
struct bfq_sched_data *sched_data;
struct bfq_entity *parent;
};
struct bfq_data {
struct request_queue *queue; // 关联的请求队列
struct bfq_entity *in_service; // 当前服务的实体
struct rb_root queue_tree; // 活跃实体红黑树
// 延迟控制参数
unsigned int bfq_max_budget; // 单次最大 Budget
unsigned int bfq_timeout; // 饥饿超时时间
u64 last_finish; // 上次完成时间戳
};
I/O 路径分析
BFQ 的 I/O 请求路径可以分为以下步骤:
用户 write() → VFS → page cache → bio → blk-mq → BFQ dispatch → NVMe
↓
Budget 扣减
↓
触发 Budget 耗尽检查
↓
选择下一个进程调度
关键路径上的几个决策点:
- Budget 计算:根据进程权重和已服务量重新计算
- 饥饿检测:如果进程等待时间超过
bfq_timeout,立即提升优先级 - 调度器选择:从服务树中选择最小 vtime 的实体
延迟控制机制
BFQ 通过两个核心机制控制延迟:
1. 饥饿超时 (bfq_timeout)
默认 250ms,任何等待超过此时间的请求会被标记为饥饿,BFQ 优先调度它。这保证了即使在高负载下,交互式任务的响应时间也有上界。
2. 低延迟模式 (low_latency)
当检测到交互式 I/O 模式(频繁小 I/O)时,BFQ 会自动进入 low_latency 模式,缩短调度粒度,减少尾部延迟。
工程部署实战
1. 启用 BFQ 调度器
# 查看当前调度器
cat /sys/block/nvme0n1/queue/scheduler
# 输出: [mq-deadline] none bfq
# 立即切换为 BFQ(临时)
echo bfq > /sys/block/nvme0n1/queue/scheduler
# 永久生效(通过 kernel cmdline)
# GRUB_CMDLINE_LINUX="scsi_mod.use_blk_mq=y intel_iommu=on"
# 或在 udev 规则中设置:
# /etc/udev/rules.d/60-scheduler.rules
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="bfq"
2. cgroup v2 级权重配置
BFQ 原生支持 cgroup v2 的 IO 权重控制,可以为不同租户容器分配不同的 I/O 优先级:
# 查看 cgroup IO 权重接口
cat /sys/fs/cgroup/io.weight
# 默认权重 100,范围 1-10000
# 为高优先级租户分配更多 I/O 权重
mkdir /sys/fs/cgroup/tenant-gold
echo "default 800" > /sys/fs/cgroup/tenant-gold/io.weight
# 为低优先级租户限制 I/O
mkdir /sys/fs/cgroup/tenant-budget
echo "default 50" > /sys/fs/cgroup/tenant-budget/io.weight
# 将容器进程移入对应 cgroup
echo $(pgrep -f "gold-app") > /sys/fs/cgroup/tenant-gold/cgroup.procs
echo $(pgrep -f "budget-app") > /sys/fs/cgroup/tenant-budget/cgroup.procs
3. BFQ sysctl 调优参数
# 通过 sysctl 调整 BFQ 行为(如果内核编译支持)
# 或通过 sysfs 接口
# 调低 Budget 粒度(更细粒度调度,适合延迟敏感型负载)
echo 128 > /sys/block/nvme0n1/queue/iosched/slice_idle
# 开启低延迟模式(适合桌面/交互场景)
echo 1 > /sys/block/nvme0n1/queue/iosched/low_latency
# 调整权重范围
cat /sys/block/nvme0n1/queue/iosched/weight
4. BFQ监控系统
使用 eBPF 或 BPFtrace 跟踪 BFQ 调度事件:
# BPFtrace 脚本:追踪 BFQ 调度延迟
#!/usr/bin/env bpftrace
kprobe:bfq_dispatch_request {
$bfq = (struct bfq_data *)arg0;
$budget = $bfq->entity.budget;
printf("BFQ dispatch: pid=%d budget=%d weight=%d\n",
pid, $budget, $bfq->entity.weight);
}
kprobe:bfq_remove_request {
printf("BFQ remove: pid=%d\n", pid);
}
5. 生产环境性能验证
推荐使用 fio 进行负载测试:
# 回归测试:模拟混合读写
fio --name=bfq-test \
--ioengine=io_uring \
--direct=1 \
--rw=randrw \
--rwmixread=70 \
--bsrange=4k-128k \
--numjobs=8 \
--time_based \
--runtime=60 \
--size=1G \
--group_reporting
# 测试 99th 尾延迟
fio --name=latency-test \
--ioengine=io_uring \
--direct=1 \
--rw=randread \
--bs=4k \
--numjobs=1 \
--time_based \
--runtime=30 \
--lat_percentiles=1 \
--percentile_list=99:99.9:99.99
BFQ vs 其他调度器
对比测试
| 调度器 | 平均延迟 | 99th 尾延迟 | 公平性 | 吞吐 | 适用场景 |
|---|---|---|---|---|---|
| none | 最低 | 低 | 无 | 最高 | 单租户裸盘 |
| mq-deadline | 中 | 中 | 部分 | 高 | 通用 NVMe |
| kyber | 中低 | 中 | 弱 | 高 | 高速 NVMe |
| BFQ | 中 | 中 | 强 | 中高 | 多租户/混合负载 |
选型建议
- 纯数据库节点(单租户高性能):使用
none或mq-deadline - 通用云主机(多租户混合):使用
BFQ+ cgroup v2 权重 - 实时性要求极高:使用
mq-deadline配合ioprio - 低延迟交互:开启 BFQ
low_latency模式
生产案例
案例一:虚拟化平台 I/O 隔离
某云平台在 200 核物理机上运行 50+ 虚拟机,发现某些重 I/O 租户影响到其他用户的数据库查询。
部署方案:
硬件: Dell R750, 3.2TB NVMe, Intel Xeon 4316
内核: 5.15+, BFQ scheduler
调度策略:
├── 默认 cgroup: weight=100
├── 高优租户: weight=400 (数据库)
├── 标准租户: weight=100 (Web应用)
└── 归档租户: weight=50 (备份任务)
效果:
- 高优租户 P99 读延迟从 12ms 降至 3.5ms
- 标准租户延迟稳定在 8ms 内
- 归档任务吞吐未明显下降(仍可达 400MB/s)
案例二:K8s 集群磁盘 QoS
在 K8s 环境中,通过 CSI driver 为不同 StorageClass 配置 BFQ 权重:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: premium-io
parameters:
csi.storage.k8s.io/fstype: ext4
io-scheduler: bfq
io-weight: "800"
mountOptions:
- discard
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard-io
parameters:
csi.storage.k8s.io/fstype: ext4
io-scheduler: bfq
io-weight: "100"
然后在 Pod 层面通过 cgroup 注入:
apiVersion: v1
kind: Pod
metadata:
annotations:
io.kubernetes.cri-o.io.weight: "400"
spec:
containers:
- name: app
volumeMounts:
- mountPath: /data
name: fast-storage
volumes:
- name: fast-storage
persistentVolumeClaim:
claimName: premium-pvc
进阶:与 io_uring 的协同
BFQ 与 io_uring 的结合是近年内核社区的重要方向。io_uring 提交的请求同样经过 BFQ 调度层,但 io_uring 的批量提交特性使得 BFQ 的 Budget 计算更加高效。
// 使用 io_uring 时与 BFQ 配合的关键配置
// 通过 uring_cmd 或 PBUF ring 进一步优化
struct io_uring_params params = {
.sq_thread_idle = 2000, // SQ 线程空闲超时
.flags = IORING_SETUP_SQPOLL, // 内核轮询
};
对于 io_uring 高性能场景,可以通过 IORING_SETUP_ATTACH_WQ 共享 WQ 减少上下文切换,同时 BFQ 会为 WQ 关联的进程分配优先 Budget。
总结
BFQ 是 Linux I/O 调度领域最成熟的公平调度器,其 Budget 机制在保证尾延迟可控的同时不牺牲吞吐量。在多租户云环境中,通过 cgroup v2 权重配合 sysfs 调优可以构建优质的 I/O 隔离方案。
关键要点回顾:
- BFQ Budget 机制比传统时间片轮转更适合混合 I/O 负载
- cgroup v2
io.weight是生产环境多租户隔离的核心工具 low_latency参数对交互式负载至关重要- BFQ 与 io_uring 协同工作良好,适合高性能 I/O 架构
- 对于极致低延迟单租户场景,考虑
none+ioprio或专用 SPDK
参考文档:kernel/block/bfq-*.c、Documentation/block/bfq-iosched.rst、Linux kernel v5.15+ 源码

发表评论 取消回复