最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux監(jiān)控系統(tǒng)磁盤IO的方法匯總

 更新時(shí)間:2026年03月10日 09:36:54   作者:Jinkxs  
在現(xiàn)代分布式系統(tǒng)和高并發(fā)服務(wù)架構(gòu)中,磁盤 I/O 性能往往是系統(tǒng)瓶頸的重要來源,本文將從基礎(chǔ)命令工具開始,逐步深入到 Java 程序如何集成磁盤監(jiān)控,最終構(gòu)建一個(gè)完整的自動(dòng)化監(jiān)控體系,需要的朋友可以參考下

在現(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

字段含義(按順序):

  1. 主設(shè)備號(hào)
  2. 次設(shè)備號(hào)
  3. 設(shè)備名
  4. 讀完成次數(shù)
  5. 合并讀次數(shù)
  6. 讀扇區(qū)數(shù)(*512字節(jié))
  7. 讀花費(fèi)毫秒數(shù)
  8. 寫完成次數(shù)
  9. 合并寫次數(shù)
  10. 寫扇區(qū)數(shù)
  11. 寫花費(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

解決方案

  1. 將日志框架從同步改為異步(Logback AsyncAppender)
  2. 增加日志緩沖區(qū)大小
  3. 使用更快的存儲(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)化措施

  1. 調(diào)整 I/O Scheduler:echo deadline > /sys/block/sdb/queue/scheduler
  2. 增大 nr_requests:echo 1024 > /sys/block/sdb/queue/nr_requests
  3. 應(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)建了:

  1. 實(shí)時(shí)監(jiān)控(iostat/iotop)
  2. 程序集成(Java + OSHI)
  3. 標(biāo)準(zhǔn)化暴露(JMX → Prometheus)
  4. 可視化與告警(Grafana + AlertManager)
  5. 自動(dòng)化報(bào)告
  6. 性能調(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)文章

最新評(píng)論

烟台市| 孝义市| 平顶山市| 石楼县| 沙河市| 清水县| 五莲县| 盐津县| 彩票| 瓮安县| 屏山县| 海原县| 莱芜市| 陇西县| 鹤山市| 江华| 河东区| 广平县| 灌阳县| 翁牛特旗| 石狮市| 平邑县| 永善县| 浮山县| 兴海县| 如东县| 樟树市| 沙田区| 长治市| 巴里| 扎囊县| 县级市| 藁城市| 哈巴河县| 上饶市| 稷山县| 教育| 兰州市| 略阳县| 吴川市| 灵石县|