目录
本文概览:metrics主要由MeterRegry、度量组件组成。通过一个应用实例介绍使用metric实现打点并保存到指标到influxdb。
1 Metrics介绍
Metrics是一个记录监控指标的度量类型,类似于Logback记录日志一样,它是面向监控的度量数据。
目前常用的有dropwizard.metrics下面的库,maven的pom.xml如下
1 2 3 4 5 6 7 8 |
<dependencies> <!-- https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-core --> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-core</artifactId> <version>3.2.2</version> </dependency> </dependencies> |
它提供了五种基本度量组件:
- Gauge 度量值,包含简单度量值、比率信息。
- Counters 计数器
- Histograms 直方图
- Meters qps计算器
- Timer 计时器
2 MetricRegistry
对于一个应用,应该只有一个MetricRegistry对象。MetricRegistry存放了这个应用的所有Metric,每一个Metric都有一个唯一名字。
分为MetricRegistry和SharedMetricRegistries两个,SharedMetricRegitries相对于MetricRegistry是线程安全的
对于一个应用一般只有一个MerticRegistry
3 度量组件
3.1 计数器Counter
1、功能
记录次数
2、demo代码
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * 测试计数器 */ public class RegistryCounterTest { /** * 测试计数器 */ public static void testCounter() { MetricRegistry registry = new MetricRegistry(); // 1.定义counter的名字 Counter counter = registry.counter("query_gmv_count_sucess_count"); // 2.增加计数 // 默认值为1 counter.inc(); // 增加指定的数 counter.inc(3); // 3.减少计数 // 3.1默认值是1 counter.dec(); // 3.2减少指定的数 counter.dec(2); // 获取值 System.out.println(counter.getCount()); } public static void main(String[] args) { RegistryCounterTest.testCounter(); } } |
执行结果为:
1 |
1 |
3.2 计时器Timer
1、功能
记录执行时间
2、demo代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package test; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; /** * Created by wuzhonghu on 17/9/21. */ public class RegistryTimerTest { /** * 测试Timer */ public static void testTimer(){ MetricRegistry registry = new MetricRegistry(); // 1.定义counter的名字 Timer timer = registry.timer("query_gmv_count_sucess_time"); // 2.打印时间 // 2.1开始计时 Timer.Context context = timer.time(); // 2.2暂停2秒,模拟执行任务 try { Thread.sleep(2000); }catch (Exception e){ } // 2.3 获取执行时间 System.out.println(context.stop()/1000000 + "mis"); } public static void main(String[] args){ RegistryTimerTest.testTimer(); } } |
执行结果为:
1 |
2001mis |
3.3 Gauge
1、功能
Guague是一个接口,只是用来返回一个度量值。如下
1 2 3 4 5 6 7 8 |
public interface Gauge<T> extends Metric { /** * Returns the metric's current value. * * @return the metric's current value */ T getValue(); } |
它包括了
- 自定义Gauage,返回一个度量值
- RationGauage,返回一个比率值。
- Cached Guages
- Derivative Guages
3.3.1 自定义
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class RegistryGaugeTest { /** *自定义 */ public static void testGauge() { MetricRegistry registry = new MetricRegistry(); Gauge gauge = registry.register("selfGuage", new Gauge<Integer>() { public Integer getValue() { return 1; } }); System.out.println(gauge.getValue()); } public static void main(String[] args){ RegistryGuageTest.testGuage(); } } |
3.3.2 Ratio Gauage
RationGuage是一个抽象类,它的作用就是通过getValue()来返回一个用来返回比值。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public abstract class RatioGauge implements Gauge<Double> { /** * 定义Ration类,,包含两个成员变量,通过两个变量做除法,得到一个比率信息。 */ public static class Ratio { public static Ratio of(double numerator, double denominator) { return new Ratio(numerator, denominator); } private final double numerator; private final double denominator; private Ratio(double numerator, double denominator) { this.numerator = numerator; this.denominator = denominator; } /** * 返回Ration的比率信息 * * @return the ratio */ public double getValue() { final double d = denominator; if (isNaN(d) || isInfinite(d) || d == 0) { return Double.NaN; } return numerator / d; } @Override public String toString() { return numerator + ":" + denominator; } } /** * Returns the {@link Ratio} which is the gauge's current value. * * @return the {@link Ratio} which is the gauge's current value */ protected abstract Ratio getRatio(); @Override public Double getValue() { return getRatio().getValue(); } } |
3.4 Meter
3.4.1 功能
统计事件发生率,如统计QPS,每一秒的请求次数。监控的数据有有:
- 总次数
- 平均吞吐量(总次数/总时间)
- 基于一分钟、五分钟和十分钟的指数加权移动平均
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public interface Metered extends Metric, Counting { /** * Returns */ long getCount(); /** * 平均值 */ double getMeanRate(); /** * 15分钟 */ double getFifteenMinuteRate(); /** * 5分钟 */ double getFiveMinuteRate(); /** * 1分钟 */ double getOneMinuteRate(); } |
2、指数加权移动平均
可以参考wiki:https://zh.wikipedia.org/wiki/%E7%A7%BB%E5%8B%95%E5%B9%B3%E5%9D%87。介绍了简单移动平均(英语:simple moving average,SMA)、加权移动平均(英语:weighted moving average,WMA)和 指数加权移动平均(英语:exponential moving average,EMA或EWMA)三种平均移动。在股市走势图常见,如下:
3.4.2 Demo代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class RegisrtyMeterTest { public static void testMeters() { MetricRegistry registry = new MetricRegistry(); Meter meter = registry.meter("request_rate"); for (int i = 1; i < 5; i++) { try { Thread.sleep(i * 1000); meter.mark(); } catch (Exception e) { } } // 返回总次数 System.out.println(meter.getCount()); // 返回平均速率=总次数/服务运行总时间。即每一秒中有多少次。 System.out.println(meter.getMeanRate()); // 返回基于15分钟指数加权移动平均 System.out.println(meter.getFifteenMinuteRate()); // 返回基于5分钟指数加权移动平均 System.out.println(meter.getFiveMinuteRate()); // 返回基于1分钟指数加权移动平均 System.out.println(meter.getOneMinuteRate()); } public static void main(String[] args) { RegisrtyMeterTest.testMeters(); } } |
执行结果为:
1 2 3 4 5 |
4 #表示总总次数 0.3991765387182781 #表示总次数/总时间=4/10 0.39889196960097933 0.3966942907643235 0.38400888292586466 |
4 应用实例
流程如下:
4.1 自定义MetricRegistry
1、pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!--metrics beg--> <!-- https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-core --> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-core</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-healthchecks</artifactId> <version>3.2.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-annotation --> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-annotation</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-servlets</artifactId> <version>3.2.2</version> </dependency> <!--metrics end--> |
2、web.xml
1 2 3 4 5 6 7 8 9 10 |
<!--监控servlet beg--> <servlet> <servlet-name>MetricsServlet</servlet-name> <servlet-class>monitorclient.MonitorServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MetricsServlet</servlet-name> <url-pattern>/monitor/metrics</url-pattern> </servlet-mapping> <!--监控servlet end--> |
3、定义监控工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class Monitor { private static MetricRegistry registry = new MetricRegistry(); /** * 成功时间后缀 */ private static String SUFFIX_SUCCESS_TIME = "_SUCCESSS_TIME"; /** * 成功次数后缀 */ private static String SUFFIX_SUCCESS_COUNT = "_SUCCESS_COUNT"; public static MetricRegistry getRegistry(){ return registry; } /** * 记录成功次数和时间 * * @param metricName 监控名字 * @param duration 毫秒 */ public static void recordOne(String metricName, long duration) { // 统计次数 Counter counter = registry.counter(metricName + SUFFIX_SUCCESS_COUNT); counter.inc(); // 统计时间 Timer timer = registry.timer(metricName + SUFFIX_SUCCESS_TIME); timer.update(duration, TimeUnit.MILLISECONDS); } } |
4、定义servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class MonitorServlet extends HttpServlet { private static final String CONTENT_TYPE = "application/json"; private transient ObjectMapper mapper = new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(CONTENT_TYPE); resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store"); final OutputStream output = resp.getOutputStream(); try { MetricRegistry registry = Monitor.getRegistry(); mapper.writer().writeValue(output, registry); } finally { output.close(); } } } |
5、http://localhost:8080/monitor/metrics
4.2 对接influxdb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
public class InfluxDbReporter extends ScheduledReporter { private static final Logger logger = LoggerFactory.getLogger(InfluxDbReporter.class); private RestTemplate restTemplate = new RestTemplate(); private static final int BATCH_SIZE = 3000; /** * url */ private String url; private String dbName; private String tableName; /** * Returns a new {@link ConsoleReporter.Builder} for {@link ConsoleReporter}. * * @param registry the registry to report * @return a {@link ConsoleReporter.Builder} instance for a {@link ConsoleReporter} */ public static InfluxDbReporter.Builder forRegistry(MetricRegistry registry) { return new InfluxDbReporter.Builder(registry); } public InfluxDbReporter(MetricRegistry registry, String url, String dbName, String tableName, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit) { super(registry, "influxdb-reporter", filter, rateUnit, durationUnit); this.url = url; this.dbName = dbName; this.tableName = tableName; } /** * 这里只处理counters * * @param gauges * @param counters * @param histograms * @param meters * @param timers */ @Override public void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) { logger.info("reporter...."); StringBuilder requset = new StringBuilder(url); // 数据库名称 requset.append("write?db=").append(dbName); if (!counters.isEmpty()) { StringBuilder data = new StringBuilder(); for (Map.Entry<String, Counter> entry : counters.entrySet()) { data.append(tableName).append(" ").append(entry.getKey()).append("=") .append(entry.getValue().getCount() + "").append("\n"); } restTemplate.postForObject(requset.toString(), data.toString(), Object.class); } } public static class Builder { private MetricRegistry registry; private MetricFilter filter; private TimeUnit rateUnit; private TimeUnit durationUnit; private String url; private String dbName; private String tableName; public Builder(MetricRegistry registry) { this.registry = registry; this.filter = MetricFilter.ALL; this.rateUnit = TimeUnit.SECONDS; this.durationUnit = TimeUnit.MILLISECONDS; } public Builder url(String url) { this.url = url; return this; } public Builder dbName(String dbName) { this.dbName = dbName; return this; } public Builder tableName(String tableName) { this.tableName = tableName; return this; } public Builder filter(MetricFilter filter) { this.filter = filter; return this; } public Builder rateUnit(TimeUnit rateUnit) { this.rateUnit = rateUnit; return this; } public Builder durationUnit(TimeUnit durationUnit) { this.durationUnit = durationUnit; return this; } public InfluxDbReporter build() { return new InfluxDbReporter(registry, url, dbName, tableName, filter, rateUnit, durationUnit); } } } |
4.3 应用influxdbReporter
定义一个bean,在初始化时,启动这个reporter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class ReporterStarter implements InitializingBean { private String url; private String dbName; private String tableName; // 默认是60秒,必须小于监控数据刷新时间 private Long timePeriod = new Long(60); /** * 启动Reporter * * @throws Exception */ public void afterPropertiesSet() throws Exception { InfluxDbReporter influxDbReporter = InfluxDbReporter.forRegistry(LMonitor.getRecordRegistry()) .url(url) // influxdb的url .dbName(dbName) // 数据库 .tableName(tableName) // 表名 .build(); influxDbReporter.start(timePeriod, TimeUnit.SECONDS); } 、、、、、、、、、、、、 getter 和 setter 方法 、、、、、、、、、、 } |
为了在初始化时加载这个bean,如下
1 2 3 4 5 6 7 8 9 10 11 12 |
@SpringBootConfiguration public class BeanConfig { @Bean public ReporterStarter reporterStarter() { ReporterStarter reporterStarter = new ReporterStarter(); reporterStarter.setDbName("licaidata"); reporterStarter.setUrl("http://xxxxx:8086"); reporterStarter.setTableName("monitor"); return reporterStarter; } } |
参考资料
1、 官网使用 http://metrics.dropwizard.io/3.1.0/manual/core/
2、http://www.jianshu.com/p/e4f70ddbc287
3、http://wuchong.me/blog/2015/08/01/getting-started-with-metrics/