前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Metrics:如何让线上应用更加透明?

Metrics:如何让线上应用更加透明?

作者头像
一猿小讲
发布2020-02-26 09:36:36
4730
发布2020-02-26 09:36:36
举报
文章被收录于专栏:一猿小讲一猿小讲

1

上期我们结合《SRE Google 运维解密》,对监控系统进行了一次脉络梳理,知道一旦离开了监控系统,我们就没法辨别一个服务是不是在正常提供服务,就如同线上的服务在随风裸奔。

文章分享最后,我们把 Google 十余年的监控实践,也尝试进行简单梳理,对于后期落地实践有一定参考意义。

不过,虽然对监控系统有了脉络上的了解,但是我们也知道,如果没有一套设计周全的监控指标体系,也就如同蒙着眼睛在狂奔,本期就好好说说:指标监控的类库 Metrics。

2

Metrics 是啥?简单去说,Metrics 是一款监控指标的度量类库,提供了一种功能强大的工具包,帮助开发者来完成自定义的监控工作。再通俗点,Metrics 类库是搬砖党的福音。

Metrics 的几种度量类型?在看框架源码时,时不时会看到一些 Meter、Guage、Counter、Histogram 等关键词,到底这些词说的都是啥?为了更好的熟读源码,就借助 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 类库中还有啥?

3

说了那么多 Metrics 类库的概念,也说的那么强大,不妨撸码实践,谈谈虚实。

Metrics 中基本度量类型的实践

如脑图所示,主要分两步走,先引入相关依赖,然后写代码反复进行体会。

Meter 代码实践(详细看代码呗)。

代码语言:javascript
复制
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();
    }
}

运行结果如下,体会 Meter 结果背后的概念。

Gauge 代码实践(详细看代码呗)。

代码语言:javascript
复制
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);
    }
}

运行结果如下,体会 Gauge 结果背后的概念。

Counter 代码实践(详细看代码呗)。

代码语言:javascript
复制
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();
    }
}

运行结果如下,体会 Counter 结果背后的概念。

Histogram 代码实践(详细看代码呗)。

代码语言:javascript
复制
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();
    }
}

运行结果如下,体会 Histogram 结果背后的概念。

Timer 代码实践(详细看代码呗)。

代码语言:javascript
复制
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();
    }
}

运行结果如下,体会 Timer 结果背后的概念。

Metrics Reporter 代码实践

Metrics 提供了 Reporter 接口来展示获取到的指标数据,可以通过 JMX、Console、CSV、SLF4J、HTTP、Graphite 等方式来报告展示指标值。

本次以 JMXReporter 为例进行代码实践体验。

代码语言:javascript
复制
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();
    }
}

代码运行成功后,在控制台输入 jconsole,效果如下。

Metrics-healthchecks 代码实践

Metrics 提供了 metrics-healthchecks 模块,可以对运行服务进行健康检查。

代码语言:javascript
复制
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, HealthCheck.Result> results = healthChecks.runHealthChecks();
        for (Map.Entry<String, HealthCheck.Result> 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";
    }
}

运行程序,控制台输出如下。

4

Metrics 类库分享就到这里,希望你能有所收获。

鉴于线上跑的每一个应用,都需要配备一套监控系统,如果能借用 Metrics 类库简单实现监控,何乐而不为呢?

鉴于开源的监控轮子与日俱增,我们在设计相关监控系统的时候,如果能提前了解规范,并按照其规范设计,那么与开源轮子将会无缝对接。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一猿小讲 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档