|
|
磁盘 I/O 调优这件事,总被很多人简单化:换个调度器、调大队列就能快。真到线上,指标却时好时坏,抖动一地。我的体会是,调度器选择和队列参数必须结合介质类型(SATA HDD、SAS、SATA SSD、NVMe)、业务模型(顺序/随机、读多/写多、延迟/吞吐优先)和内核版本一起谈,否则就是玄学。
先说调度器。机械盘上,BFQ/CFQ 这类公平型或预算型调度器对多队列串行设备更友好,能把随机请求重排成更顺序的磁头移动,显著降低平均寻道时间。尤其是日志系统或数据库有大量小随机读写时,BFQ 往往比 noop/deadline 更稳。但到了 SSD,特别是 NVMe,多队列硬件已经在控制器里做了并行和重排,软件层面再“聪明”只会增加 CPU 开销,延长提交路径。这个场景下,none(早期叫 noop)或 mq-deadline 通常更合适:none 让请求尽快下发,mq-deadline 在拥塞时给一点简单的写饥饿保护。我的经验规则是:
- 机械盘:优先 BFQ,其次 deadline;批量顺序吞吐场景也可试 deadline。
- SATA/企业级 SSD:mq-deadline 常作为稳妥默认;极致低延迟服务尝试 none。
- NVMe:默认 none,如果发现写放大/读被写饿死,再评估 mq-deadline。
再说队列参数。多队列(blk-mq)时代,两个容易被忽视的点是 nr_requests 和硬中断亲和/队列映射。很多人盲目把 nr_requests 拉到上千,吞吐确实上来了,但延迟尾部炸裂,P99 变成业务杀手。我的做法是分型调参:延迟敏感(OLTP、在线检索)把 nr_requests 控制在较小范围(128-256),并配合应用层并发限流;吞吐优先(备份、批处理)可以提高到 512-1024,但要同时放宽合并阈值,避免过度小请求。对于 NVMe,合理的队列数通常与 CPU 物理核数接近,用 set_affinity 把提交队列与中断绑核,减少跨核抖动,很多时候比继续“加大队列”更有效。
合并策略(read_ahead、merge)也值得琢磨。顺序读多的场景适当提高 readahead(例如从默认的 128KB 调到 1-4MB)能让后端设备做更大粒度的传输;随机读多则应降低,免得徒增延迟和缓存污染。写入方面,如果文件系统和应用支持顺序写(如批量落盘日志),适当放宽合并能提升写带宽;但随机小写场景,一味追求合并会拖延提交,反而拉长尾延迟。
指标驱动是落地关键。不要只看平均值。至少同时看 iostat 里的 await、svctm、util,以及延迟分位(P95/P99),并用 fio 重现实验:分别构造 4K 随机读写、128K 顺序读写、混合 70/30 负载,覆盖你业务的典型与极端。每次只改一个变量,给系统预热与稳态时间,再记录 CPU 占用与上下文切换数。很多时候,你会发现“更快”的配置其实是把延迟账单推给了尾部。
最后提醒两个常见坑。其一,容器化环境里宿主机与容器双层限流/调度叠加,导致判断失真,调参尽量在宿主机实盘上验证。其二,文件系统与设备协同:XFS 对并发写吞吐友好,ext4 在小文件延迟更稳,ZFS 自带缓存/调度逻辑,盲改内核队列可能收益有限。调优是一套生态位匹配,选对调度器只是第一步,围绕业务形态去设定队列与亲和,配合应用并发与批量策略,才能既要马儿跑又要马儿不吃太多草。 |
|