
1 <!-- metrics --> 2 <dependency> 3 <groupId>io.dropwizard.metrics</groupId> 4 <artifactId>metrics-core</artifactId> 5 </dependency>
這里,依托於springboot,版本號是3.1.2
一、meter類metrics
作用:統計最近1分鍾(m1),5分鍾(m5),15分鍾(m15),還有全部時間的速率(速率就是平均值)
例如:qps
線程安全:mark()方法中的四個操作都是基於CAS實現,統計線程安全。
1 package com.xxx.secondboot.metrics; 2 3 import java.util.concurrent.TimeUnit; 4 5 import com.codahale.metrics.ConsoleReporter; 6 import com.codahale.metrics.Meter; 7 import com.codahale.metrics.MetricRegistry; 8 9 /** 10 * Meter 11 * 作用:度量速率(例如,tps) 12 * Meters會統計最近1分鍾(m1),5分鍾(m5),15分鍾(m15),還有全部時間的速率(速率就是平均值)。 13 */ 14 public class TestMeter { 15 public static void main(String[] args) throws InterruptedException { 16 final MetricRegistry registry = new MetricRegistry();//其實就是一個metrics容器,因為該類的一個屬性final ConcurrentMap<String, Metric> metrics,在實際使用中做成單例就好 17 ConsoleReporter reporter = ConsoleReporter.forRegistry(registry) 18 .convertRatesTo(TimeUnit.SECONDS) 19 .convertDurationsTo(TimeUnit.MILLISECONDS) 20 .build(); 21 reporter.start(1, TimeUnit.SECONDS);//從啟動后的1s后開始(所以通常第一個計數都是不准的,從第二個開始會越來越准),每隔一秒從MetricRegistry鍾poll一次數據 22 Meter meterTps = registry.meter(MetricRegistry.name(TestMeter.class, "request", "tps"));//將該Meter類型的指定name的metric加入到MetricsRegistry中去 23 24 System.out.println("執行與業務邏輯"); 25 26 while(true){ 27 meterTps.mark();//總數以及m1,m5,m15的數據都+1 28 Thread.sleep(500); 29 } 30 } 31 }
注意:
- MetricRegistry是一個所有metrics的容器(通常設為單例)
- ConsoleReporter根據指定的打印速率(在start方法中指定)將metrics打印到console
- metrics name需要指定,這對於在statsd的統計部分以及聚合函數的選擇都有用,上邊的name()方法實際上是將類的全類名與后續的不定參數以"."拼接而成,這里metric name就是"com.xxx.secondboot.metrics.TestMeter.request.tps"
- mark方法:總數count和m1,m5,m15的數據都+1
report.start()方法源碼:
1 public void start(long period, TimeUnit unit) { 2 executor.scheduleAtFixedRate(new Runnable() { 3 @Override 4 public void run() { 5 try { 6 report(); 7 } catch (RuntimeException ex) { 8 LOG.error("RuntimeException thrown from {}#report. Exception was suppressed.", ScheduledReporter.this.getClass().getSimpleName(), ex); 9 } 10 } 11 }, period, period, unit); 12 }
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
方法含義:
- 在服務啟動的initialDelay unit(這里就是1s)后開始每隔period unit執行一次command(所以,通常第一次統計都不准確,從第二次開始變得准確)
- reporter值主動從MetricRegistry中poll數據的
- 真正的report是被synchronized塊包起來的(也就是線程安全的),而report的內部邏輯隨着report的類型不同而不同(例如,ConsoleReporter就是將四種數據打印到console)
啟動服務,輸出:(從系統時間開始輸出,該例子正好是在01s開始輸出的)
16-10-3 20:23:07 =============================================================== -- Meters ---------------------------------------------------------------------- com.xxx.secondboot.metrics.TestMeter.request.tps count = 14 mean rate = 2.00 events/second 1-minute rate = 2.00 events/second 5-minute rate = 2.00 events/second 15-minute rate = 2.00 events/second
7s內輸出14,每秒count+2,符合程序!!!
二、gauge類metrics
作用:返回一個瞬時值(就是一個具體值)
例如:某一時刻的隊列size
線程安全:只是做讀操作,線程安全
1 package com.xxx.secondboot.metrics; 2 3 import java.time.LocalDateTime; 4 import java.util.LinkedList; 5 import java.util.Queue; 6 import java.util.concurrent.TimeUnit; 7 8 import com.codahale.metrics.ConsoleReporter; 9 import com.codahale.metrics.Gauge; 10 import com.codahale.metrics.MetricRegistry; 11 12 /** 13 * Gauge 14 * 作用:只返回一個簡單值(一個瞬時值) 15 * eg:返回隊列的size 16 */ 17 public class TestGauge { 18 19 public static Queue<String> queue = new LinkedList<>();//隊列 20 21 public static void main(String[] args) { 22 MetricRegistry registry = new MetricRegistry(); 23 ConsoleReporter reporter = ConsoleReporter.forRegistry(registry) 24 .convertRatesTo(TimeUnit.SECONDS) 25 .convertDurationsTo(TimeUnit.MILLISECONDS) 26 .build(); 27 reporter.start(1, TimeUnit.SECONDS); 28 29 registry.register(MetricRegistry.name(TestGauge.class, "queue", "size"), new Gauge<Integer>() { 30 public Integer getValue() { 31 return queue.size(); 32 } 33 }); 34 35 while (true) { 36 try { 37 Thread.sleep(1000); 38 queue.add("job - " + LocalDateTime.now()); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 } 44 }
注意:
- 在registry()的時候,可以直接將一個類型的Metric直接注入到容器中,其name就是registry()的第一個參數
輸出:
16-10-3 20:57:27 ===============================================================
-- Gauges ----------------------------------------------------------------------
com.xxx.secondboot.metrics.TestGauge.queue.size
value = 1
三、counter類metrics
作用:gauge的AtomicLong實例(Counter 只是用 Gauge 封裝了 AtomicLong
),可用於加(inc())減(dec())
例如:獲得隊列長度(此處的獲取要比使用gauge通過size()方法獲取高效很多,后者size()方法的獲取大多數是O(n)),方法執行成功失敗次數(這個就是gauge無法做的)
作用:AtomicLong基於CAS,線程安全
1 package com.xxx.secondboot.metrics; 2 3 import java.util.Queue; 4 import java.util.concurrent.LinkedBlockingQueue; 5 import java.util.concurrent.TimeUnit; 6 7 import com.codahale.metrics.ConsoleReporter; 8 import com.codahale.metrics.Counter; 9 import com.codahale.metrics.MetricRegistry; 10 11 /** 12 * counter: 13 * 作用:計數器(用gauge封裝了AtomicLong) 14 */ 15 public class TestCounters { 16 public static Queue<String> queue = new LinkedBlockingQueue<>(); 17 public static Counter counter;//計算queue的大小 18 19 public static void main(String[] args) throws InterruptedException { 20 MetricRegistry registry = new MetricRegistry(); 21 ConsoleReporter reporter = ConsoleReporter.forRegistry(registry) 22 .convertRatesTo(TimeUnit.SECONDS) 23 .convertDurationsTo(TimeUnit.MILLISECONDS) 24 .build(); 25 reporter.start(1, TimeUnit.SECONDS); 26 counter = registry.counter(MetricRegistry.name(TestCounters.class, "queue", "size")); 27 28 int num = 0; 29 while (true) { 30 if (num < 10) { 31 queue.add("job - " + num); 32 counter.inc(); 33 } else if (num > 10 && num < 16) { 34 queue.poll(); 35 counter.dec(); 36 } else { 37 queue.add("job - " + num); 38 counter.inc(); 39 } 40 num++; 41 Thread.sleep(500); 42 } 43 } 44 }
輸出:
16-10-3 21:15:17 ===============================================================
-- Counters --------------------------------------------------------------------
com.xxx.secondboot.metrics.TestCounters.queue.size
count = 4
四、histogram類metrics(使用較少)
作用:計算執行次數count、最小值min,最大值max,平均值mean,方差stddev,中位數median,75百分位, 90百分位, 95百分位, 98百分位, 99百分位, 和 99.9百分位的值
例如:統計某個函數的執行耗時,以上這些值通常會是執行時間,如min是最短執行時間等
線程:update的操作需要獲取鎖,操作之后釋放鎖。線程安全。
1 package com.xxx.secondboot.metrics; 2 3 import java.util.Random; 4 import java.util.concurrent.TimeUnit; 5 6 import com.codahale.metrics.ConsoleReporter; 7 import com.codahale.metrics.ExponentiallyDecayingReservoir; 8 import com.codahale.metrics.Histogram; 9 import com.codahale.metrics.MetricRegistry; 10 11 /** 12 * Histogram 13 * 作用:計算執行次數count、最小值min,最大值max,平均值mean,方差stddev,中位數median,75百分位, 90百分位, 95百分位, 98百分位, 99百分位, 和 99.9百分位的值 14 */ 15 public class TestHistogram { 16 public static void main(String[] args) throws InterruptedException { 17 MetricRegistry registry = new MetricRegistry(); 18 ConsoleReporter reporter = ConsoleReporter.forRegistry(registry) 19 .convertRatesTo(TimeUnit.SECONDS) 20 .convertDurationsTo(TimeUnit.MILLISECONDS) 21 .build(); 22 reporter.start(1, TimeUnit.SECONDS); 23 24 Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir());//95% 25 registry.register(MetricRegistry.name(TestHistogram.class, "request","histogram"), histogram); 26 27 Random random = new Random(); 28 while(true){ 29 Thread.sleep(1000); 30 histogram.update(random.nextInt(10000)); 31 } 32 } 33 }
輸出:
1 16-10-3 21:26:05 =============================================================== 2 3 -- Histograms ------------------------------------------------------------------ 4 com.xxx.secondboot.metrics.TestHistogram.request.histogram 5 count = 3 6 min = 685 7 max = 6754 8 mean = 3149.05 9 stddev = 2584.36 10 median = 2078.00 11 75% <= 6754.00 12 95% <= 6754.00 13 98% <= 6754.00 14 99% <= 6754.00 15 99.9% <= 6754.00
五、timer類metrics
作用:meter和histogram的組合體
例如:統計某個函數的qps和執行耗時。
線程安全:meter和histogram都安全,所以也線程安全
1 package com.xxx.secondboot.metrics; 2 3 import java.util.concurrent.TimeUnit; 4 5 import com.codahale.metrics.ConsoleReporter; 6 import com.codahale.metrics.MetricRegistry; 7 import com.codahale.metrics.Timer; 8 9 /** 10 * Timers 11 * 作用:histogram和meter的組合體 12 */ 13 public class TestTimer { 14 public static void main(String[] args) throws InterruptedException { 15 MetricRegistry registry = new MetricRegistry(); 16 ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build(); 17 reporter.start(1, TimeUnit.SECONDS); 18 19 Timer timer = registry.timer(MetricRegistry.name(TestTimer.class, "get-latency")); 20 Timer.Context ctx = timer.time(); 21 22 try{ 23 Thread.sleep(2000); 24 }finally{ 25 ctx.stop(); 26 } 27 } 28 }
輸出:
1 -- Timers ---------------------------------------------------------------------- 2 com.xxx.secondboot.metrics.TestTimer.get-latency 3 count = 0 4 mean rate = 0.00 calls/second 5 1-minute rate = 0.00 calls/second 6 5-minute rate = 0.00 calls/second 7 15-minute rate = 0.00 calls/second 8 min = 0.00 milliseconds 9 max = 0.00 milliseconds 10 mean = 0.00 milliseconds 11 stddev = 0.00 milliseconds 12 median = 0.00 milliseconds 13 75% <= 0.00 milliseconds 14 95% <= 0.00 milliseconds 15 98% <= 0.00 milliseconds 16 99% <= 0.00 milliseconds 17 99.9% <= 0.00 milliseconds
總結:
- 統計某個函數被調用的頻率(TPS),使用Meters。
- 統計某個方法的耗時,使用Histograms。--注意時間是以納秒為單位的
- 既要統計某個方法的TPS又要統計其耗時時,使用Timers。--注意時間是以納秒為單位的
- counter用於計數
- gauge只用於記錄瞬時值
counter與gauge:
- 在某些時候,只能用gauge,比如說這個值是在第三方包提供的,例如guava cache的cache size(而恰好我們將該cache集成在spring cache中,通過注解來使用了),無法用哪個counter來測量
- 在某些時候,只能用counter,比如說一個方法的執行成功與失敗次數
histogram:
在統計中位數以及95%這樣的數據的時候,通常需要把所有的數據拿出來,然后進行運算(在大量的數據下該方法失效,所以采用了水庫采集法--reservoir sampling,通過維護一個小的、可管理的水庫來代表全部統計數據),具體采集法有以下幾種:
- Uniform Reservoirs:隨機選擇具有線性遞減概率的儲層的值,僅用於長時間的測量。測量統計數據最近是不是發生了變化,不要使用這個(使用下邊的指數衰減水庫)。
- Exponentially Decaying Reservoirs(指數衰減水庫):該水庫采集的數據可以代表大約最后5分鍾的全部數據。該水庫也是Times 類metrics使用histogram的默認選擇水庫。
- Sliding Window Reservoirs:代表過去n次測量的數據
- Sliding Time Window Reservoirs:嚴格的代表過去n秒內的數據(注意與指數衰減庫的區別,該方法嚴格的記錄過去的每一秒的數據(而指數衰減其實還是在最后5min進行抽樣),所以在高頻下可能需要更多內存,而且也是最慢的水庫類型)
參考:
http://metrics.dropwizard.io/3.1.0/getting-started/
http://wuchong.me/blog/2015/08/01/getting-started-with-metrics/