服務監控 | 徹底搞懂Dropwizard Metrics一篇就夠了
Metrics是一個提供服務效能檢測工具的Java類庫,它提供了功能強大的效能指標工具庫用於度量生產環境中的各關鍵元件效能。
度量型別
Metrics提供了以下幾種基本的度量型別:
Gauge
:用於提供自定義度量。Counter
:計數器,本質是一個java.util.concurrent.atomic.LongAdder
。Histogram
:直方圖資料。Meter
:統計系統中某一事件的響應速率,如TPS、QPS。該項指標值直接反應系統當前的處理能力Timer
:計時器,是Meter
和Histogram
的結合,可以統計介面請求速率和響應時長。
Gauge
Gauge
是對一項值的瞬時度量。我們可以通過實現Gauge
例如,想要度量佇列中處於等待狀態的作業數量:
public class QueueManager { private final Queue queue; public QueueManager(MetricRegistry metrics, String name) { this.queue = new Queue(); // 通過MetricRegistry 的register方法註冊Gauge度量 metrics.register(MetricRegistry.name(QueueManager.class, name, "size"), new Gauge<Integer>() { @Override public Integer getValue() { return queue.size(); } }); } }
官方目前提供了以下幾種Gauge
實現:
Counter
Counter
是一個常規計數器,用於對某項指標值進行累加或者遞減操作。
Counter
本質是一個java.util.concurrent.atomic.LongAdder
,在多執行緒同時更新計數器的場景下,當併發量較大時,LongAdder
比AtomicLong
具有更高的吞吐量,當然空間資源消耗也更大一些。
final Counter evictions = registry.counter(name(SessionStore.class, "cache-evictions")); evictions.inc(); evictions.inc(3); evictions.dec(); evictions.dec(2);
Histograms
Histogram
反應的是資料流中的值的分佈情況。包含最小值、最大值、平均值、中位數、p75、p90、p95、p98、p99以及p999資料分佈情況。
private final Histogram responseSizes = metrics.histogram(name(RequestHandler.class, "response-sizes"));
public void handleRequest(Request request, Response response) {
// etc
responseSizes.update(response.getContent().length);
}
Histogram
計算分位數的方法是先對整個資料集進行排序,然後取排序後的資料集中特定位置的值(比如p99就是取倒序1%位置的值)。這種方式適合於小資料集或者批處理系統,不適用於要求高吞吐量、低延時的服務。
對於資料量較大,系統對吞吐量、時延要求較大的場景,我們可以採用抽樣的方式獲取資料。通過動態地抽取程式執行過程中的能夠代表系統真實執行情況的一小部分資料來實現對整個系統執行指標的近似度量,這種方法叫做蓄水池演算法(reservoir sampling)。
Metrics中提供了各式各樣的Reservoir
實現:
Meter
Meter
用於度量事件響應的平均速率,它表示的是應用程式整個執行生命週期內的總速率(總請求響應量/處理請求的總毫秒數,即每秒請求數)。
除此之外,Meter
還提供了1分鐘、5分鐘以及15分鐘的動態平均響應速率。
final Meter getRequests = registry.meter(name(WebProxy.class, "get-requests", "requests"));
getRequests.mark();
getRequests.mark(requests.size());
Timer
Timer
會度量服務的響應速率,同時也會統計服務響應時長的分佈情況。
final Timer timer = registry.timer(name(WebProxy.class, "get-requests"));
final Timer.Context context = timer.time();
try {
// handle request
} finally {
context.stop();
}
Reporters
通過上述各項度量監測服務指標後,我們可以通過Reporters報表匯出度量結果。metrics-core
模組中實現了以下幾種匯出指標的Report:
Console Reporters
定時向控制檯傳送服務的各項指標資料。
final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(1, TimeUnit.MINUTES);
CsvReporter
定時向給定目錄下的.csv
檔案追加服務各項指標資料。對於每一項指標都會在指定目錄下建立一個.csv
檔案,然後定時(本例中是1s)向每個檔案中追加指標最新資料。
final CsvReporter reporter = CsvReporter.forRegistry(registry)
.formatFor(Locale.US)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(new File("~/projects/data/"));
reporter.start(1, TimeUnit.SECONDS);
JmxReporter
將服務的各項度量指標通過JMX MBeans暴露出來,之後可以使用VisualVM檢視指標資料。生產環境不建議使用。
final JmxReporter reporter = JmxReporter.forRegistry(registry).build();
reporter.start();
Slf4jReporter
Slf4jReporter
允許我們將服務的指標資料作為日誌記錄到日誌檔案中。
final Slf4jReporter reporter = Slf4jReporter.forRegistry(registry)
.outputTo(LoggerFactory.getLogger("com.example.metrics"))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(1, TimeUnit.MINUTES);
如何使用
直接引用
直接依賴Metrics的核心庫,通過其提供的各類API完成服務指標資料度量。
- 引入Maven依賴
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version>
</dependency>
- 建立一個
MetricRegistry
物件,它是Metrics類庫的核心類,所有的應用指標都需要註冊到MetricRegistry
。
// 例項化MetricsRegistry
final MetricRegistry metrics = new MetricRegistry();
// 開啟Console Reporter
startConsoleReporter();
Meter requests = metrics.meter(name(MetricsConfig.class, "requests", "size"));
requests.mark();
void startReport() {
ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(1, TimeUnit.SECONDS);
}
整合Spring
- 引入Maven依賴
<dependency>
<groupId>com.ryantenney.metrics</groupId>
<artifactId>metrics-spring</artifactId>
<version>3.1.3</version>
</dependency>
- 通過Java註解配置Metrics。
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
@Configuration
@EnableMetrics
public class SpringConfiguringClass extends MetricsConfigurerAdapter {
@Override
public void configureReporters(MetricRegistry metricRegistry) {
// registerReporter allows the MetricsConfigurerAdapter to
// shut down the reporter when the Spring context is closed
registerReporter(ConsoleReporter
.forRegistry(metricRegistry)
.build())
.start(1, TimeUnit.MINUTES);
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DelegatingMetricsConfiguration.class})
public @interface EnableMetrics {
// 預設false表示使用JDK動態代理,設定為true時表示使用CGLIB動態代理(當使用基於類的服務暴露方式時)
boolean exposeProxy() default false;
// 設定為true時,目標物件可以通過AopContext.currentProxy()訪問封裝它的代理
boolean proxyTargetClass() default false;
}
使用限制
因為Spring AOP中,只有宣告為public
的方法可以被代理,所以@Timed
, @Metered
, @ExceptionMetered
以及 @Counted
在non-public
無法生效。
因為@Gauge
註解不涉及代理,所以它可以被應用在non-public
屬性和方法上。
public class MetricsBeanPostProcessorFactory {
private MetricsBeanPostProcessorFactory() {
}
public static AdvisingBeanPostProcessor exceptionMetered(MetricRegistry metricRegistry, ProxyConfig proxyConfig) {
return new AdvisingBeanPostProcessor(ExceptionMeteredMethodInterceptor.POINTCUT, ExceptionMeteredMethodInterceptor.adviceFactory(metricRegistry), proxyConfig);
}
public static AdvisingBeanPostProcessor metered(MetricRegistry metricRegistry, ProxyConfig proxyConfig) {
return new AdvisingBeanPostProcessor(MeteredMethodInterceptor.POINTCUT, MeteredMethodInterceptor.adviceFactory(metricRegistry), proxyConfig);
}
public static AdvisingBeanPostProcessor timed(MetricRegistry metricRegistry, ProxyConfig proxyConfig) {
return new AdvisingBeanPostProcessor(TimedMethodInterceptor.POINTCUT, TimedMethodInterceptor.adviceFactory(metricRegistry), proxyConfig);
}
public static AdvisingBeanPostProcessor counted(MetricRegistry metricRegistry, ProxyConfig proxyConfig) {
return new AdvisingBeanPostProcessor(CountedMethodInterceptor.POINTCUT, CountedMethodInterceptor.adviceFactory(metricRegistry), proxyConfig);
}
public static GaugeFieldAnnotationBeanPostProcessor gaugeField(MetricRegistry metricRegistry) {
return new GaugeFieldAnnotationBeanPostProcessor(metricRegistry);
}
public static GaugeMethodAnnotationBeanPostProcessor gaugeMethod(MetricRegistry metricRegistry) {
return new GaugeMethodAnnotationBeanPostProcessor(metricRegistry);
}
public static CachedGaugeAnnotationBeanPostProcessor cachedGauge(MetricRegistry metricRegistry) {
return new CachedGaugeAnnotationBeanPostProcessor(metricRegistry);
}
public static MetricAnnotationBeanPostProcessor metric(MetricRegistry metricRegistry) {
return new MetricAnnotationBeanPostProcessor(metricRegistry);
}
public static HealthCheckBeanPostProcessor healthCheck(HealthCheckRegistry healthRegistry) {
return new HealthCheckBeanPostProcessor(healthRegistry);
}
}
除此此外,在一個方法中呼叫處於同一個類中的另一個帶有Metrics註解的方法時,方法執行流程不會經過代理。
本文來自部落格園,作者:MindForward,轉載請註明原文連結:https://www.cnblogs.com/mindforward/p/15792132.html