使用Cat斷斷續續將近兩週的時間,感受它還算是很輕量級的。文檔相對來講薄弱一些,沒有太全面的官方文檔(官方文檔大可能是介紹每一個名詞是什麼意思,界面是什麼意思,部署方面比較欠缺);可是好在有一個很是活躍的羣,羣裏有不少經驗豐富的高手,不會的問題基本都能獲得解答。php
下面就開始步入正題吧,本篇主要講述一下如何利用Cat進行分佈式的調用鏈追蹤。java
分佈式開發基礎
在最開始網站基本都是單節點的,因爲業務逐漸發展,使用者開始增多,單節點已經沒法支撐了。因而開始切分系統,把系統拆分紅幾個獨立的模塊,模塊之間採用遠程調用的方式進行通訊。數據庫
那麼遠程調用是如何作到的呢?下面就用最古老的RMI的方式來舉個例子吧!服務器
RMI(Remote method invocation)是java從1.1就開始支持的功能,它支持跨進程間的方法調用。markdown
大致上的原理能夠理解爲,服務端會持續監聽一個端口。客戶端經過proxy代理的方式遠程調用服務端。即客戶端會把方法的參數以字符串的的方式序列化傳給服務端。服務端反序列化後調用本地的方法執行,執行結果再序列化返回給客戶端。網絡
服務端的代碼能夠參考以下:多線程
-
-
interface IBusiness extends Remote{
-
String echo(String message) throws RemoteException;
-
-
class BusinessImpl extends UnicastRemoteObject implements IBusiness {
-
public BusinessImpl() throws RemoteException {}
-
-
public String echo(String message) throws RemoteException {
-
-
-
-
-
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
-
IBusiness business = new BusinessImpl();
-
LocateRegistry.createRegistry(8888);
-
Naming.bind("rmi://localhost:8888/Business",business);
-
System.out.println("Hello, RMI Server!");
-
-
客戶端的代碼以下:app
-
IBusiness business = (IBusiness) Naming.lookup(
"rmi://localhost:8888/Business");
-
business.
echo("xingoo",ctx);
上面的例子就能夠實現客戶端跨進程調用的例子。框架
Cat監控
Cat的監控跟傳統的APM產品差很少,模式都是類似的,須要一個agent在客戶端進行埋點,而後把數據發送給服務端,服務端進行解析並存儲。只要你埋點足夠全,那麼它是能夠進行全面監控的。監控到的數據會首先按照某種規則進行消息的合併,合併成一個MessageTree,這個MessageTree會被放入BlockingQueue裏面,這樣就解決了多線程數據存儲的問題。jvm
隊列會限制存儲的MessageTree的個數,可是若是服務端掛掉,客戶端也有可能由於堆積大量的心跳而致使內存溢出(心跳是Cat客戶端自動向服務端發出的,裏面包含了jvm本地磁盤IO等不少的內容,因此MesssageTree挺大的)。
所以數據在客戶端的流程能夠理解爲:
Trasaction\Event-->MessageTree-->BlockingQueue-->netty發出網絡流
即Transaction、Event等消息會先合併爲消息樹,以消息樹爲單位存儲在內存中(並未進行本地持久化),專門有一個TcpSocketSender負責向外發送數據。
再說說服務端,服務端暫時看的不深,大致上能夠理解爲專門有一個TcpSocketReciever接收數據,因爲數據在傳輸過程當中是須要序列化的。所以接收後首先要進行decode,生成消息樹。而後把消息放入BlockingQueue,有分析器不斷的來隊列拿消息樹進行分析,分析後按照必定的規則把報表存儲到數據庫,把原始數據存儲到本地文件中(默認是存儲到本地)。
所以數據在服務端的流程大體能夠理解爲:
-
網絡流-->decode反序列化-->BlockingQueue-->analyzer分析--->報表存儲在DB
-
簡單的Transaction例子
在Cat裏面,消息大體能夠分爲幾個類型:
- Transaction 有可能出錯、須要記錄處理的時間的監控,好比SQL查詢、URL訪問等
- Event 普通的監控,沒有處理時間的要求,好比一次偶然的異常,一些基本的信息
- Hearbeat 心跳檢測,經常用於一些基本的指標監控,通常是一分鐘一次
- Metric 指標,好比有一個值,每次訪問都要加一,就可使用它
Transaction支持嵌套,便可以做爲消息樹的根節點,也能夠做爲葉子節點。可是Event、Heartbeat和Metric只能做爲葉子節點。有了這種樹形結構,就能夠描述出下面這種調用鏈的結果了:
Transaction和Event的使用很簡單,好比:
-
-
public @ResponseBody String test() {
-
Transaction t = Cat.newTransaction(
"MY-TRANSACTION","test in TransactionTest");
-
-
Cat.logEvent(
"EVENT-TYPE-1","EVENT-NAME-1");
-
-
-
-
-
-
-
-
t.setStatus(Transaction.SUCCESS);
-
-
-
return "trasaction test!";
-
這是一個最基本的Transaction的例子。
分佈式調用鏈監控
在分佈式環境中,應用是運行在獨立的進程中的,有多是不一樣的機器,或者不一樣的服務器進程。那麼他們若是想要彼此聯繫在一塊兒,造成一個調用鏈,就須要經過幾個ID進行串聯。這種串聯的模式,基本上都是同樣的。
舉個例子,A系統在aaa()中調用了B系統的bbb()方法,若是咱們在aaa方法中埋點記錄上面例子中的信息,在bbb中也記錄信息,可是這兩個信息是彼此獨立的。所以就須要使用一個全局的id,證實他們是一個調用鏈中的調用方法。除此以外,還須要一個標識誰在調用它的ID,以及一個標識它調用的方法的ID。
總結來講,每一個Transaction須要三個ID:
- RootId,用於標識惟一的一個調用鏈
- ParentId,父Id是誰?誰在調用我
- ChildId,我在調用誰?
其實ParentId和ChildId有點冗餘,可是Cat裏面仍是都加上吧!
那麼問題來了,如何傳遞這些ID呢?在Cat中須要你本身實現一個Context,由於Cat裏面只提供了一個內部的接口:
-
public interface Context {
-
String ROOT = "_catRootMessageId";
-
String PARENT = "_catParentMessageId";
-
String CHILD = "_catChildMessageId";
-
-
void addProperty(String var1, String var2);
-
-
String getProperty(String var1);
-
咱們須要本身實現這個接口,並存儲相關的ID:
-
public class MyContext implements Cat.Context,Serializable{
-
-
private static final long serialVersionUID = 7426007315111778513L;
-
-
private Map<String,String> properties = new HashMap<String,String>();
-
-
-
public void addProperty(String s, String s1) {
-
-
-
-
-
public String getProperty(String s) {
-
return properties.get(s);
-
-
因爲這個Context須要跨進程網絡傳輸,所以須要實現序列化接口。
在Cat中其實已經給咱們實現了兩個方法logRemoteCallClient
以及logRemoteCallServer
,能夠簡化處理邏輯,有興趣能夠看一下Cat中的邏輯實現:
-
-
public static void logRemoteCallClient(Cat.Context ctx) {
-
MessageTree tree = getManager().getThreadLocalMessageTree();
-
String messageId = tree.getMessageId();
-
-
messageId = createMessageId();
-
tree.setMessageId(messageId);
-
-
-
String childId = createMessageId();
-
logEvent(
"RemoteCall", "", "0", childId);
-
String root = tree.getRootMessageId();
-
-
-
-
-
ctx.addProperty(
"_catRootMessageId", root);
-
ctx.addProperty(
"_catParentMessageId", messageId);
-
ctx.addProperty(
"_catChildMessageId", childId);
-
-
-
-
public static void logRemoteCallServer(Cat.Context ctx) {
-
MessageTree tree = getManager().getThreadLocalMessageTree();
-
String messageId = ctx.getProperty("_catChildMessageId");
-
String rootId = ctx.getProperty("_catRootMessageId");
-
String parentId = ctx.getProperty("_catParentMessageId");
-
-
tree.setMessageId(messageId);
-
-
-
-
tree.setParentMessageId(parentId);
-
-
-
-
tree.setRootMessageId(rootId);
-
-
-
這樣,結合前面的RMI調用,整個思路就清晰多了.
客戶端調用者的埋點:
-
-
public @ResponseBody String test2() {
-
Transaction t = Cat.newTransaction(
"Call","test2");
-
-
Cat.logEvent(
"Call.server","localhost");
-
Cat.logEvent(
"Call.app","business");
-
Cat.logEvent(
"Call.port","8888");
-
-
MyContext ctx =
new MyContext();
-
Cat.logRemoteCallClient(ctx);
-
-
IBusiness business = (IBusiness) Naming.lookup(
"rmi://localhost:8888/Business");
-
business.echo(
"xingoo",ctx);
-
-
-
-
-
t.setStatus(Transaction.SUCCESS);
-
-
-
-
遠程被調用者的埋點:
-
interface IBusiness extends Remote{
-
String echo(String message,MyContext ctx) throws RemoteException;
-
-
class BusinessImpl extends UnicastRemoteObject implements IBusiness {
-
public BusinessImpl() throws RemoteException {}
-
-
public String echo(String message,MyContext ctx) throws RemoteException {
-
Transaction t = Cat.newTransaction(
"Service","echo");
-
-
Cat.logEvent(
"Service.client","localhost");
-
Cat.logEvent(
"Service.app","cat-client");
-
Cat.logRemoteCallServer(ctx);
-
System.out.println(message);
-
-
-
-
-
t.setStatus(Transaction.SUCCESS);
-
-
-
-
-
-
-
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
-
IBusiness business =
new BusinessImpl();
-
LocateRegistry.createRegistry(
8888);
-
Naming.bind(
"rmi://localhost:8888/Business",business);
-
System.out.println(
"Hello, RMI Server!");
-
-
![](http://static.javashuo.com/static/loading.gif)
須要注意的是,Service的client和app須要和Call的server以及app對應上,要否則圖表是分析不出東西的!
最後
Cat對於一些分佈式的開源框架,都有很好的集成,好比dubbo,有興趣的能夠查看它在script中的文檔,結合上面的例子能夠更好地理解。