# 前提
spring-actuator做度量統(tǒng)計收集,使用Prometheus(普羅米修斯)進行數(shù)據(jù)收集,Grafana(增強ui)進行數(shù)據(jù)展示鹏秋,用于監(jiān)控生成環(huán)境機器的性能指標(biāo)和業(yè)務(wù)數(shù)據(jù)指標(biāo)尊蚁。一般,我們叫這樣的操作為”埋點”侣夷。SpringBoot中的依賴spring-actuator中集成的度量統(tǒng)計API使用的框架是Micrometer横朋,官網(wǎng)是Micrometer.io。
在實踐中發(fā)現(xiàn)了業(yè)務(wù)開發(fā)者濫用了Micrometer的度量類型Counter百拓,導(dǎo)致無論什么情況下都只使用計數(shù)統(tǒng)計的功能琴锭。這篇文章就是基于Micrometer分析其他的度量類型API的作用和適用場景。
# Micrometer提供的度量類庫
Meter是指一組用于收集應(yīng)用中的度量數(shù)據(jù)的接口衙传,Meter單詞可以翻譯為”米”或者”千分尺”决帖,但是顯然聽起來都不是很合理,因此下文直接叫Meter蓖捶,理解它為度量接口即可地回。Meter是由MeterRegistry創(chuàng)建和保存的,可以理解MeterRegistry是Meter的工廠和緩存中心腺阳,一般而言每個JVM應(yīng)用在使用Micrometer的時候必須創(chuàng)建一個MeterRegistry的具體實現(xiàn)落君。
Micrometer中穿香,Meter的具體類型包括:Timer亭引,Counter,Gauge皮获,DistributionSummary焙蚓,LongTaskTimer,F(xiàn)unctionCounter洒宝,F(xiàn)unctionTimer和TimeGauge购公。
下面分節(jié)詳細介紹這些類型的使用方法和實戰(zhàn)使用場景。而一個Meter具體類型需要通過名字和Tag(這里指的是Micrometer提供的Tag接口)作為它的唯一標(biāo)識雁歌,這樣做的好處是可以使用名字進行標(biāo)記宏浩,通過不同的Tag去區(qū)分多種維度進行數(shù)據(jù)統(tǒng)計。
# MeterRegistry
MeterRegistry在Micrometer是一個抽象類靠瞎,主要實現(xiàn)包括:
SimpleMeterRegistry:每個Meter的最新數(shù)據(jù)可以收集到SimpleMeterRegistry實例中比庄,但是這些數(shù)據(jù)不會發(fā)布到其他系統(tǒng),也就是數(shù)據(jù)是位于應(yīng)用的內(nèi)存中的乏盐。
CompositeMeterRegistry:多個MeterRegistry聚合佳窑,內(nèi)部維護了一個MeterRegistry的列表。
全局的MeterRegistry:工廠類io.micrometer.core.instrument.Metrics中持有一個靜態(tài)final的CompositeMeterRegistry實例globalRegistry父能。
當(dāng)然神凑,使用者也可以自行繼承MeterRegistry去實現(xiàn)自定義的MeterRegistry。
SimpleMeterRegistry適合做調(diào)試的時候使用何吝,它的簡單使用方式如下:
MeterRegistry registry = new SimpleMeterRegistry();
Counter counter = registry.counter("counter");
counter.increment();
CompositeMeterRegistry實例初始化的時候溉委,內(nèi)部持有的MeterRegistry列表是空的鹃唯,如果此時用它新增一個Meter實例,Meter實例的操作是無效的
CompositeMeterRegistry composite = new CompositeMeterRegistry();
Counter compositeCounter = composite.counter("counter");
compositeCounter.increment(); // <- 實際上這一步操作是無效的,但是不會報錯
SimpleMeterRegistry simple = new SimpleMeterRegistry();
composite.add(simple);? // <- 向CompositeMeterRegistry實例中添加SimpleMeterRegistry實例
compositeCounter.increment();? // <-計數(shù)成功
全局的MeterRegistry的使用方式更加簡單便捷瓣喊,因為一切只需要操作工廠類Metrics的靜態(tài)方法:
Metrics.addRegistry(new SimpleMeterRegistry());
Counter counter = Metrics.counter("counter", "tag-1", "tag-2");
counter.increment();
# Tag與Meter的命名
Micrometer中俯渤,Meter的命名約定使用英文逗號(dot,也就是”.”)分隔單詞型宝。但是對于不同的監(jiān)控系統(tǒng)八匠,對命名的規(guī)約可能并不相同,如果命名規(guī)約不一致趴酣,在做監(jiān)控系統(tǒng)遷移或者切換的時候梨树,可能會對新的系統(tǒng)造成破壞。
Micrometer中使用英文逗號分隔單詞的命名規(guī)則岖寞,再通過底層的命名轉(zhuǎn)換接口NamingConvention進行轉(zhuǎn)換抡四,最終可以適配不同的監(jiān)控系統(tǒng),同時可以消除監(jiān)控系統(tǒng)不允許的特殊字符的名稱和標(biāo)記等仗谆。開發(fā)者也可以覆蓋NamingConvention實現(xiàn)自定義的命名轉(zhuǎn)換規(guī)則:registry.config().namingConvention(myCustomNamingConvention);指巡。
在Micrometer中,對一些主流的監(jiān)控系統(tǒng)或者存儲系統(tǒng)的命名規(guī)則提供了默認的轉(zhuǎn)換方式隶垮,例如當(dāng)我們使用下面的命名時候:
MeterRegistry registry = ...
registry.timer("http.server.requests");
對于不同的監(jiān)控系統(tǒng)或者存儲系統(tǒng)藻雪,命名會自動轉(zhuǎn)換如下:
Prometheus - http_server_requests_duration_seconds。
Atlas - httpServerRequests狸吞。
Graphite - http.server.requests勉耀。
InfluxDB - http_server_requests。
其實NamingConvention已經(jīng)提供了5種默認的轉(zhuǎn)換規(guī)則:dot蹋偏、snakeCase便斥、camelCase、upperCamelCase和slashes威始。
另外枢纠,Tag(標(biāo)簽)是Micrometer的一個重要的功能,嚴格來說黎棠,一個度量框架只有實現(xiàn)了標(biāo)簽的功能晋渺,才能真正地多維度進行度量數(shù)據(jù)收集。Tag的命名一般需要是有意義的葫掉,所謂有意義就是可以根據(jù)Tag的命名可以推斷出它指向的數(shù)據(jù)到底代表什么維度或者什么類型的度量指標(biāo)些举。
假設(shè)我們需要監(jiān)控數(shù)據(jù)庫的調(diào)用和Http請求調(diào)用統(tǒng)計,一般推薦的做法是:
MeterRegistry registry = ...
registry.counter("database.calls", "db", "users")
registry.counter("http.requests", "uri", "/api/users")
這樣俭厚,當(dāng)我們選擇命名為”database.calls”的計數(shù)器户魏,我們可以進一步選擇分組”db”或者”users”分別統(tǒng)計不同分組對總調(diào)用數(shù)的貢獻或者組成。一個反例如下:
MeterRegistry registry = ...
registry.counter("calls",
? ? "class", "database",
? ? "db", "users");
registry.counter("calls",
? ? "class", "http",
? ? "uri", "/api/users");
通過命名”calls”得到的計數(shù)器,由于標(biāo)簽混亂叼丑,數(shù)據(jù)是基本無法分組統(tǒng)計分析关翎,這個時候可以認為得到的時間序列的統(tǒng)計數(shù)據(jù)是沒有意義的○牛可以定義全局的Tag纵寝,也就是全局的Tag定義之后,會附加到所有的使用到的Meter上(只要是使用同一MeterRegistry)星立,全局的Tag可以這樣定義:
MeterRegistry registry = ...
registry.counter("calls",
? ? "class", "database",
? ? "db", "users");
registry.counter("calls",
? ? "class", "http",
? ? "uri", "/api/users");
MeterRegistry registry = ...
registry.config().commonTags("stack", "prod", "region", "us-east-1");
// 和上面的意義是一樣的
registry.config().commonTags(Arrays.asList(Tag.of("stack", "prod"), Tag.of("region", "us-east-1")));
像上面這樣子使用爽茴,就能通過主機,實例绰垂,區(qū)域室奏,堆棧等操作環(huán)境進行多維度深入
分析。
還有兩點點需要注意:
1劲装、Tag的值必須不為null胧沫。
2、Micrometer中占业,Tag必須成對出現(xiàn)绒怨,也就是Tag必須設(shè)置為偶數(shù)個,實際上它們以Key=Value的形式存在谦疾,具體可以看io.micrometer.core.instrument.Tag接口:
public interface Tag extends Comparable<Tag> {
? ? String getKey();
? ? String getValue();
? ? static Tag of(String key, String value) {
? ? ? ? return new ImmutableTag(key, value);
? ? }
? ? default int compareTo(Tag o) {
? ? ? ? return this.getKey().compareTo(o.getKey());
? ? }
}
當(dāng)然南蹂,有些時候,我們需要過濾一些必要的標(biāo)簽或者名稱進行統(tǒng)計餐蔬,或者為Meter的名稱添加白名單碎紊,這個時候可以使用MeterFilter佑附。MeterFilter本身提供一些列的靜態(tài)方法樊诺,多個MeterFilter可以疊加或者組成鏈實現(xiàn)用戶最終的過濾策略。例如:
MeterRegistry registry = ...
registry.config()
? ? .meterFilter(MeterFilter.ignoreTags("http"))
? ? .meterFilter(MeterFilter.denyNameStartsWith("jvm"));
表示忽略”http”標(biāo)簽音同,拒絕名稱以”jvm”字符串開頭的Meter词爬。更多用法可以參詳一下MeterFilter這個類。
Meter的命名和Meter的Tag相互結(jié)合权均,以命名為軸心顿膨,以Tag為多維度要素,可以使度量數(shù)據(jù)的維度更加豐富叽赊,便于統(tǒng)計和分析恋沃。
# Meters
前面提到Meter主要包括:Timer,Counter必指,Gauge囊咏,DistributionSummary,LongTaskTimer,F(xiàn)unctionCounter梅割,F(xiàn)unctionTimer和TimeGauge霜第。下面逐一分析它們的作用和個人理解的實際使用場景(應(yīng)該說是生產(chǎn)環(huán)境)。
# Counter
Counter是一種比較簡單的Meter户辞,它是一種單值的度量類型泌类,或者說是一個單值計數(shù)器。Counter接口允許使用者使用一個固定值(必須為正數(shù))進行計數(shù)底燎。準(zhǔn)確來說:Counter就是一個增量為正數(shù)的單值計數(shù)器刃榨。這個舉個很簡單的使用例子:
使用場景:
Counter的作用是記錄XXX的總量或者計數(shù)值,適用于一些增長類型的統(tǒng)計双仍,例如下單喇澡、支付次數(shù)、Http請求總量記錄等等殊校,通過Tag可以區(qū)分不同的場景晴玖,對于下單,可以使用不同的Tag標(biāo)記不同的業(yè)務(wù)來源或者是按日期劃分为流,對于Http請求總量記錄呕屎,可以使用Tag區(qū)分不同的URL。用下單業(yè)務(wù)舉個例子:
//實體
@Data
public class Order {
? ? private String orderId;
? ? private Integer amount;
? ? private String channel;
? ? private LocalDateTime createTime;
}
public class CounterMain {
? ? private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
? ? static {
? ? ? ? ? ? Metrics.addRegistry(new SimpleMeterRegistry());
? ? ? ? }
? ? ? ? public static void main(String[] args) throws Exception {
? ? ? ? ? ? Order order1 = new Order();
? ? ? ? ? ? order1.setOrderId("ORDER_ID_1");
? ? ? ? ? ? order1.setAmount(100);
? ? ? ? ? ? order1.setChannel("CHANNEL_A");
? ? ? ? ? ? order1.setCreateTime(LocalDateTime.now());
? ? ? ? ? ? createOrder(order1);
? ? ? ? ? ? Order order2 = new Order();
? ? ? ? ? ? order2.setOrderId("ORDER_ID_2");
? ? ? ? ? ? order2.setAmount(200);
? ? ? ? ? ? order2.setChannel("CHANNEL_B");
? ? ? ? ? ? order2.setCreateTime(LocalDateTime.now());
? ? ? ? ? ? createOrder(order2);
? ? ? ? ? ? Search.in(Metrics.globalRegistry).meters().forEach(each -> {
? ? ? ? ? ? ? ? StringBuilder builder = new StringBuilder();
? ? ? ? ? ? ? ? builder.append("name:")
? ? ? ? ? ? ? ? ? ? ? ? .append(each.getId().getName())
? ? ? ? ? ? ? ? ? ? ? ? .append(",tags:")
? ? ? ? ? ? ? ? ? ? ? ? .append(each.getId().getTags())
? ? ? ? ? ? ? ? ? ? ? ? .append(",type:").append(each.getId().getType())
? ? ? ? ? ? ? ? ? ? ? ? .append(",value:").append(each.measure());
? ? ? ? ? ? ? ? System.out.println(builder.toString());
? ? ? ? ? ? });
? ? }
? ? private static void createOrder(Order order) {
? ? ? ? //忽略訂單入庫等操作
? ? ? ? Metrics.counter("order.create",
? ? ? ? ? ? ? ? "channel", order.getChannel(),
? ? ? ? ? ? ? ? "createTime", FORMATTER.format(order.getCreateTime())).increment();
? ? }
}
控制臺輸出
name:order.create,tags:[tag(channel=CHANNEL_A), tag(createTime=2018-11-10)],type:COUNTER,value:[Measurement{statistic='COUNT', value=1.0}]
name:order.create,tags:[tag(channel=CHANNEL_B), tag(createTime=2018-11-10)],type:COUNTER,value:[Measurement{statistic='COUNT', value=1.0}]
上面的例子是使用全局靜態(tài)方法工廠類Metrics去構(gòu)造Counter實例敬察,實際上秀睛,
io.micrometer.core.instrument.Counter接口提供了一個內(nèi)部建造器類
Counter.Builder去實例化Counter,Counter.Builder的使用方式如下:
public class CounterBuilderMain {
? ? ? ? public static void main(String[] args) throws Exception{
? ? ? ? ? ? Counter counter = Counter.builder("name")? //名稱
? ? ? ? ? ? ? ? ? ? .baseUnit("unit") //基礎(chǔ)單位
? ? ? ? ? ? ? ? ? ? .description("desc") //描述
? ? ? ? ? ? ? ? ? ? .tag("tagKey", "tagValue")? //標(biāo)簽
? ? ? ? ? ? ? ? ? ? .register(new SimpleMeterRegistry());//綁定的MeterRegistry
? ? ? ? ? ? counter.increment();
? ? ? ? }
}
# FunctionCounter
FunctionCounter是Counter的特化類型莲祸,它把計數(shù)器數(shù)值增加的動作抽象成接口類型ToDoubleFunction蹂安,這個接口JDK1.8中對于Function的特化類型接口。
FunctionCounter的使用場景和Counter是一致的锐帜,這里介紹一下它的用法:
public class FunctionCounterMain {
? ? ? ? public static void main(String[] args) throws Exception {
? ? ? ? ? ? MeterRegistry registry = new SimpleMeterRegistry();
? ? ? ? ? ? AtomicInteger n = new AtomicInteger(0);
? ? ? ? ? ? //這里ToDoubleFunction匿名實現(xiàn)其實可以使用Lambda表達式簡化為AtomicInteger::get
? ? ? ? ? ? FunctionCounter.builder("functionCounter", n, new ToDoubleFunction<AtomicInteger>() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public double applyAsDouble(AtomicInteger value) {
? ? ? ? ? ? ? ? ? ? return value.get();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }).baseUnit("function")
? ? ? ? ? ? ? ? ? ? .description("functionCounter")
? ? ? ? ? ? ? ? ? ? .tag("createOrder", "CHANNEL-A")
? ? ? ? ? ? ? ? ? ? .register(registry);
? ? ? ? ? ? //下面模擬三次計數(shù)? ? ? ?
? ? ? ? ? ? n.incrementAndGet();
? ? ? ? ? ? n.incrementAndGet();
? ? ? ? ? ? n.incrementAndGet();
? ? ? ? }
}
FunctionCounter使用的一個明顯的好處是田盈,我們不需要感知FunctionCounter實例的存在,實際上我們只需要操作作為FunctionCounter實例構(gòu)建元素之一的AtomicInteger實例即可缴阎,這種接口的設(shè)計方式在很多框架里面都可以看到允瞧。
# Timer
Timer(計時器)適用于記錄耗時比較短的事件的執(zhí)行時間,通過時間分布展示事件的序列和發(fā)生頻率蛮拔。所有的Timer的實現(xiàn)至少記錄了發(fā)生的事件的數(shù)量和這些事件的總耗時述暂,從而生成一個時間序列。
Timer的基本單位基于服務(wù)端的指標(biāo)而定建炫,但是實際上我們不需要過于關(guān)注Timer的基本單位畦韭,因為Micrometer在存儲生成的時間序列的時候會自動選擇適當(dāng)?shù)幕締挝弧imer接口提供的常用方法如下:
public interface Timer extends Meter {
? ? ...
? ? void record(long var1, TimeUnit var3);
? ? default void record(Duration duration) {
? ? ? ? this.record(duration.toNanos(), TimeUnit.NANOSECONDS);
? ? }
? ? <T> T record(Supplier<T> var1);
? ? <T> T recordCallable(Callable<T> var1) throws Exception;
? ? void record(Runnable var1);
? ? default Runnable wrap(Runnable f) {
? ? ? ? return () -> {
? ? ? ? ? ? this.record(f);
? ? ? ? };
? ? }
? ? default <T> Callable<T> wrap(Callable<T> f) {
? ? ? ? return () -> {
? ? ? ? ? ? return this.recordCallable(f);
? ? ? ? };
? ? }
? ? long count();
? ? double totalTime(TimeUnit var1);
? ? default double mean(TimeUnit unit) {
? ? ? ? return this.count() == 0L ? 0.0D : this.totalTime(unit) / (double)this.count();
? ? }
? ? double max(TimeUnit var1);
? ? ...
}
實際上肛跌,比較常用和方便的方法是幾個函數(shù)式接口入?yún)⒌姆椒ǎ?/p>
Timer timer = ...
timer.record(() -> dontCareAboutReturnValue());
timer.recordCallable(() -> returnValue());
Runnable r = timer.wrap(() -> dontCareAboutReturnValue());
Callable c = timer.wrap(() -> returnValue());
使用場景:
根據(jù)個人經(jīng)驗和實踐艺配,總結(jié)如下:
記錄指定方法的執(zhí)行時間用于展示据过。
記錄一些任務(wù)的執(zhí)行時間,從而確定某些數(shù)據(jù)來源的速率妒挎,例如消息隊列消息的消費速率等绳锅。
這里舉個實際的例子,要對系統(tǒng)做一個功能酝掩,記錄指定方法的執(zhí)行時間鳞芙,還是用下單方法做例子:
public class TimerMain {
? ? ? ? private static final Random R = new Random();
? ? ? ? static {
? ? ? ? ? ? Metrics.addRegistry(new SimpleMeterRegistry());
? ? ? ? }
? ? ? ? public static void main(String[] args) throws Exception {
? ? ? ? ? ? Order order1 = new Order();
? ? ? ? ? ? order1.setOrderId("ORDER_ID_1");
? ? ? ? ? ? order1.setAmount(100);
? ? ? ? ? ? order1.setChannel("CHANNEL_A");
? ? ? ? ? ? order1.setCreateTime(LocalDateTime.now());
? ? ? ? ? ? Timer timer = Metrics.timer("timer", "createOrder", "cost");
? ? ? ? ? ? timer.record(() -> createOrder(order1));
? ? ? ? }
? ? ? ? private static void createOrder(Order order) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(R.nextInt(5)); //模擬方法耗時
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? //no-op
? ? ? ? ? ? }
? ? ? ? }
}
在實際生產(chǎn)環(huán)境中,可以通過spring-aop把記錄方法耗時的邏輯抽象到一個切面中期虾,這樣就能減少不必要的冗余的模板代碼原朝。上面的例子是通過Mertics構(gòu)造Timer實例,實際上也可以使用Builder構(gòu)造:
MeterRegistry registry = ...
Timer timer = Timer
? ? .builder("my.timer")
? ? .description("a description of what this timer does") // 可選
? ? .tags("region", "test") // 可選
? ? .register(registry);
另外镶苞,Timer的使用還可以基于它的內(nèi)部類Timer.Sample喳坠,通過start和stop兩個方法記錄兩者之間的邏輯的執(zhí)行耗時。例如:
Timer.Sample sample = Timer.start(registry);
// 這里做業(yè)務(wù)邏輯
Response response = ...
sample.stop(registry.timer("my.timer", "response", response.status()));
# FunctionTimer
FunctionTimer是Timer的特化類型茂蚓,它主要提供兩個單調(diào)遞增的函數(shù)(其實并不是單調(diào)遞增壕鹉,只是在使用中一般需要隨著時間最少保持不變或者說不減少):一個用于計數(shù)的函數(shù)和一個用于記錄總調(diào)用耗時的函數(shù),它的建造器的入?yún)⑷缦拢?/p>
public interface FunctionTimer extends Meter {
? ? static <T> Builder<T> builder(String name, T obj, ToLongFunction<T> countFunction,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ToDoubleFunction<T> totalTimeFunction,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit totalTimeFunctionUnit) {
? ? ? ? return new Builder<>(name, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit);
? ? }
? ? ...
}
官方文檔中的例子如下:
IMap<?, ?> cache = ...; // 假設(shè)使用了Hazelcast緩存
registry.more().timer("cache.gets.latency", Tags.of("name", cache.getName()), cache,
? ? c -> c.getLocalMapStats().getGetOperationCount(),? //實際上就是cache的一個方法聋涨,記錄緩存生命周期初始化的增量(個數(shù))
? ? c -> c.getLocalMapStats().getTotalGetLatency(),? // Get操作的延遲時間總量晾浴,可以理解為耗時
? ? TimeUnit.NANOSECONDS
);
按照個人理解,ToDoubleFunction用于統(tǒng)計事件個數(shù)牍白,ToDoubleFunction用于記錄執(zhí)行總時間脊凰,實際上兩個函數(shù)都只是Function函數(shù)的變體,還有一個比較重要的是總時間的單位totalTimeFunctionUnit茂腥。簡單的使用方式如下:
public class FunctionTimerMain {
? ? ? ? public static void main(String[] args) throws Exception {
? ? ? ? ? ? //這個是為了滿足參數(shù),暫時不需要理會
? ? ? ? ? ? Object holder = new Object();
? ? ? ? ? ? AtomicLong totalTimeNanos = new AtomicLong(0);
? ? ? ? ? ? AtomicLong totalCount = new AtomicLong(0);
? ? ? ? ? ? FunctionTimer.builder("functionTimer", holder, p -> totalCount.get(),
? ? ? ? ? ? ? ? ? ? p -> totalTimeNanos.get(), TimeUnit.NANOSECONDS)
? ? ? ? ? ? ? ? ? ? .register(new SimpleMeterRegistry());
? ? ? ? ? ? totalTimeNanos.addAndGet(10000000);
? ? ? ? ? ? totalCount.incrementAndGet();
? ? ? ? }
}
# LongTaskTimer
LongTaskTimer也是一種Timer的特化類型狸涌,主要用于記錄長時間執(zhí)行的任務(wù)的持續(xù)時間,在任務(wù)完成之前最岗,被監(jiān)測的事件或者任務(wù)仍然處于運行狀態(tài)帕胆,任務(wù)完成的時候,任務(wù)執(zhí)行的總耗時才會被記錄下來仑性。
LongTaskTimer適合用于長時間持續(xù)運行的事件耗時的記錄惶楼,例如相對耗時的定時任務(wù)。在Spring應(yīng)用中诊杆,可以簡單地使用@Scheduled和@Timed注解,基于spring-aop完成定時調(diào)度任務(wù)的總耗時記錄:
@Timed(value = "aws.scrape", longTask = true)
@Scheduled(fixedDelay = 360000)
void scrapeResources() {
? ? //這里做相對耗時的業(yè)務(wù)邏輯
}
當(dāng)然何陆,在非spring體系中也能方便地使用LongTaskTimer:
public class LongTaskTimerMain {
? ? ? ? public static void main(String[] args) throws Exception{
? ? ? ? ? ? MeterRegistry meterRegistry = new SimpleMeterRegistry();
? ? ? ? ? ? LongTaskTimer longTaskTimer = meterRegistry.more().longTaskTimer("longTaskTimer");
? ? ? ? ? ? longTaskTimer.record(() -> {
? ? ? ? ? ? ? ? //這里編寫Task的邏輯
? ? ? ? ? ? });
? ? ? ? ? ? //或者這樣
? ? ? ? ? ? Metrics.more().longTaskTimer("longTaskTimer").record(()-> {
? ? ? ? ? ? ? ? //這里編寫Task的邏輯
? ? ? ? ? ? });
? ? ? ? }
}