Metrics:如何讓線上應用更透明?

1

上期我們結合《SRE Google 運維解密》,對監控系統進行了一次脈絡梳理,知道一旦離開了監控系統,我們就沒法辨別一個服務是不是在正常提供服務,就如同線上的服務在隨風裸奔。


文章分享最後,我們把 Google 十餘年的監控實踐,也嘗試進行簡單梳理,對於後期落地實踐有一定參考意義。

Metrics:如何讓線上應用更透明?

不過,雖然對監控系統有了脈絡上的瞭解,但是我們也知道,如果沒有一套設計周全的監控指標體系,也就如同蒙著眼睛在狂奔,本期就好好說說:指標監控的類庫 Metrics。

Metrics:如何讓線上應用更透明?

2

Metrics:如何讓線上應用更透明?

Metrics 是啥?簡單去說,Metrics 是一款監控指標的度量類庫,提供了一種功能強大的工具包,幫助開發者來完成自定義的監控工作。再通俗點,Metrics 類庫是搬磚黨的福音。


Metrics 的幾種度量類型

?在看框架源碼時,時不時會看到一些 Meter、Guage、Counter、Histogram 等關鍵詞,到底這些詞說的都是啥?為了更好的熟讀源碼,就藉助 Metrics 定義的幾種度量類型,逐個進行解密。

Metrics:如何讓線上應用更透明?

Meter 主要用於統計系統中某一個事件的速率,可以反應系統當前的處理能力,幫助我們判斷資源是否已經不足。可以很方便幫助我們統計,每秒請求數(TPS)、每秒查詢數(QPS)、最近 1 分鐘平均每秒請求數、最近 5 分鐘平均每秒請求數、最近 15 分鐘平均每秒請求數等。


Guage 是最簡單的度量指標,只有一個簡單的返回值,通常用來記錄一些對象或者事物的瞬時值。通過 Gauge 可以完成自定義的度量類型,可以用於衡量一個待處理隊列中任務的個數,以及目前內存使用量等等場景。


Counter 是累計型的度量指標,內部用 Gauge 封裝了 AtomicLong。主要用它來統計隊列中 Job 的總數;錯誤出現次數;服務請求數等等場景。


Histogram 是統計數據的分佈情況的度量指標,提供了最小值,最大值,中間值,還有中位數,75 百分位,90 百分位,95 百分位,98 百分位,99 百分位,和 99.9 百分位的值。使用的場景,例如統計流量最大值、最小值、平均值、中位值等等。


Timer

本質是 Histogram 和 Meter 的結合,可以很方便的統計請求的速率和處理時間,例如磁盤讀延遲統計,以及接口調用的延遲等信息的統計等等場景。


Metrics 類庫中還有啥?

Metrics:如何讓線上應用更透明?

3

說了那麼多 Metrics 類庫的概念,也說的那麼強大,不妨擼碼實踐,談談虛實。

3.1 Metrics 中基本度量類型的實踐

Metrics:如何讓線上應用更透明?

如腦圖所示,主要分兩步走,先引入相關依賴,然後寫代碼反覆進行體會。


Meter 代碼實踐(詳細看代碼唄)。

