新blog地址:http://hengyunabc.github.io/about-metrics/
metrics,按字面意思是度量,指標(biāo)。
舉具體的例子來(lái)講,1個(gè)web服務(wù)器:
- 1分鐘內(nèi)要求多少次?
- 平均要求耗時(shí)多長(zhǎng)?
- 最長(zhǎng)要求時(shí)間?
- 某個(gè)方法的被調(diào)用次數(shù),時(shí)長(zhǎng)?
以緩存為例:
- 平均查詢緩存時(shí)間?
- 緩存獲得不命中的次數(shù)/比例?
以jvm為例:
- GC的次數(shù)?
- Old Space的大小?
在1個(gè)利用里,需要搜集的metrics數(shù)據(jù)是多種多樣的,需求也是各不同的。需要1個(gè)統(tǒng)1的metrics搜集,統(tǒng)計(jì),展現(xiàn)平臺(tái)。
https://github.com/dropwizard/metrics
java實(shí)現(xiàn),很多開(kāi)源項(xiàng)目用到,比如hadoop,kafka。下面稱為dropwizard/metrics。
https://github.com/tumblr/colossus
scala實(shí)現(xiàn),把數(shù)據(jù)存到OpenTsdb上。
spring boot 項(xiàng)目里的metrics:
http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html
spring boot里的metrics很多都是參考dropwizard/metrics的。
dropwizard/metrics 里主要把metrics分為下面幾大類:
https://dropwizard.github.io/metrics/3.1.0/getting-started/
gauge用于丈量1個(gè)數(shù)值。比如隊(duì)列的長(zhǎng)度:
public class QueueManager {
private final Queue queue;
public QueueManager(MetricRegistry metrics, String name) {
this.queue = new Queue();
metrics.register(MetricRegistry.name(QueueManager.class, name, "size"),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return queue.size();
}
});
}
}
counter是AtomicLong類型的gauge。比如可以統(tǒng)計(jì)阻塞在隊(duì)列里的job的數(shù)量:
private final Counter pendingJobs = metrics.counter(name(QueueManager.class, "pending-jobs"));
public void addJob(Job job) {
pendingJobs.inc();
queue.offer(job);
}
public Job takeJob() {
pendingJobs.dec();
return queue.take();
}
histogram統(tǒng)計(jì)數(shù)據(jù)的散布。比如最小值,最大值,中間值,還有中位數(shù),75百分位, 90百分位, 95百分位, 98百分位, 99百分位, and 99.9百分位的值(percentiles)。
比如request的大小的散布:
private final Histogram responseSizes = metrics.histogram(name(RequestHandler.class, "response-sizes"));
public void handleRequest(Request request, Response response) {
// etc
responseSizes.update(response.getContent().length);
}
timer正如其名,統(tǒng)計(jì)的是某部份代碼/調(diào)用的運(yùn)行時(shí)間。比如統(tǒng)計(jì)response的耗時(shí):
private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
public String handleRequest(Request request, Response response) {
final Timer.Context context = responses.time();
try {
// etc;
return "OK";
} finally {
context.stop();
}
}
這個(gè)實(shí)際上不是統(tǒng)計(jì)數(shù)據(jù)。是接口讓用戶可以自己判斷系統(tǒng)的健康狀態(tài)。如判斷數(shù)據(jù)庫(kù)是不是連接正常:
final HealthCheckRegistry healthChecks = new HealthCheckRegistry();
public class DatabaseHealthCheck extends HealthCheck {
private final Database database;
public DatabaseHealthCheck(Database database) {
this.database = database;
}
@Override
public HealthCheck.Result check() throws Exception {
if (database.isConnected()) {
return HealthCheck.Result.healthy();
} else {
return HealthCheck.Result.unhealthy("Cannot connect to " + database.getUrl());
}
}
}
利用dropwizard/metrics 里的annotation,可以很簡(jiǎn)單的實(shí)現(xiàn)統(tǒng)計(jì)某個(gè)方法,某個(gè)值的數(shù)據(jù)。
如:
/**
* 統(tǒng)計(jì)調(diào)用的次數(shù)和時(shí)間
*/
@Timed
public void call() {
}
/**
* 統(tǒng)計(jì)登陸的次數(shù)
*/
@Counted
public void userLogin(){
}
想要詳細(xì)了解各種metrics的實(shí)際效果,簡(jiǎn)單的運(yùn)行下測(cè)試代碼,用ConsoleReporter輸出就能夠知道了。
dropwizard/metrics 里提供了reporter的接口,用戶可以自己實(shí)現(xiàn)如何處理metrics數(shù)據(jù)。
dropwizard/metrics有很多現(xiàn)成的reporter:
ConsoleReporter 輸出到stdout
JmxReporter 轉(zhuǎn)化為MBean
metrics-servlets 提供http接口,可以查詢到metrics信息
CsvReporter 輸出為CSV文件
Slf4jReporter 以log方式輸出
GangliaReporter 上報(bào)到Ganglia
GraphiteReporter 上報(bào)到Graphite
上面的各種reporter中,Ganglia開(kāi)源多年,但缺少1些監(jiān)控的功能,圖形展現(xiàn)也很簡(jiǎn)陋。Graphite已停止開(kāi)發(fā)了。
而公司所用的監(jiān)控系統(tǒng)是zabbix,而dropwizard/metrics沒(méi)有現(xiàn)成的zabbix reporter。
zabbix上報(bào)數(shù)據(jù)通經(jīng)常使用zabbix agent或zabbix trapper。
用戶自己上報(bào)的數(shù)據(jù)通經(jīng)常使用zabbix trapper來(lái)上報(bào)。
zabbix上搜集數(shù)據(jù)的叫item,每一個(gè)item都有自己的key,而這些item不會(huì)自動(dòng)創(chuàng)建。zabbix有Low-level discovery,可以自動(dòng)創(chuàng)建item,但是也相當(dāng)麻煩,而且key的命名非常奇怪。不如直接用template了。
https://www.zabbix.com/documentation/2.4/manual/discovery/low_level_discovery
假定zabbix上不同的利用的key都是相對(duì)固定的,那末就能夠通過(guò)模板的方式,比較方便地統(tǒng)1創(chuàng)建item, graph了。
另外想要實(shí)現(xiàn)自動(dòng)創(chuàng)建item,比較好的辦法是通過(guò)zabbix api了。
但目前Java版沒(méi)有實(shí)現(xiàn),因而實(shí)現(xiàn)了1個(gè)簡(jiǎn)單的:
https://github.com/hengyunabc/zabbix-api
基于上面的template的思路,實(shí)現(xiàn)了1個(gè)dropwizard/metrics 的zabbix reporter。
原理是,通過(guò)zabbix sender,把metrics數(shù)據(jù)直接發(fā)送到zabbix server上。
https://github.com/hengyunabc/zabbix-sender
https://github.com/hengyunabc/metrics-zabbix
上面的方案感覺(jué)還是不太理想:
- 沒(méi)有實(shí)現(xiàn)自動(dòng)化,還要手動(dòng)為每個(gè)利用配置template,不夠靈活
- 所有的數(shù)據(jù)都發(fā)送到1個(gè)zabbix server上,擔(dān)心性能有瓶頸
因而,新的思路是,把metrics數(shù)據(jù)發(fā)送到kafka上,然后再?gòu)膋afka上消費(fèi),再把數(shù)據(jù)傳到zabbix server上。
這樣的好處是:
- kafka可以靈活擴(kuò)容,不會(huì)有性能瓶頸
- 從kafka上消費(fèi)metrics數(shù)據(jù),可以靈活地用zabbix api來(lái)創(chuàng)建item, graph
因而實(shí)現(xiàn)了兩個(gè)新項(xiàng)目:
- https://github.com/hengyunabc/metrics-kafka
- https://github.com/hengyunabc/kafka-zabbix
Java程序先把metrics數(shù)據(jù)上報(bào)到kafka,然后kafka consumer從metrics數(shù)據(jù)里,提取出host, key信息,再用zabbix-api在zabbix server上創(chuàng)建item,最后把metrics數(shù)據(jù)上報(bào)給zabbix server。
自動(dòng)創(chuàng)建的zabbix item的效果圖:
在zabbix上顯示的用戶自定義的統(tǒng)計(jì)數(shù)據(jù)的圖:
比如,統(tǒng)計(jì)接口的訪問(wèn)次數(shù),而這個(gè)接口部署在多臺(tái)服務(wù)器上,那末如何展現(xiàn)聚合的數(shù)據(jù)?
zabbix自帶有聚合功能,參考:
http://opsnotes.net/2014/10/24/zabbix_juhe/ 實(shí)戰(zhàn):Zabbix 聚合功能配置與利用
從dropwizard/metrics里,我們可以看到1種簡(jiǎn)單直觀的實(shí)現(xiàn):
- app內(nèi)搜集統(tǒng)計(jì)數(shù)據(jù),計(jì)算好具體的key/value
- 定時(shí)上報(bào)
另外,用散布式調(diào)用追蹤(dapper/zipkin)的辦法,也能夠?qū)崿F(xiàn)部份metrics的功能。
比如某個(gè)方法的調(diào)用次數(shù),緩存命中次數(shù)等。
固然,二者只是部份功能有重合。
dropwizard/metrics 是1種輕量級(jí)的手段,用戶可以隨便增加自己想要的統(tǒng)計(jì)數(shù)據(jù),代碼也很靈活。有些簡(jiǎn)單直觀的統(tǒng)計(jì)數(shù)據(jù)如果用散布式調(diào)用追蹤的方式來(lái)做,明顯會(huì)比較費(fèi)勁,得不償失。
本文提出并實(shí)現(xiàn)了,利用dropwizard/metrics做數(shù)據(jù)統(tǒng)計(jì),kafka做數(shù)據(jù)傳輸,zabbix做數(shù)據(jù)展現(xiàn)的完全流程。
對(duì)開(kāi)發(fā)者來(lái)講,不需要關(guān)心具體的實(shí)現(xiàn),只需要按dropwizard/metrics的文檔做統(tǒng)計(jì),再配置上metrics-kafka reporter便可。