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 代码实践(详细看代码呗)。

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 代码实践(详细看代码呗)。

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 代码实践(详细看代码呗)。

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 代码实践(详细看代码呗)。

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 代码实践(详细看代码呗)。

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 为例进行代码实践体验。

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 模块,可以对运行服务进行健康检查。

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 类库简单实现监控,何乐而不为呢?

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

好了,本次的分享就到这里,希望你们能够喜欢。下期我们将钻到框架源码里,去透彻分析 Metrics 的应用与展示,敬请期待。

Metrics:如何让线上应用更加透明?的更多相关文章

  1. BTrace : Java 线上问题排查神器

    BTrace 是什么 BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务.写好脚本,直接用命 ...

  2. BTrace:线上问题排查工具

    BTrace简介 GitHub地址:BTrace 下载地址:v1.3.11.3 官方使用教程:Btrace使用教程 使用场景 BTrace 是一个事后工具,所谓事后工具就是在服务已经上线了,但是发现存 ...

  3. CAS (15) — CAS 线上环境 Ehcache Replication 的非稳定重现错误 java.util.ConcurrentModificationException

    CAS (15) - CAS 线上环境 Ehcache Replication 的非稳定重现错误 摘要 线上环境在 EhCache Replication 过程中出现 java.util.Concur ...

  4. DBAplus社群线上分享----Sharding-Sphere之Proxy初探

    功能 Cobar Mycat Heisenberg Shark TDDL Sharding-JDBC 是否开源 开源 开源 开源 开源 部分开源 开源 架构模型 Proxy架构 Proxy架构 Pro ...

  5. 今天,你遇到redis线上连接超时了吗?

    一封报警邮件,大量服务节点 redis 响应超时. 又来,好烦. redis 响应变慢,查看日志,发现大量 TimeoutException. 大量TimeoutException,说明当前redis ...

  6. 数据库char varchar nchar nvarchar,编码Unicode,UTF8,GBK等,Sql语句中文前为什么加N(一次线上数据存储乱码排查)

    背景 公司有一个数据处理线,上面的数据经过不同环境处理,然后上线到正式库.其中一个环节需要将数据进行处理然后导入到另外一个库(Sql Server).这个处理的程序是老大用python写的,处理完后进 ...

  7. 直播预告 | 猪齿鱼V1.1发布,线上新功能详解邀您参加

    2021年11月11日,数智化效能平台猪齿鱼 Choerodon发布 V1.1版本,多项功能新增或优化,多管齐下,全面提升团队工作效能! 通过提供体系化方法论和协作.测试.DevOps及容器工具,猪齿 ...

  8. Springcloud及Git线上配置详解

    SpringCloud 这个阶段该如何学? 三层架构 + MVC 框架: Spring IOC AOP SpringBoot,新一代的JavaEE开发标准,自动装配 模块化~ all in one,代 ...

  9. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

随机推荐

  1. Yuchuan_Linux_C 编程之三 静态库的制作和使用

    一.整体大纲 二.静态库的制作 1)命名规则        lib + 库的名字 + .a        例如:libyuchuan.a2)制作步骤:        1). 生成对应的.o文件 -- ...

  2. Promise,Generator,Await/Async

    上节中忘记讲:Iterator接口和Generator函数的关系了,Symbol.iterator方法的最简单的实现就是通过Generator函数: let myIterable = { [Symbo ...

  3. 面试被问分布式事务(2PC、3PC、TCC),这样解释没毛病!

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...

  4. win7下firefox和chrome升级到最新版之后页面打不开的解决办法

    一.升级firefox到最新版后,页面崩溃,打开是空白页,连选项设置都打不开了. 最开始是我的firefox很久没升级,最近要要开始做开发,于是最让它自动升级.等升级到最新版本后,打开浏览器是结果显示 ...

  5. 爬取疫情数据,以django+pyecharts实现数据可视化web网页

    在家呆着也是呆着,不如做点什么消磨时间呗~ 试试用django+pyecharts实现疫情数据可视化web页面 这里要爬疫情数据 来自丁香园.搜狗及百度的疫情实时动态展示页 先看看劳动成果: 导航栏: ...

  6. 【原创】(五)Linux进程调度-CFS调度器

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  7. javascript的垃圾回收机制与内存管理

    一.垃圾回收机制—GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...

  8. Java 14 发布了,可以扔掉Lombok了?

    2020年3月17日发布,Java正式发布了JDK 14 ,目前已经可以开放下载.在JDK 14中,共有16个新特性,本文主要来介绍其中的一个特性:JEP 359: Records 官方吐槽最为致命 ...

  9. ILspy不能查看VS代码问题

    1.问题情况 刚接触IL,发现从网上下载的IL无法反编译 dll和exe文件都不行 出现如下提示: System.OutOfMemoryException: 数组维度超过了支持的范围. 2.解决方法 ...

  10. 洛谷P5661 公交换乘(CSP-J 2019 T2)

    传送门 题目可能排版有问题,导致出现一些乱码,具体请参考洛谷原题 题目描述 著名旅游城市 B 市为了鼓励大家采用公共交通方式出行,推出了一种地铁换乘公交车的优惠方案: 在搭乘一次地铁后可以获得一张优惠 ...