Linux監(jiān)控系統(tǒng)磁盤IO的方法匯總
在現(xiàn)代分布式系統(tǒng)和高并發(fā)服務(wù)架構(gòu)中,磁盤 I/O 性能往往是系統(tǒng)瓶頸的重要來源。無論是數(shù)據(jù)庫(kù)服務(wù)器、日志收集系統(tǒng),還是緩存層與持久化存儲(chǔ)的交互,磁盤讀寫速度直接決定了整體響應(yīng)能力。特別是在 Java 應(yīng)用廣泛部署的 Linux 服務(wù)器環(huán)境中,掌握磁盤 I/O 監(jiān)控技巧是保障服務(wù)穩(wěn)定性和性能優(yōu)化的關(guān)鍵。
本文將從基礎(chǔ)命令工具開始,逐步深入到 Java 程序如何集成磁盤監(jiān)控,最終構(gòu)建一個(gè)完整的自動(dòng)化監(jiān)控體系。我們將涵蓋:
- Linux 原生命令行工具(iostat, iotop, sar)
- /proc 和 /sys 文件系統(tǒng)中的 I/O 數(shù)據(jù)
- Java 中調(diào)用系統(tǒng)命令獲取磁盤指標(biāo)
- 使用 JMX 暴露自定義磁盤監(jiān)控 MBean
- Prometheus + Grafana 構(gòu)建可視化大盤
- 告警策略與閾值配置建議
- 性能調(diào)優(yōu)實(shí)戰(zhàn)案例
無論你是 DevOps 工程師、Java 后端開發(fā)者,還是系統(tǒng)架構(gòu)師,都能從本文獲得實(shí)用技能。
為什么需要監(jiān)控磁盤 I/O?
磁盤 I/O 是系統(tǒng)中最慢的操作之一。相比內(nèi)存訪問(納秒級(jí))和 CPU 運(yùn)算(皮秒級(jí)),即使是 NVMe SSD 也需要微秒甚至毫秒級(jí)別的響應(yīng)時(shí)間。當(dāng)磁盤成為瓶頸時(shí),會(huì)導(dǎo)致:
- 請(qǐng)求堆積、線程阻塞
- GC 頻率升高(因日志刷盤慢)
- 數(shù)據(jù)庫(kù)寫入延遲飆升
- 服務(wù)超時(shí)、熔斷觸發(fā)
- 用戶體驗(yàn)下降
根據(jù) Google SRE 手冊(cè) 中的經(jīng)驗(yàn)法則:“如果你無法度量它,你就無法改進(jìn)它。” 因此,建立有效的磁盤 I/O 監(jiān)控體系,是保障系統(tǒng) SLA 的第一步。
Linux 原生監(jiān)控工具概覽
1.iostat—— 最常用的 I/O 統(tǒng)計(jì)工具
iostat 屬于 sysstat 包,提供設(shè)備級(jí)別的 I/O 統(tǒng)計(jì)信息。
# 安裝 sysstat(如未安裝) sudo apt install sysstat # Ubuntu/Debian sudo yum install sysstat # CentOS/RHEL # 查看每秒統(tǒng)計(jì),刷新間隔2秒,共5次 iostat -x 2 5
輸出示例:
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util sda 8.33 2.50 266.67 80.00 0.00 0.20 1.20 nvme0n1 15.40 12.80 1232.00 1024.00 0.00 0.00 8.90
關(guān)鍵字段解釋:
r/s,w/s:每秒讀/寫請(qǐng)求數(shù)rkB/s,wkB/s:每秒讀/寫字節(jié)數(shù)(KB)%util:設(shè)備利用率(接近 100% 表示飽和)
提示:%util 并非絕對(duì)性能指標(biāo),SSD 多隊(duì)列并發(fā)下即使 %util=100%,也可能未達(dá)性能極限。
2.iotop—— 實(shí)時(shí)進(jìn)程級(jí) I/O 監(jiān)控
類似 top,但專注于 I/O:
sudo iotop -oPa
參數(shù)說明:
-o:僅顯示有 I/O 活動(dòng)的進(jìn)程-P:僅顯示進(jìn)程(不顯示線程)-a:累積模式(顯示歷史總量)
適合定位“誰(shuí)在瘋狂讀寫磁盤”。
3.sar—— 歷史數(shù)據(jù)回溯利器
sar 可記錄歷史 I/O 數(shù)據(jù),默認(rèn)每10分鐘采樣一次。
# 查看昨天全天磁盤使用情況 sar -d -f /var/log/sysstat/sa$(date -d yesterday +%d) # 實(shí)時(shí)查看,每3秒一次 sar -d 3 5
從 /proc 和 /sys 獲取原始數(shù)據(jù)
Linux 內(nèi)核通過虛擬文件系統(tǒng)暴露大量運(yùn)行時(shí)數(shù)據(jù)。
/proc/diskstats
每一行代表一個(gè)塊設(shè)備的統(tǒng)計(jì):
cat /proc/diskstats
輸出格式(部分字段):
8 0 sda 12345 0 67890 123 4567 0 89012 456 0 579 579
字段含義(按順序):
- 主設(shè)備號(hào)
- 次設(shè)備號(hào)
- 設(shè)備名
- 讀完成次數(shù)
- 合并讀次數(shù)
- 讀扇區(qū)數(shù)(*512字節(jié))
- 讀花費(fèi)毫秒數(shù)
- 寫完成次數(shù)
- 合并寫次數(shù)
- 寫扇區(qū)數(shù)
- 寫花費(fèi)毫秒數(shù)
…
我們可以通過兩次采樣差值計(jì)算實(shí)時(shí)速率。
/sys/block/[dev]/stat
結(jié)構(gòu)與 /proc/diskstats 類似,但按設(shè)備組織:
cat /sys/block/sda/stat
Java 中監(jiān)控磁盤 I/O —— 基礎(chǔ)版
下面我們用 Java 編寫一個(gè)簡(jiǎn)單的磁盤監(jiān)控器,調(diào)用 iostat 并解析結(jié)果。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SimpleDiskMonitor {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
Process process = Runtime.getRuntime().exec("iostat -x 1 1");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
boolean startReading = false;
while ((line = reader.readLine()) != null) {
if (line.startsWith("Device")) {
startReading = true;
continue;
}
if (startReading && !line.trim().isEmpty()) {
String[] parts = line.split("\\s+");
if (parts.length >= 14) {
String device = parts[0];
double util = Double.parseDouble(parts[13]);
System.out.printf("[%s] Device: %-8s Utilization: %.2f%%%n",
java.time.LocalDateTime.now(), device, util);
}
}
}
process.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}, 0, 5, TimeUnit.SECONDS);
}
}
注意:生產(chǎn)環(huán)境慎用 Runtime.exec(),存在安全與穩(wěn)定性風(fēng)險(xiǎn)。建議改用 JNI 或 JNA 調(diào)用底層 API。
使用 Sigar 庫(kù)(已歸檔,僅作參考)
Sigar(System Information Gatherer And Reporter)曾是流行的跨平臺(tái)系統(tǒng)監(jiān)控庫(kù),現(xiàn)雖已停止維護(hù),但其設(shè)計(jì)思想仍具參考價(jià)值。
Maven 依賴(若倉(cāng)庫(kù)仍有緩存):
<dependency>
<groupId>org.fusesource</groupId>
<artifactId>sigar</artifactId>
<version>1.6.4</version>
</dependency>
示例代碼:
import org.hyperic.sigar.DiskUsage;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
public class SigarDiskMonitor {
public static void main(String[] args) throws SigarException, InterruptedException {
Sigar sigar = new Sigar();
while (true) {
String[] devices = sigar.getFileSystemList();
for (String dev : devices) {
try {
DiskUsage usage = sigar.getDiskUsage(dev);
System.out.printf("Device: %s, ReadBytes: %d, WriteBytes: %d%n",
dev, usage.getReadBytes(), usage.getWriteBytes());
} catch (SigarException e) {
// 忽略不可讀設(shè)備
}
}
Thread.sleep(3000);
}
}
}
替代方案:推薦使用 OSHI(Operating System and Hardware Information library),活躍維護(hù)中。
使用 OSHI 庫(kù)監(jiān)控磁盤(推薦)
OSHI 是當(dāng)前 Java 生態(tài)中最活躍的系統(tǒng)信息庫(kù),支持 Linux、Windows、macOS。
添加 Maven 依賴:
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.4</version>
</dependency>
完整監(jiān)控示例:
import oshi.SystemInfo;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HardwareAbstractionLayer;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class OshiDiskMonitor {
private static HWDiskStore[] previousDisks = new HWDiskStore[0];
public static void main(String[] args) {
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
HWDiskStore[] currentDisks = hal.getDiskStores();
if (previousDisks.length == 0) {
previousDisks = currentDisks.clone();
return;
}
long now = System.currentTimeMillis();
for (int i = 0; i < currentDisks.length; i++) {
HWDiskStore curr = currentDisks[i];
HWDiskStore prev = findPreviousDisk(curr.getName());
if (prev != null) {
long timeDiff = now - prev.getTimeStamp();
if (timeDiff <= 0) continue;
double readBytesPerSec = (curr.getReadBytes() - prev.getReadBytes()) * 1000.0 / timeDiff;
double writeBytesPerSec = (curr.getWriteBytes() - prev.getWriteBytes()) * 1000.0 / timeDiff;
double transfersPerSec = (curr.getReads() + curr.getWrites() - prev.getReads() - prev.getWrites()) * 1000.0 / timeDiff;
System.out.printf("[%s] %-10s | R: %7.2f KB/s | W: %7.2f KB/s | IOPS: %.1f%n",
java.time.LocalTime.now(),
curr.getName(),
readBytesPerSec / 1024,
writeBytesPerSec / 1024,
transfersPerSec);
}
}
previousDisks = currentDisks.clone();
}, 0, 2, TimeUnit.SECONDS);
}
private static HWDiskStore findPreviousDisk(String name) {
return Arrays.stream(previousDisks)
.filter(d -> d.getName().equals(name))
.findFirst()
.orElse(null);
}
}
輸出效果:
[14:23:05.123] sda | R: 128.50 KB/s | W: 64.25 KB/s | IOPS: 15.3 [14:23:07.125] sda | R: 32.10 KB/s | W: 256.80 KB/s | IOPS: 8.7 [14:23:09.127] nvme0n1 | R: 1024.00 KB/s | W: 512.00 KB/s | IOPS: 120.5
使用 JMX 暴露磁盤指標(biāo)
為了讓監(jiān)控?cái)?shù)據(jù)被外部系統(tǒng)(如 Prometheus、Zabbix)采集,我們將其封裝為 JMX MBean。
首先定義接口:
public interface DiskMonitorMBean {
double getReadBytesPerSecond(String deviceName);
double getWriteBytesPerSecond(String deviceName);
double getIops(String deviceName);
String[] getDeviceNames();
}
實(shí)現(xiàn)類:
import oshi.SystemInfo;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HardwareAbstractionLayer;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DiskMonitor extends NotificationBroadcasterSupport implements DiskMonitorMBean {
private final Map<String, HWDiskStore> previousStats = new HashMap<>();
private final HardwareAbstractionLayer hal;
private long sequenceNumber = 1;
public DiskMonitor() {
this.hal = new SystemInfo().getHardware();
startSampling();
}
private void startSampling() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::sampleDisks, 0, 2, TimeUnit.SECONDS);
}
private void sampleDisks() {
HWDiskStore[] disks = hal.getDiskStores();
long now = System.currentTimeMillis();
for (HWDiskStore disk : disks) {
HWDiskStore prev = previousStats.get(disk.getName());
if (prev != null) {
long timeDiff = now - prev.getTimeStamp();
if (timeDiff > 0) {
double rps = (disk.getReadBytes() - prev.getReadBytes()) * 1000.0 / timeDiff;
double wps = (disk.getWriteBytes() - prev.getWriteBytes()) * 1000.0 / timeDiff;
double iops = (disk.getReads() + disk.getWrites() - prev.getReads() - prev.getWrites()) * 1000.0 / timeDiff;
// 發(fā)送 JMX 通知(可選)
sendNotification(new Notification(
"disk.stats.update",
this,
sequenceNumber++,
String.format("Device %s updated: R=%.2f KB/s, W=%.2f KB/s, IOPS=%.1f",
disk.getName(), rps/1024, wps/1024, iops)));
}
}
previousStats.put(disk.getName(), disk);
}
}
@Override
public double getReadBytesPerSecond(String deviceName) {
HWDiskStore curr = getDiskByName(deviceName);
HWDiskStore prev = previousStats.get(deviceName);
if (curr == null || prev == null) return 0.0;
long timeDiff = curr.getTimeStamp() - prev.getTimeStamp();
if (timeDiff <= 0) return 0.0;
return (curr.getReadBytes() - prev.getReadBytes()) * 1000.0 / timeDiff;
}
@Override
public double getWriteBytesPerSecond(String deviceName) {
HWDiskStore curr = getDiskByName(deviceName);
HWDiskStore prev = previousStats.get(deviceName);
if (curr == null || prev == null) return 0.0;
long timeDiff = curr.getTimeStamp() - prev.getTimeStamp();
if (timeDiff <= 0) return 0.0;
return (curr.getWriteBytes() - prev.getWriteBytes()) * 1000.0 / timeDiff;
}
@Override
public double getIops(String deviceName) {
HWDiskStore curr = getDiskByName(deviceName);
HWDiskStore prev = previousStats.get(deviceName);
if (curr == null || prev == null) return 0.0;
long timeDiff = curr.getTimeStamp() - prev.getTimeStamp();
if (timeDiff <= 0) return 0.0;
return (curr.getReads() + curr.getWrites() - prev.getReads() - prev.getWrites()) * 1000.0 / timeDiff;
}
@Override
public String[] getDeviceNames() {
return hal.getDiskStores().stream()
.map(HWDiskStore::getName)
.toArray(String[]::new);
}
private HWDiskStore getDiskByName(String name) {
return Arrays.stream(hal.getDiskStores())
.filter(d -> d.getName().equals(name))
.findFirst()
.orElse(null);
}
}
注冊(cè) MBean:
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class JmxRegistration {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example.monitoring:type=DiskMonitor");
DiskMonitor monitor = new DiskMonitor();
mbs.registerMBean(monitor, name);
System.out.println("JMX Disk Monitor registered. Attach with JConsole or VisualVM.");
Thread.sleep(Long.MAX_VALUE); // keep alive
}
}
啟動(dòng)后,可用 jconsole 連接本地 JVM,查看磁盤指標(biāo)。
集成 Prometheus + Grafana
Prometheus 是當(dāng)前最流行的時(shí)序數(shù)據(jù)庫(kù),Grafana 是強(qiáng)大的可視化工具。我們將 Java 應(yīng)用暴露的 JMX 指標(biāo)通過 JMX Exporter 導(dǎo)出為 Prometheus 格式。
步驟 1:下載 jmx_exporter
從 Prometheus JMX Exporter 頁(yè)面 下載 jmx_prometheus_javaagent.jar(請(qǐng)自行搜索最新官方發(fā)布頁(yè))。
步驟 2:編寫 config.yaml
rules: - pattern: ".*"
(簡(jiǎn)單起見,導(dǎo)出所有指標(biāo);生產(chǎn)環(huán)境應(yīng)精細(xì)化配置)
步驟 3:?jiǎn)?dòng) Java 應(yīng)用時(shí)附加 Agent
java -javaagent:/path/to/jmx_prometheus_javaagent.jar=8081:/path/to/config.yaml \
-jar your-application.jar
此時(shí)訪問 http://localhost:8081/metrics 可看到類似:
# HELP com_example_monitoring_DiskMonitor_ReadBytesPerSecond
# TYPE com_example_monitoring_DiskMonitor_ReadBytesPerSecond gauge
com_example_monitoring_DiskMonitor_ReadBytesPerSecond{deviceName="sda",} 131072.0
com_example_monitoring_DiskMonitor_WriteBytesPerSecond{deviceName="sda",} 65536.0
步驟 4:配置 Prometheus 抓取
在 prometheus.yml 中添加:
scrape_configs:
- job_name: 'java-disk-monitor'
static_configs:
- targets: ['localhost:8081']
重啟 Prometheus。
步驟 5:Grafana 創(chuàng)建儀表盤
添加 Prometheus 數(shù)據(jù)源,創(chuàng)建新面板,使用 PromQL 查詢:
com_example_monitoring_DiskMonitor_ReadBytesPerSecond{deviceName="sda"} / 1024
單位設(shè)為 KB/s,即可看到實(shí)時(shí)曲線圖。
mermaid 圖表:磁盤監(jiān)控架構(gòu)流程

