1.相關(guān)概念
如下在Cat中我們稱為: logview消息樹(或者M(jìn)essageTree),即應(yīng)用內(nèi)部的調(diào)用鏈路∠穹可參考:http://www.reibang.com/p/855207522411
2.整體設(shè)計(jì)介紹
Cat整體處理流程如下圖所示:
(cat-server對(duì)應(yīng)開源的cat-consumer模塊)
1. 業(yè)務(wù)接入cat-client倡蝙,進(jìn)行Cat埋點(diǎn)
2. cat-client將埋點(diǎn)信息,組織成logview消息樹踢步,上報(bào)到后端cat-sever服務(wù)器(通過tcp方式上報(bào))
3. cat-server端接受到logview癣亚,將logview放到不同的線程里進(jìn)行處理
3.1 Transaction線程,提取logview中transaction數(shù)據(jù)获印,構(gòu)造transaction報(bào)表述雾。
3.2 Event線程,提取logview中event數(shù)據(jù)兼丰,構(gòu)造event報(bào)表
3.3 Problem線程玻孟,提取logview中problem數(shù)據(jù),構(gòu)造problem報(bào)表
3.4 Logview線程鳍征,將logview進(jìn)行存儲(chǔ)黍翎。
4. 查詢端通過指定報(bào)表,查詢相應(yīng)數(shù)據(jù)艳丛。
3.logView樹構(gòu)造
logView樹(也叫 消息樹匣掸、messageTree):在同一個(gè)線程里,通過transaction容器氮双,將Cat產(chǎn)生的各個(gè)埋點(diǎn)進(jìn)行串聯(lián)碰酝,形成的進(jìn)程內(nèi)部的調(diào)用鏈路。
埋點(diǎn)示例一:
public void test() throws InterruptedException {
Cat.initializeByDomain("cat.test");
Transaction t1 = Cat.newTransaction("method", "test");
try{
test2("dpg");
}catch (Exception e){
Cat.logError(e);
t1.setStatus(e); //設(shè)置transaction狀態(tài)
}finally {
t1.complete(); //transaction一定要complete戴差,一般放到finally來保證
}
//cat數(shù)據(jù)上報(bào)是異步的送爸,需要sleep一段時(shí)間等待數(shù)據(jù)上報(bào),在結(jié)束進(jìn)程
Thread.sleep(3000);
}
private void test2(String param) throws Exception {
Cat.logEvent("test2param", param);
Transaction t2 = Cat.newTransaction("method2", "test2");
//中間執(zhí)行業(yè)務(wù)邏輯
Thread.sleep(10);
t2.setSuccessStatus();
t2.complete();
throw new Exception("error");
}
生成的logView如下所示:
埋點(diǎn)示例二:
public void test() throws InterruptedException {
Cat.initializeByDomain("cat.test");
Transaction t1 = Cat.newTransaction("method", "test");
try{
test2("dpg");
}catch (Exception e){
Cat.logError(e);
t1.setStatus(e); //設(shè)置transaction狀態(tài)
}finally {
t1.complete(); //transaction一定要complete,一般放到finally來保證
}
Transaction t3 = Cat.newTransaction("DbMethod", "DbTest");
Cat.logEvent("db", "mysql");
t3.complete();
//cat數(shù)據(jù)上報(bào)是異步的袭厂,需要sleep一段時(shí)間等待數(shù)據(jù)上報(bào)墨吓,在結(jié)束進(jìn)程
Thread.sleep(3000);
}
private void test2(String param) throws Exception {
Cat.logEvent("test2param", param);
Transaction t2 = Cat.newTransaction("method2", "test2");
//中間執(zhí)行業(yè)務(wù)邏輯
Thread.sleep(10);
t2.setSuccessStatus();
t2.complete();
throw new Exception("error");
}
會(huì)生成2個(gè)logView樹,如下圖所示:
4.原理解析
你的埋點(diǎn)會(huì)被cat-cliet 串街成一個(gè)logview消息樹纹磺,具體實(shí)現(xiàn)原理如下:
(t:代表transaction肛真, E:代表Event)(t:transaction開始,T:transaction結(jié)束)
1. 我們使用ThreadLocal變量爽航,當(dāng)new 一個(gè)Transaction1蚓让,會(huì)向stack push,并構(gòu)造 Logview 消息樹
2. Transaction2 new的時(shí)候讥珍,也會(huì)向stack push历极,并將Trasaction2 放到 當(dāng)前l(fā)ogView 消息樹中
3. Event new的時(shí)候,會(huì)將Event放到當(dāng)前l(fā)ogView消息樹中
4. Transaction2 complete衷佃,會(huì)從stack堆棧pop趟卸,并記錄transaction2的耗時(shí)時(shí)間、狀態(tài)氏义。
5. Transactin1 complete锄列,會(huì)從stack堆棧pop,并記錄transaction1的耗時(shí)時(shí)間惯悠、狀態(tài)邻邮。發(fā)現(xiàn)此時(shí)當(dāng)前stack為空,就會(huì)將當(dāng)前構(gòu)建好的Logview消息樹發(fā)送出去
6. 當(dāng)代碼流程在有Cat 埋點(diǎn)克婶,就會(huì)走上面同樣邏輯筒严。 (最頂層通過Transaction樹組織起來)
4.1 注意
1. Transaction一定要complete,并且不能跨線程comoplete(transaction的生成和complete需要在同一個(gè)線程里)
a. transaction只有complete我們才會(huì)發(fā)送整個(gè)logview消息樹情萤,如果你不complete鸭蛙,消息樹就不會(huì)發(fā)送,擠壓在內(nèi)存里筋岛,造成監(jiān)控?cái)?shù)據(jù)不準(zhǔn)娶视。進(jìn)而引發(fā)內(nèi)存泄漏風(fēng)險(xiǎn)。
b. 由于cat-client內(nèi)部使用ThreadLocal變量睁宰,所以需要在同一個(gè)線程進(jìn)行new 和complete肪获,否則complete會(huì)失效。
2. transaction complete順序勋陪,和new的順序相反贪磺。最先new的transaction,最后complete
a. transaction是一個(gè)堆棧結(jié)構(gòu)诅愚,需要complete 和new 相互對(duì)應(yīng)。
b. 如果,new的順序是 a b c违孝。 complete的順序 也是 a b c刹前。那么在complete a的時(shí)候,此時(shí)堆棧內(nèi)容是 c雌桑、b喇喉、a,
b1. 我們會(huì)從stack pop一個(gè)對(duì)象校坑,發(fā)現(xiàn)是c拣技,不是a本身,就會(huì)把c complete耍目。
b2. 繼續(xù)stack pop一個(gè)對(duì)象膏斤,發(fā)現(xiàn)是 b,不是a本身邪驮,就會(huì)把 b complete莫辨。
b3. 繼續(xù)stack pop 一個(gè)對(duì)象,發(fā)現(xiàn)是a本身毅访,就會(huì)把a(bǔ) complete
b4. 之后b沮榜、c complete,就不會(huì)產(chǎn)生任何動(dòng)作喻粹。
因?yàn)閎蟆融、c被提前complete,所以會(huì)導(dǎo)致耗時(shí)統(tǒng)計(jì)不準(zhǔn)守呜。
5. 采樣與聚合
1. 使用Cat埋點(diǎn)振愿,cat會(huì)在客戶端將埋點(diǎn)串成logview消息樹上報(bào)到cat后端,進(jìn)而解析成相應(yīng)報(bào)表弛饭。
2. 當(dāng)應(yīng)用流量變得很大冕末,埋點(diǎn)生成消息樹也會(huì)成爆發(fā)行增長(zhǎng),為了不影響客戶端機(jī)器性能侣颂,以及降低網(wǎng)絡(luò)流量档桃,我們會(huì)對(duì)cat埋點(diǎn)的消息樹,進(jìn)行采樣聚合憔晒。
采樣:只將部分logview 原樣上報(bào)藻肄,減少上報(bào)量。比如采樣率:1%拒担, 100個(gè)logview消息樹嘹屯,只會(huì)上報(bào)一個(gè)。
聚合:將未被采樣到的logview消息樹从撼,進(jìn)行聚合州弟、編碼變成一個(gè)消息樹 上報(bào),保證報(bào)表統(tǒng)計(jì)的準(zhǔn)確性。
如下圖所示婆翔,cat-client會(huì)將近3s未被采樣到的logview消息樹拯杠,聚合成一個(gè)消息樹。(被采樣到的logview會(huì)被原樣上報(bào))
在生成的新消息樹里啃奴,會(huì)統(tǒng)計(jì)每個(gè)transaction/event 次數(shù)潭陪、耗時(shí)。這樣就可以把很多l(xiāng)ogview 合并成一個(gè)logview最蕾,極大減少了網(wǎng)絡(luò)流量和消息量依溯。(多個(gè)logview被聚合成一個(gè)logview上報(bào),丟失了調(diào)用鏈路關(guān)系瘟则,但是指標(biāo)數(shù)量信息沒有丟失)
5.1 如何判斷一個(gè)鏈路是否被聚合
比如在Cat上查看一個(gè)鏈路黎炉,如下所示:
最頂層的埋點(diǎn)是System TransactionAggregator 或者 System EventAggregator 或則System _CatMergeTree(低版本) 則代表這個(gè)鏈路是被聚合的,不是真實(shí)的鏈路壹粟。 聚合的鏈路只是將:埋點(diǎn)的Transaction和Event 次數(shù)信息統(tǒng)計(jì)上報(bào)上來拜隧,為了保證Transaction、Event報(bào)表統(tǒng)計(jì)的準(zhǔn)確性趁仙,不代表真實(shí)的鏈路
5.2 如何查看被采樣到的鏈路
problem報(bào)表都是采樣到的鏈路洪添。對(duì)于耗時(shí)長(zhǎng)(比如耗時(shí)長(zhǎng)的rpc、db雀费、緩存調(diào)用)干奢、失敗的鏈路我們會(huì)在problem報(bào)表展示,所以在problem報(bào)表可以看到有問題的采樣鏈
6.截?cái)?/h1>
對(duì)于采樣命中的logview盏袄,我們會(huì)將整個(gè)logview發(fā)送到cat后端忿峻,進(jìn)行存儲(chǔ)。
對(duì)于有些業(yè)務(wù)辕羽,單個(gè)logview大小可能很多(比如:在一個(gè)循環(huán)里加了cat埋點(diǎn))逛尚,為了保證logview整個(gè)傳輸存儲(chǔ)的成功率,我們會(huì)對(duì)logview大小進(jìn)行限制刁愿。
如果發(fā)現(xiàn)logview埋點(diǎn)個(gè)數(shù)(Transaction+Event數(shù)量)超過2000绰寞,我們會(huì)將logview進(jìn)行截?cái)啵?000個(gè)大小作為限制進(jìn)行截?cái)唷?/p>
如果點(diǎn)擊logview如上圖所示铣口,有個(gè) RootLogview滤钱、ParentLogview的超鏈接,那么這個(gè)logview就是被截?cái)嗟摹?/p>
比如原始的logview脑题,因?yàn)檫^長(zhǎng)被截成5段件缸。
其中:RootLogview:鏈接到第一段logview
ParentLogview:連接到上一個(gè)被截?cái)嗪蟮膌ogview
6.1 截?cái)嘣?/strong>
a. 如上圖所示,當(dāng)前我們處于1這個(gè)階段叔遂,此時(shí)內(nèi)存構(gòu)造的logview樹狀態(tài)是A(其中他炊,t:代表 開始一個(gè)Transaction争剿,T:代表結(jié)束一個(gè)Transaction,E:代表一個(gè)Event或者Error)
b. 當(dāng)我們繼續(xù)new 一個(gè)Transctioin t7時(shí)佑稠,會(huì)檢查當(dāng)前內(nèi)存中l(wèi)ogview的長(zhǎng)度秒梅,如果長(zhǎng)度超過2000限制(即:Transaction + Event + Error 數(shù)量)旗芬,開始對(duì)logview進(jìn)行截?cái)嗌嘟海M(jìn)入階段2
c. 在階段2,我們會(huì)把已經(jīng)complete的Transaction(t3疮丛、t4)打包在一起幔嫂,構(gòu)造一個(gè)新的完整logview A1 發(fā)送(當(dāng)然發(fā)送是異步,不會(huì)阻塞進(jìn)程)
d. 沒有complete的Transaction(t1誊薄、t2履恩、t5、t6)會(huì)再次組裝在一起呢蔫,變成A2切心,繼續(xù)留在內(nèi)存里 ,這樣當(dāng)new Transaction t7片吊,t7被加入到A2中绽昏,進(jìn)入到階段3,繼續(xù)等待新的埋點(diǎn)數(shù)據(jù)加入
e. 直到整個(gè)t1 這個(gè)transaction complete俏脊,整個(gè)logview才會(huì)發(fā)送走
6.2 截?cái)鄦栴}
a. 統(tǒng)計(jì)次數(shù)不準(zhǔn):根據(jù)上面所述全谤,在截?cái)鄷r(shí), t1爷贫、t2认然、t5、t6 其實(shí)統(tǒng)計(jì)數(shù)據(jù)會(huì)不準(zhǔn)漫萄。因?yàn)樵贏1中發(fā)送過一次卷员,在A2中還會(huì)發(fā)送一次(當(dāng)然截?cái)啻螖?shù)越多,統(tǒng)計(jì)數(shù)值偏差越大)腾务。不過為了修復(fù)統(tǒng)計(jì)次數(shù)不準(zhǔn)毕骡,我們不會(huì)統(tǒng)計(jì)t1次數(shù),知道最后一次階段上報(bào)才會(huì)統(tǒng)計(jì)t1窑睁,其實(shí)這么做也只是解決了t1統(tǒng)計(jì)不準(zhǔn)確問題挺峡。具體為什么不修復(fù)其他transaction統(tǒng)計(jì)不準(zhǔn)問題,只能說太復(fù)雜担钮,不好搞
b. 耗時(shí)統(tǒng)計(jì)不準(zhǔn):被階段后橱赠,同一個(gè)transaction被放到2個(gè)logview里了(比如t2),這樣耗時(shí)就被拆分成2部分箫津,所以平均耗時(shí)狭姨,會(huì)偏低
6.3 截?cái)鄷r(shí)機(jī):
1. transaction+event+error數(shù)量超過 2000
2. logview 跨小時(shí)超過10s