<code>import com.codahale.metrics.ConsoleReporter;import com.codahale.metrics.Meter;import com.codahale.metrics.MetricRegistry;import java.util.concurrent.TimeUnit;/** * Meters(TPS 計算器) * 示例: * 例如:每秒請求數(TPS) * 例如:最近 1 分鐘平均每秒請求數 * 例如:最近 5 分鐘平均每秒請求數 * 例如:最近 15分鐘平均每秒請求數 * * @author 一猿小講 */public class MeterApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    private final MetricRegistry metrics = new MetricRegistry();    /**     * Meters 本身是一個自增計數器,統計系統中某一個事件的速率     */    private final Meter requests = metrics.meter("requests");    /**     * 處理請求     */    public void handleRequest() {        requests.mark();        // etc        System.out.println("處理請求handleRequest");    }    /**     * 啟動指標報告     * (採用控制檯輸出的形式)     */    public void startReport() {        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();        reporter.start(1, TimeUnit.SECONDS);    }    /**     * 等待 2 分鐘     */    static void wait120Seconds() {        try {            Thread.sleep(120 * 1000);        } catch (InterruptedException e) {        }    }    /**     * 程序入口     *     * @param args     */    public static void main(String[] args) {        MeterApp meterApp = new MeterApp();        // 啟動監控指標報告展示        meterApp.startReport();        // 處理 20 筆請求,觀察指標        for (int i = 0; i < 20; i++) {            meterApp.handleRequest();        }        // 等待 120 秒        wait120Seconds();    }}/<code> 

運行結果如下,體會 Meter 結果背後的概念。

Metrics:如何讓線上應用更透明?

Gauge 代碼實踐(詳細看代碼唄)。

<code>import com.codahale.metrics.ConsoleReporter;import com.codahale.metrics.Gauge;import com.codahale.metrics.MetricRegistry;import java.util.Queue;import java.util.Random;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;/** * Gauges 最簡單的度量指標 * 示例:衡量一個待處理隊列中任務的個數; * * @author 一猿小講 */public class GaugeApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    private final MetricRegistry metrics = new MetricRegistry();    /**     * 任務隊列     */    private static final Queue jobQueue = new LinkedBlockingQueue();    /**     * 處理     */    public void handle() {        // 向 mertics 註冊 Gauge 指標監控        metrics.register(MetricRegistry.name(GaugeApp.class, "jobQueue", "size"),                new Gauge<integer>() {                    public Integer getValue() {                        return jobQueue.size();                    }                });        // 模擬向隊列中放入任務        while (true) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            jobQueue.add(new Random().nextInt(10) + "-Job");        }    }    public static void main(String[] args) {        GaugeApp gaugeApp = new GaugeApp();        // 啟動監控指標報告展示        gaugeApp.startReport();        // 註冊Gauge指標監控,並模擬添加任務到隊列        gaugeApp.handle();    }    /**     * 啟動指標報告     * (採用控制檯輸出的形式)     */    void startReport() {        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();        reporter.start(1, TimeUnit.SECONDS);    }}/<integer>/<code> 

運行結果如下,體會 Gauge 結果背後的概念。

Metrics:如何讓線上應用更透明?

Counter 代碼實踐(詳細看代碼唄)。

<code>import com.codahale.metrics.ConsoleReporter;import com.codahale.metrics.Counter;import com.codahale.metrics.MetricRegistry;import java.util.Queue;import java.util.Random;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;/** * Counters 累計型的度量指標 * 示例:統計一個待處理隊列中任務的個數; * * @author 一猿小講 */public class CounterApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    private final MetricRegistry metrics = new MetricRegistry();    /**     * 任務隊列     */    private static final Queue<string> jobQueue = new LinkedBlockingQueue<string>();    /**     * 累計型的度量指標     */    private final Counter pendingJobs = metrics.counter("pending-jobs.size");    /**     * 向隊列中添加任務     *     * @param job     */    public void addJob(String job) {        pendingJobs.inc();        jobQueue.offer(job);    }    /**     * 從隊列中取出任務     *     * @return     */    public String takeJob() {        pendingJobs.dec();        return jobQueue.poll();    }    /**     * 處理     */    public void handle() {        Random random = new Random();        // 模擬向隊列中放入任務        while (true) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            String jobId;            if (random.nextInt(10) > 8) {                jobId = takeJob();                System.out.println(String.format("取出的任務ID為%s", jobId));            } else {                jobId = random.nextInt(100) + "-Job";                addJob(jobId);                System.out.println(String.format("向隊列中加入任務,ID為%s", jobId));            }        }    }    /**     * 啟動指標報告     * (採用控制檯輸出的形式)     */    void startReport() {        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();        reporter.start(1, TimeUnit.SECONDS);    }    /**     * 程序入口     * @param args     */    public static void main(String[] args) {        CounterApp counterApp = new CounterApp();        // 啟動監控指標報告展示        counterApp.startReport();        // 並模擬生產/消費任務到隊列        counterApp.handle();    }}/<string>/<string>/<code> 

運行結果如下,體會 Counter 結果背後的概念。

Metrics:如何讓線上應用更透明?

Histogram 代碼實踐(詳細看代碼唄)。

<code>import com.codahale.metrics.ConsoleReporter;import com.codahale.metrics.Histogram;import com.codahale.metrics.MetricRegistry;import java.util.Random;import java.util.concurrent.TimeUnit;/** * Histogram 統計數據的分佈情況 * 示例: 響應字節的最大值、最小值、平均值、中位值。 * * @author 一猿小講 */public class HistogramApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    private final MetricRegistry metrics = new MetricRegistry();    /**     * Histogram 統計數據的分佈情況,向 metrics 註冊並獲取 Histogram 監控     */    private final Histogram responseSizes = metrics.histogram("response-sizes");    /**     * 處理請求     */    public void handle() {        while (true) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            // etc            responseSizes.update(new Random().nextInt(100));        }    }    /**     * 啟動指標報告     * (採用控制檯輸出的形式)     */    public void startReport() {        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();        reporter.start(1, TimeUnit.SECONDS);    }    /**     * 程序入口     *     * @param args     */    public static void main(String[] args) {        HistogramApp histogramApp = new HistogramApp();        // 啟動監控指標報告展示        histogramApp.startReport();        // 處理請求,觀察指標        histogramApp.handle();    }}/<code> 

運行結果如下,體會 Histogram 結果背後的概念。

Metrics:如何讓線上應用更透明?

Timer 代碼實踐(詳細看代碼唄)。

<code>import com.codahale.metrics.ConsoleReporter;import com.codahale.metrics.MetricRegistry;import com.codahale.metrics.Timer;import java.util.Random;import java.util.concurrent.TimeUnit;/** * Timer 是 Histogram 和 Meter 的結合,可以比較方便地統計請求的速率和處理時間。 * 應用場景: * 例如:磁盤讀延遲統計; * 例如:接口調用的延遲等信息的統計。 * * @author 一猿小講 */public class TimerApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    private final MetricRegistry metrics = new MetricRegistry();    /**     * 向 metrics 註冊並獲取 Timer 監控     */    private final Timer responses = metrics.timer("responses");    /**     * 處理請求     */    public void handle() {        Timer.Context context;        Random random = new Random();        while (true) {            context = responses.time();            // 業務邏輯處理 etc            try {                Thread.sleep(random.nextInt(1000));            } catch (InterruptedException e) {                e.printStackTrace();            }            context.stop();        }    }    /**     * 啟動指標報告     * (採用控制檯輸出的形式)     */    void startReport() {        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();        reporter.start(1, TimeUnit.SECONDS);    }    /**     * 等待 2 分鐘     */    static void wait120Seconds() {        try {            Thread.sleep(120 * 1000);        } catch (InterruptedException e) {        }    }    /**     * 程序入口     *     * @param args     */    public static void main(String[] args) {        TimerApp timerApp = new TimerApp();        // 啟動監控指標報告展示        timerApp.startReport();        // 處理請求,觀察指標        timerApp.handle();        // 等它 2 分鐘        wait120Seconds();    }}/<code> 

運行結果如下,體會 Timer 結果背後的概念。

Metrics:如何讓線上應用更透明?

3.2 Metrics Reporter 代碼實踐

Metrics:如何讓線上應用更透明?

Metrics 提供了 Reporter 接口來展示獲取到的指標數據,可以通過 JMX、Console、CSV、SLF4J、HTTP、Graphite 等方式來報告展示指標值。


本次以 JMXReporter 為例,進行代碼實踐體驗。

<code>import com.codahale.metrics.Meter;import com.codahale.metrics.MetricRegistry;import com.codahale.metrics.jmx.JmxReporter;/** * JMXReporter 體驗 * * @author 一猿小講 */public class JMXReporterApp {    /**     * MetricRegistry 是 Metrics 的核心,用於存放應用中所有 metrics 的容器     * 所有度量工具都要註冊到 MetricRegistry 實例中才可以使用     */    static final MetricRegistry metrics = new MetricRegistry();    /**     * 啟動 JMXReporter     */    static void startReport() {        JmxReporter reporter = JmxReporter.forRegistry(metrics).build();        reporter.start();    }    /**     * 等待 2 分鐘     */    static void wait120Seconds() {        try {            Thread.sleep(120 * 1000);        } catch (InterruptedException e) {        }    }    /**     * 程序入口     *     * @param args     */    public static void main(String[] args) {        // 啟動監控指標報告展示        startReport();        // Meters(TPS 計算器)        Meter requests = metrics.meter("requests");        requests.mark();        // 等 2 分鐘        wait120Seconds();    }}/<code>

代碼運行成功後,在控制檯輸入 jconsole,效果如下。

Metrics:如何讓線上應用更透明?

3.3 Metrics-healthchecks 代碼實踐

Metrics:如何讓線上應用更透明?

Metrics 提供了 metrics-healthchecks 模塊,可以對運行服務進行健康檢查。

<code>import com.codahale.metrics.health.HealthCheck;import com.codahale.metrics.health.HealthCheckRegistry;import java.util.Map;/** * 應用健康檢查初體驗 * * @author 一猿小講 */public class HealthCheckApp {    public static void main(String[] args) {        HealthCheckRegistry healthChecks = new HealthCheckRegistry();        healthChecks.register("MySQL", new DatabaseHealthCheck(new Database()));        final Map<string> results = healthChecks.runHealthChecks();        for (Map.Entry<string> entry : results.entrySet()) {            if (entry.getValue().isHealthy()) {                System.out.println(entry.getKey() + " is healthy");            } else {                System.err.println(entry.getKey() + " is UNHEALTHY: " + entry.getValue().getMessage());                final Throwable e = entry.getValue().getError();                if (e != null) {                    e.printStackTrace();                }            }        }    }}class DatabaseHealthCheck extends HealthCheck {    private final Database database;    public DatabaseHealthCheck(Database database) {        this.database = database;    }    @Override    public HealthCheck.Result check() {        if (database.isConnected()) {            return HealthCheck.Result.healthy();        } else {            return HealthCheck.Result.unhealthy("Cannot connect to " + database.getUrl());        }    }}class Database {    public boolean isConnected() {        return false;    }    public String getUrl() {        return "jdbc:localhost:3306";    }}/<string>/<string>/<code>

運行程序,控制檯輸出如下。

Metrics:如何讓線上應用更透明?

4

Metrics 類庫分享就到這裡,希望你能有所收穫。


鑑於線上跑的每一個應用,都需要配備一套監控系統,如果能借用 Metrics 類庫簡單實現監控,何樂而不為呢?


鑑於開源的監控輪子與日俱增,我們在設計相關監控系統的時候,如果能提前瞭解規範,並按照其規範設計,那麼與開源輪子將會無縫對接。


好了,本次的分享就到這裡,希望你們能夠喜歡。下期我們將鑽到框架源碼裡,去透徹分析 Metrics 的應用與展示,敬請期待。


分享到:


相關文章: