微服務監控主要分爲兩部分,一部分是對微服務自己的監控,另外一方面是對整個調用鏈的監控。目前,咱們主要採用dubbo做爲rpc框架,因此下面重點介紹dubbo監控。java
dubbo架構以下: node
經過閱讀dubbo源碼,全部的rpc方法調用都會通過MonitorFilter進行攔截,python
MonitorFilter.invoke()mysql
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (invoker.getUrl().hasParameter("monitor")) {
RpcContext context = RpcContext.getContext();
long start = System.currentTimeMillis();
this.getConcurrent(invoker, invocation).incrementAndGet();
Result var7;
try {
Result result = invoker.invoke(invocation);
this.collect(invoker, invocation, result, context, start, false);
var7 = result;
} catch (RpcException var11) {
this.collect(invoker, invocation, (Result)null, context, start, true);
throw var11;
} finally {
this.getConcurrent(invoker, invocation).decrementAndGet();
}
return var7;
} else {
return invoker.invoke(invocation);
}
}
複製代碼
對於配置了監控的服務,會收集一些方法的基本統計信息。nginx
MonitorFilter.collect()git
private void collect(Invoker<?> invoker, Invocation invocation, Result result, RpcContext context, long start, boolean error) {
try {
long elapsed = System.currentTimeMillis() - start;
int concurrent = this.getConcurrent(invoker, invocation).get();
String application = invoker.getUrl().getParameter("application");
String service = invoker.getInterface().getName();
String method = RpcUtils.getMethodName(invocation);
URL url = invoker.getUrl().getUrlParameter("monitor");
Monitor monitor = this.monitorFactory.getMonitor(url);
int localPort;
String remoteKey;
String remoteValue;
if ("consumer".equals(invoker.getUrl().getParameter("side"))) {
context = RpcContext.getContext();
localPort = 0;
remoteKey = "provider";
remoteValue = invoker.getUrl().getAddress();
} else {
localPort = invoker.getUrl().getPort();
remoteKey = "consumer";
remoteValue = context.getRemoteHost();
}
String input = "";
String output = "";
if (invocation.getAttachment("input") != null) {
input = invocation.getAttachment("input");
}
if (result != null && result.getAttachment("output") != null) {
output = result.getAttachment("output");
}
monitor.collect(new URL("count", NetUtils.getLocalHost(), localPort, service + "/" + method, new String[]{"application", application, "interface", service, "method", method, remoteKey, remoteValue, error ? "failure" : "success", "1", "elapsed", String.valueOf(elapsed), "concurrent", String.valueOf(concurrent), "input", input, "output", output}));
} catch (Throwable var21) {
logger.error("Failed to monitor count service " + invoker.getUrl() + ", cause: " + var21.getMessage(), var21);
}
}
複製代碼
DubboMonitor對收集到的數據進行簡單統計,諸如成功次數,失敗次數,調用時間等,統計完後存儲數據到本地。github
DubboMonitor.collect()web
public void collect(URL url) {
int success = url.getParameter("success", 0);
int failure = url.getParameter("failure", 0);
int input = url.getParameter("input", 0);
int output = url.getParameter("output", 0);
int elapsed = url.getParameter("elapsed", 0);
int concurrent = url.getParameter("concurrent", 0);
Statistics statistics = new Statistics(url);
AtomicReference<long[]> reference = (AtomicReference)this.statisticsMap.get(statistics);
if (reference == null) {
this.statisticsMap.putIfAbsent(statistics, new AtomicReference());
reference = (AtomicReference)this.statisticsMap.get(statistics);
}
long[] update = new long[10];
long[] current;
do {
current = (long[])reference.get();
if (current == null) {
update[0] = (long)success;
update[1] = (long)failure;
update[2] = (long)input;
update[3] = (long)output;
update[4] = (long)elapsed;
update[5] = (long)concurrent;
update[6] = (long)input;
update[7] = (long)output;
update[8] = (long)elapsed;
update[9] = (long)concurrent;
} else {
update[0] = current[0] + (long)success;
update[1] = current[1] + (long)failure;
update[2] = current[2] + (long)input;
update[3] = current[3] + (long)output;
update[4] = current[4] + (long)elapsed;
update[5] = (current[5] + (long)concurrent) / 2L;
update[6] = current[6] > (long)input ? current[6] : (long)input;
update[7] = current[7] > (long)output ? current[7] : (long)output;
update[8] = current[8] > (long)elapsed ? current[8] : (long)elapsed;
update[9] = current[9] > (long)concurrent ? current[9] : (long)concurrent;
}
} while(!reference.compareAndSet(current, update));
}
複製代碼
DubboMonitor有異步線程定時(默認每分鐘)將收集到數據發送到遠端監控服務。redis
public DubboMonitor(Invoker<MonitorService> monitorInvoker, MonitorService monitorService) {
this.monitorInvoker = monitorInvoker;
this.monitorService = monitorService;
this.monitorInterval = (long)monitorInvoker.getUrl().getPositiveParameter("interval", 60000);
this.sendFuture = this.scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
DubboMonitor.this.send();
} catch (Throwable var2) {
DubboMonitor.logger.error("Unexpected error occur at send statistic, cause: " + var2.getMessage(), var2);
}
}
}, this.monitorInterval, this.monitorInterval, TimeUnit.MILLISECONDS);
}
複製代碼
調用遠端的MonitorService.collect方法,而後將本地緩存數據置置零。sql
DubboMonitor.send()
public void send() {
if (logger.isInfoEnabled()) {
logger.info("Send statistics to monitor " + this.getUrl());
}
String timestamp = String.valueOf(System.currentTimeMillis());
Iterator i$ = this.statisticsMap.entrySet().iterator();
while(i$.hasNext()) {
Entry<Statistics, AtomicReference<long[]>> entry = (Entry)i$.next();
Statistics statistics = (Statistics)entry.getKey();
AtomicReference<long[]> reference = (AtomicReference)entry.getValue();
long[] numbers = (long[])reference.get();
long success = numbers[0];
long failure = numbers[1];
long input = numbers[2];
long output = numbers[3];
long elapsed = numbers[4];
long concurrent = numbers[5];
long maxInput = numbers[6];
long maxOutput = numbers[7];
long maxElapsed = numbers[8];
long maxConcurrent = numbers[9];
URL url = statistics.getUrl().addParameters(new String[]{"timestamp", timestamp, "success", String.valueOf(success), "failure", String.valueOf(failure), "input", String.valueOf(input), "output", String.valueOf(output), "elapsed", String.valueOf(elapsed), "concurrent", String.valueOf(concurrent), "max.input", String.valueOf(maxInput), "max.output", String.valueOf(maxOutput), "max.elapsed", String.valueOf(maxElapsed), "max.concurrent", String.valueOf(maxConcurrent)});
this.monitorService.collect(url);
long[] update = new long[10];
while(true) {
long[] current = (long[])reference.get();
if (current == null) {
update[0] = 0L;
update[1] = 0L;
update[2] = 0L;
update[3] = 0L;
update[4] = 0L;
update[5] = 0L;
} else {
update[0] = current[0] - success;
update[1] = current[1] - failure;
update[2] = current[2] - input;
update[3] = current[3] - output;
update[4] = current[4] - elapsed;
update[5] = current[5] - concurrent;
}
if (reference.compareAndSet(current, update)) {
break;
}
}
}
}
複製代碼
dubbo監控的主流開源項目,都是實現了MonitorService接口來實現監控,區別無非就是數據存儲,報表統計邏輯的差別,基本原理都大同小異。
public interface MonitorService {
String APPLICATION = "application";
String INTERFACE = "interface";
String METHOD = "method";
String GROUP = "group";
String VERSION = "version";
String CONSUMER = "consumer";
String PROVIDER = "provider";
String TIMESTAMP = "timestamp";
String SUCCESS = "success";
String FAILURE = "failure";
String INPUT = "input";
String OUTPUT = "output";
String ELAPSED = "elapsed";
String CONCURRENT = "concurrent";
String MAX_INPUT = "max.input";
String MAX_OUTPUT = "max.output";
String MAX_ELAPSED = "max.elapsed";
String MAX_CONCURRENT = "max.concurrent";
void collect(URL var1);
List<URL> lookup(URL var1);
}
複製代碼
主流dubbo監控主要有:
下面進行簡單的對比:
方案 | 支持版本 | 基礎功能 | 開源做者 | 社區活躍度 | 數據存儲 | 維護成本 |
---|---|---|---|---|---|---|
dubbo-monitor | 基於dubbox,理論上也支持dubbo | 通常,QPS、RT、服務狀態等,缺少報表功能 | 韓都衣舍 | 513星,兩年前有提交 | mysql、mongodb | 無侵入、須要按期清理歷史數據 |
dubbo-d-monitor | dubbo | 通常,只有一些基礎數據 | 我的 | 189星,一年前有提交 | mysql、redis(後續再也不維護) | 無侵入、須要按期清理歷史數據 |
dubbokeeper | dubbo | 豐富,除了基礎指標數據,有top200數據報表,還提供了相似dubbo-admin功能(限流、超時時間設置、消費客戶端設置、容錯等),同時支持zk節點可視化 | 我的組織 | 989星,一個月內有提交 | mysql、mongodb、lucene | 無侵入、須要按期清理歷史記錄 |
dubbo-monitor-simple | dubbo | 簡陋 | dubbo官方 | 330星,一個月內有提交 | 文件存儲 | 無侵入、但目前線上使用發現數據量大了常常掛 |
對比以上幾種,dubbokeeper>dubbo-monitor>dubbo-d-monitor,因此選取dubbokeeper最爲dubbo服務監控方案。
咱們採用mongodb存儲方案,採用單機部署。
環境:jdk1.8及以上(低版未測試),安裝tomcat,安裝zookeeper並啓動,安裝啓動mongodb
一、獲取源碼 github.com/dubboclub/d…
二、解壓下載下來的zip包dubbokeeper-master到任意目錄,修改解壓後的項目中dubbo及數據庫的配置\dubbokeeper-master\conf\dubbo-mongodb.properties。
執行\dubbokeeper-master\install-mongodb.sh 執行完上一步後會生成一個target目錄,目錄下會存在如下三個文件夾及一個壓縮包
archive-tmp
mongodb-dubbokeeper-server
mongodb-dubbokeeper-ui
mongodb-dubbokeeper-server.tar.gz
複製代碼
三、執行mongodb-dubbokeeper-server/bin/start-mongodb.sh啓動存儲端(數據存儲和web端是分開獨立部署的)
四、將mongodb-dubbokeeper-ui下的war包拷貝到tomcat的webapps目錄下,啓動tomcat。
五、最後,打開瀏覽器,輸入http://localhost:8080/dubbokeeper-ui-1.0.1便可。
在業務代碼中,只須要配置dubbo監控鏈接到註冊中心,就能完成監控數據採集。
<dubbo:monitor protocol="registry"/>
複製代碼
主要的配置信息:
dubbo.application.name=mongodb-monitor
dubbo.application.owner=bieber
dubbo.registry.address=zookeeper://*.*.*.*:2181?backup=*.*.*.*:2181,*.*.*.*:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20884
dubbo.protocol.dubbo.payload=20971520
#dubbo數據採集週期 單位毫秒
monitor.collect.interval=60000
#use netty4
dubbo.provider.transporter=netty4
#dubbokeeper寫入mongodb週期 單位秒
monitor.write.interval=60
#mongdb配置
dubbo.monitor.mongodb.url=localhost
dubbo.monitor.mongodb.port=27017
dubbo.monitor.mongodb.dbname=dubbokeeper
dubbo.monitor.mongodb.username=
dubbo.monitor.mongodb.password=
dubbo.monitor.mongodb.storage.timeout=60000
複製代碼
首頁能看到應用整體信息(區分應用提供者和消費者),服務數量信息,節點部署信息及依賴關係圖等。
Admin提供了全部原生dubbo-admin的絕大部分功能。
ZooPeeper能夠查看zookeeper節點信息
Monitor能夠查看dubbo監控相關信息
應用總覽信息,可根據時間篩選:
應用詳細信息,有接口耗時、併發、失敗、成功等數據:
方法級別總覽及詳細信息:
一、官方默認monitor.write.interval(存儲週期)配置的是6000,閱讀源碼發現單位是秒,也就是默認配置100分鐘纔會寫入mongodb,要把它改爲60。
二、dubbokeeper默認沒有對collections加索引,數據量大了以後打開會異常慢,因此須要本身經過腳本對collection加索引。
import pymongo
from pymongo import MongoClient
import time
import datetime
import sys
import os
client = MongoClient('127.0.0.1', 27017)
db = client['dubbokeeper']
collectionlist = db.collection_names()
for collection in collectionlist:
if collection!='application':
db[collection].ensure_index([("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING)])
db[collection].ensure_index([("method",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("method",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("concurrent",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("elapsed",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("failureCount",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("successCount",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("elapsed",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("concurrent",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("failureCount",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
db[collection].ensure_index([("serviceInterface",pymongo.DESCENDING),("successCount",pymongo.DESCENDING),("timestamp",pymongo.DESCENDING)])
print 'success'
複製代碼
三、通常歷史數據基本不用保存過久,目前咱們線上保留2週數據,提供瞭如下腳本按期刪除數據。
import pymongo
from pymongo import MongoClient
import time
import datetime
import sys
import os
day=int(sys.argv[1])
print day
timestamp = time.time()*1000-1000*24*3600*day
print timestamp
client = MongoClient('127.0.0.1', 27017)
db = client['dubbokeeper']
collectionlist = db.collection_names()
for collection in collectionlist:
if collection!='application':
db[collection].remove({"timestamp": {"$lt": timestamp}})
print 'clean mongodb data success'
複製代碼
天天定時清理15天的數據
0 3 * * * python /home/monitor/shell/clean-mongodb.py 15
複製代碼
四、mongodb緩存比較吃內存,最好配置8G以上的服務器,或者量大能夠考慮集羣部署
五、dubbokeeper-ui原生交互有點坑,有些頁面會遍歷展現全部應用的數據,效率比較低下。若是應用過多可能會超時打不開,服務端團隊對交互進行了簡單優化,每次只能查看一個應用或一個接口,若是你們有需求能夠留言,咱們後續會開源出來。
考慮接入應用性能監控主要想解決如下問題:
方案 | cat | zipkin | pinpoint | skywalking |
---|---|---|---|---|
依賴 | Java 6 7 八、Maven 3+ MySQL 5.6 5.七、Linux 2.6+ hadoop可選 | Java 6,7,8 Maven3.2+ rabbitMQ | Java 6,7,8 maven3+ Hbase0.94+ | Java 6,7,8 maven3.0+ nodejs zookeeper elasticsearch |
實現方式 | 代碼埋點(攔截器,註解,過濾器等) | 攔截請求,發送(HTTP,mq)數據至zipkin服務 | java探針,字節碼加強 | java探針,字節碼加強 |
存儲 | mysql , hdfs | in-memory , mysql , Cassandra , Elasticsearch | HBase | elasticsearch , H2 |
jvm監控 | 不支持 | 不支持 | 支持 | 支持 |
trace查詢 | 支持 | 支持 | 須要二次開發 | 支持 |
stars | 5.5k | 9.1k | 6.5k | 4k |
侵入 | 高,須要埋點 | 高,須要開發 | 低 | 低 |
部署成本 | 中 | 中 | 較高 | 低 |
基於對應用盡量的低侵入考慮,以上方案選型優先級pinpoint>skywalking>zipkin>cat。
基於咱們的選型,重點關注pinpoint和skywalking。
2.3.1 google dapper 主流的分佈式調用鏈跟蹤技術大都和google dapper類似。簡單介紹下dapper原理:
span 基本工做單元,一次鏈路調用(能夠是RPC,DB等沒有特定的限制)建立一個span,經過一個64位ID標識它,uuid較爲方便,span中還有其餘的數據,例如描述信息,時間戳,key-value對的(Annotation)tag信息,parent_id等,其中parent-id能夠表示span調用鏈路來源。
上圖說明了span在一次大的跟蹤過程當中是什麼樣的。Dapper記錄了span名稱,以及每一個span的ID和父ID,以重建在一次追蹤過程當中不一樣span之間的關係。若是一個span沒有父ID被稱爲root span。全部span都掛在一個特定的跟蹤上,也共用一個跟蹤id。 trace 相似於 樹結構的Span集合,表示一次完整的跟蹤,從請求到服務器開始,服務器返回response結束,跟蹤每次rpc調用的耗時,存在惟一標識trace_id。好比:你運行的分佈式大數據存儲一次Trace就由你的一次請求組成。
每種顏色的note標註了一個span,一條鏈路經過TraceId惟一標識,Span標識發起的請求信息。樹節點是整個架構的基本單元,而每個節點又是對span的引用。節點之間的連線表示的span和它的父span直接的關係。
總體部署結構:
2.3.2 pinpoint
2.3.3 skywalking
以上幾種方案數據採集端都採用了字節碼加強技術,原理以下:
在類加載的過程當中,執行main方法前,會先執行premain方法來加載各類監控插件,從而在運行時實現整個鏈路的監控。下面重點介紹pinpoint部署,目前咱們線上是集羣部署,總體架構以下:
機器 | 部署應用 |
---|---|
master | zookeeper,hadoop,hbase,pinpoint-collector |
node1 | zookeeper,hadoop,hbase |
node2 | zookeeper,nginx,hadoop,hbase,pinpoint-web,pinpoint-collector |
搭建pinpoint線上用了三臺服務器,master、node一、node2。應用數據採集端agent-client將採集到的數據經過udp發送到部署在node2的nginx,經過負載均衡分流到兩臺pinpoint-collector服務器,落庫經過hadoop集羣master節點負載均衡到兩臺hbase服務器上。
pinpoint編譯條件比較苛刻,須要jdk6,7,8環境。
集羣部署,須要先搭建hadoop集羣,hbase集羣。搭建完成後初始化表,執行 ./hbase shell /pinpoint-1.7.2/hbase/scripts/hbase-create.hbase,能夠根據本身對歷史數據的需求設置表的ttl時間。
/pinpoint-1.7.2/web/target/pinpoint-web-1.7.2.war拷貝到tomcat webapps目錄下 修改tomcat目錄/webapps/pinpoint-web-1.7.2/WEB-INF/classes/hbase.properties hbase配置啓動
/pinpoint-1.7.2/collector/target/pinpoint-collector-1.7.2.war拷貝到tomcat webapps目錄下,修改tomcat目錄/webapps/pinpoint-collector-1.7.2/WEB-INF/classes/hbase.properties和pinpoint-collector.properties配置並啓動
將/pinpoint-1.7.2/agent整個目錄拷貝到應用服務器指定目錄下修改/agent/target/pinpoint-agent-1.7.2/pinpoint.config配置業務應用啓動時增長參數-javaagent:/root/agent/target/pinpoint-agent-1.7.2/pinpoint-bootstrap-1.7.2.jar -Dpinpoint.agentId=application01 -Dpinpoint.applicationName=application
具體集羣部署能夠參考: blog.csdn.net/yue530tomto…
須要注意: 默認配置的日誌級別是DEBUG,會產生海量日誌,要將其修改爲INFO級別
首頁能看到應用的拓撲信息,接口調用的成功失敗數,響應時間等。
能夠查看具體的某一次請求的整個調用鏈路信息 能夠查看jvm相關信息 針對某個慢請求,咱們能夠經過pinpoint跟蹤整個調用鏈,從而定位慢在哪裏。年糕媽媽--洛特