該架構(gòu)實(shí)現(xiàn)了從應(yīng)用層到告警通道的全鏈路監(jiān)控閉環(huán)。
告警策略與閾值建議
合理的告警能避免“狼來了”效應(yīng)。以下是針對(duì)磁盤 I/O 的典型告警規(guī)則(Prometheus AlertManager 配置):
groups:
- name: disk-alerts
rules:
- alert: HighDiskUtilization
expr: com_example_monitoring_DiskMonitor_Utilization{deviceName=~"sd.*"} > 85
for: 5m
labels:
severity: warning
annotations:
summary: "High disk utilization on {{ $labels.deviceName }}"
description: "Disk {{ $labels.deviceName }} has been over 85% utilization for 5 minutes."
- alert: DiskIOPSLimitApproaching
expr: com_example_monitoring_DiskMonitor_IOPS{deviceName="nvme0n1"} > 50000
for: 2m
labels:
severity: critical
annotations:
summary: "NVMe drive approaching IOPS limit"
description: "Device nvme0n1 is sustaining >50K IOPS, may impact latency."
- alert: ZeroDiskActivitySuspicious
expr: com_example_monitoring_DiskMonitor_ReadBytesPerSecond{deviceName="sdb"} == 0
for: 10m
labels:
severity: warning
annotations:
summary: "Disk sdb has zero activity for 10m"
description: "This may indicate failure or misconfiguration."
告警黃金法則:
- 相關(guān)性:確保告警與業(yè)務(wù)影響強(qiáng)相關(guān)
- 可操作性:收到告警后知道該做什么
- 分級(jí)制:區(qū)分 Warning / Critical
- 抑制機(jī)制:避免重復(fù)轟炸
性能調(diào)優(yōu)實(shí)戰(zhàn)案例
案例一:日志寫入導(dǎo)致 %util 飆升
現(xiàn)象:某 Java 服務(wù)每隔 5 分鐘 %util 達(dá)到 95%,GC 暫停時(shí)間增加。
診斷:
# 使用 iotop 定位進(jìn)程 sudo iotop -oPa # 發(fā)現(xiàn) Java 進(jìn)程在刷日志 Total DISK READ: 0.00 B/s | Total DISK WRITE: 45.23 M/s PID PRIO USER DISK READ DISK WRITE COMMAND 1234 be/4 appuser 0.00 B/s 45.23 M/s java -jar app.jar
解決方案:
- 將日志框架從同步改為異步(Logback AsyncAppender)
- 增加日志緩沖區(qū)大小
- 使用更快的存儲(chǔ)介質(zhì)(如 SSD 專用日志盤)
調(diào)整后 Logback 配置:
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>8192</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
</appender>
案例二:數(shù)據(jù)庫(kù)寫入延遲高
現(xiàn)象:MySQL INSERT 延遲從 2ms 升至 50ms。
排查步驟:
# 查看設(shè)備詳細(xì)統(tǒng)計(jì) iostat -xmt 1 # 輸出顯示 await 很高 Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sdb 0.00 5.00 0.00 200.00 0.00 8.00 81.92 8.00 40.00 0.00 40.00 5.00 100.00
await=40ms 表示平均 I/O 等待時(shí)間,遠(yuǎn)高于正常值(<5ms)。
根因分析:
- 磁盤隊(duì)列深度(avgqu-sz=8)過高
- 服務(wù)時(shí)間(svctm=5ms)尚可,說明是排隊(duì)導(dǎo)致延遲
優(yōu)化措施:
- 調(diào)整 I/O Scheduler:
echo deadline > /sys/block/sdb/queue/scheduler - 增大 nr_requests:
echo 1024 > /sys/block/sdb/queue/nr_requests - 應(yīng)用層批量提交事務(wù),減少小 I/O
自動(dòng)化腳本:生成每日磁盤報(bào)告
結(jié)合 Java 與 Shell,我們可以每天凌晨生成磁盤健康報(bào)告。
Java 部分(生成 JSON 數(shù)據(jù)):
import com.fasterxml.jackson.databind.ObjectMapper;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.SystemInfo;
import java.io.FileWriter;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DailyDiskReportGenerator {
public static void main(String[] args) throws Exception {
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
HWDiskStore[] disks = hal.getDiskStores();
List<DiskSummary> summaries = Arrays.stream(disks)
.map(disk -> new DiskSummary(
disk.getName(),
disk.getSize(),
disk.getReadBytes(),
disk.getWriteBytes(),
disk.getReads(),
disk.getWrites()))
.collect(Collectors.toList());
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(summaries);
String filename = String.format("/var/log/disk-report-%s.json", LocalDate.now());
try (FileWriter fw = new FileWriter(filename)) {
fw.write(json);
}
System.out.println("Report written to: " + filename);
}
static class DiskSummary {
public String name;
public long size;
public long totalReadBytes;
public long totalWriteBytes;
public long totalReads;
public long totalWrites;
public DiskSummary(String name, long size, long totalReadBytes, long totalWriteBytes, long totalReads, long totalWrites) {
this.name = name;
this.size = size;
this.totalReadBytes = totalReadBytes;
this.totalWriteBytes = totalWriteBytes;
this.totalReads = totalReads;
this.totalWrites = totalWrites;
}
}
}
Shell 腳本(/etc/cron.daily/disk-report):
#!/bin/bash # 設(shè)置 Java 環(huán)境 export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH cd /opt/disk-monitor java -cp "lib/*:target/*" DailyDiskReportGenerator # 可選:發(fā)送郵件或上傳到對(duì)象存儲(chǔ) # mail -s "Daily Disk Report" admin@company.com < /var/log/disk-report-$(date +%Y-%m-%d).json
賦予執(zhí)行權(quán)限:
sudo chmod +x /etc/cron.daily/disk-report
單元測(cè)試你的監(jiān)控邏輯
良好的監(jiān)控代碼也應(yīng)具備可測(cè)試性。
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DiskMonitorTest {
@Test
public void testCalculateReadRate() {
HWDiskStore mockPrev = Mockito.mock(HWDiskStore.class);
HWDiskStore mockCurr = Mockito.mock(HWDiskStore.class);
Mockito.when(mockPrev.getReadBytes()).thenReturn(1024L);
Mockito.when(mockCurr.getReadBytes()).thenReturn(5120L); // +4096 bytes
Mockito.when(mockPrev.getTimeStamp()).thenReturn(1000L);
Mockito.when(mockCurr.getTimeStamp()).thenReturn(3000L); // 2 seconds later
double rate = (mockCurr.getReadBytes() - mockPrev.getReadBytes()) * 1000.0 /
(mockCurr.getTimeStamp() - mockPrev.getTimeStamp());
assertTrue(Math.abs(rate - 2048.0) < 0.01); // 2048 bytes/sec
}
@Test
public void testZeroTimeDiffReturnsZero() {
HWDiskStore mockPrev = Mockito.mock(HWDiskStore.class);
HWDiskStore mockCurr = Mockito.mock(HWDiskStore.class);
Mockito.when(mockPrev.getTimeStamp()).thenReturn(1000L);
Mockito.when(mockCurr.getTimeStamp()).thenReturn(1000L);
double rate = (mockCurr.getReadBytes() - mockPrev.getReadBytes()) * 1000.0 /
(mockCurr.getTimeStamp() - mockPrev.getTimeStamp());
assertTrue(Double.isNaN(rate) || rate == 0.0);
}
}
云端環(huán)境注意事項(xiàng)
在 AWS EC2、阿里云 ECS 等云主機(jī)上,磁盤 I/O 行為略有不同:
- EBS 卷:有基準(zhǔn) IOPS 限制,突發(fā)型實(shí)例使用積分制
- 實(shí)例存儲(chǔ):高性能但臨時(shí)性,重啟即丟失
- 網(wǎng)絡(luò)延遲:云盤本質(zhì)是網(wǎng)絡(luò)存儲(chǔ),延遲高于本地 SSD
監(jiān)控建議:
- 同時(shí)監(jiān)控
CloudWatch或云廠商原生監(jiān)控 - 關(guān)注 BurstBalance(突發(fā)積分余額)
- 對(duì)比
iostat與云監(jiān)控?cái)?shù)據(jù),驗(yàn)證一致性
AWS CLI 示例:
aws cloudwatch get-metric-statistics \
--namespace AWS/EBS \
--metric-name VolumeReadBytes \
--dimensions Name=VolumeId,Value=vol-1234567890abcdef0 \
--start-time $(date -u -d '1 hour ago' '+%Y-%m-%dT%H:%M:%SZ') \
--end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
--period 300 \
--statistics Sum
未來演進(jìn)方向
隨著 eBPF、io_uring 等新技術(shù)普及,磁盤監(jiān)控也在進(jìn)化:
- eBPF:無需修改內(nèi)核即可追蹤 I/O 路徑,開銷極低
- io_uring:異步 I/O 新接口,需適配監(jiān)控工具
- OpenTelemetry:統(tǒng)一觀測(cè)性標(biāo)準(zhǔn),未來可能替代部分 JMX/Prometheus
總結(jié)
磁盤 I/O 監(jiān)控不是一次性任務(wù),而是持續(xù)優(yōu)化的過程。我們從基礎(chǔ)命令出發(fā),逐步構(gòu)建了:
- 實(shí)時(shí)監(jiān)控(iostat/iotop)
- 程序集成(Java + OSHI)
- 標(biāo)準(zhǔn)化暴露(JMX → Prometheus)
- 可視化與告警(Grafana + AlertManager)
- 自動(dòng)化報(bào)告
- 性能調(diào)優(yōu)閉環(huán)
記住幾個(gè)關(guān)鍵原則:
?? 監(jiān)控一切,但只告警重要的事
?? 指標(biāo)要可聚合、可對(duì)比、可預(yù)測(cè)
?? 與業(yè)務(wù)指標(biāo)聯(lián)動(dòng)(如“磁盤延遲上升 → 訂單處理變慢”)
?? 定期回顧告警有效性,刪除噪音規(guī)則
以上就是Linux監(jiān)控系統(tǒng)磁盤IO的方法匯總的詳細(xì)內(nèi)容,更多關(guān)于Linux監(jiān)控系統(tǒng)磁盤IO的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Centos6.x服務(wù)器配置jdk+tomcat+mysql環(huán)境(jsp+mysql)
這篇文章主要介紹了Centos6.x服務(wù)器配置jdk+tomcat+mysql環(huán)境(jsp+mysql),需要的朋友可以參考下2017-09-09
Linux網(wǎng)絡(luò)分析終極武器之Tcpdump深度指南
Tcpdump 是 Linux/Unix 系統(tǒng)上最經(jīng)典的命令行網(wǎng)絡(luò)抓包工具,用于捕獲、過濾和分析網(wǎng)絡(luò)數(shù)據(jù)包,下面小編就為大家詳細(xì)介紹一下它的具體使用2025-10-10

