對系統行為進行監控的過程非常的復雜,特別是在分布式系統中。為了理解這種復雜性,首先來看如圖2-30所示的一個過程。
在圖中,用戶發出一個請求X,它期待得到系統對它做出的應答X。但是接收到該請求的前端A發現該請求的處理需要涉及服務器B和服務器C,因此A又向B和C發出兩個RPC (遠程過程調用)。B收到后立刻做出晌應,但是C在接到后發現它還需要調用服務器D和E才能完成請求X,因此C對D和E分別發出了RPC, D和E接到后分別做出了應答,收到D和E的應答之后C才向A做出響應,在接收到B和C的應答之后A才對用戶請求X做出一個應答X。在監控系統中記錄下所有這些消息不難,如何將這些消息記錄同特定的請求(本例中的X)關聯起來才是分布式監控系統設計中需要解決的關鍵性問題之一。

一般來說,有兩種方案可供選擇:黑盒(Black Box)方案及基于注釋的監控 (Annotation-based Monitoring)方案。二者比較而言,黑盒方案比較輕便,但是在消息關系判斷的過程中,黑盒方案主要是利用一些統計學的知識來進行推斷,有時不是很準確。基于注釋的方案利用應用程序或中間件給每條記錄賦予一個全局性的標示符,借此將相關消息串聯起來。考慮到實際的需求,Google的工程師最終選擇了基于注釋的方案,為了盡可能消除監控系統的應用程序對被監控系統的性能產生的不良影響,Google的工程師設計并實現了一套輕量級的核心功能庫,這將在后面進行介紹。
Dapper監控系統中有三個基本概念:監控樹(Trace Tree)、區間(Span)和注釋 (Annotation)。如圖2-31所示是一個典型的監控樹,從中可以看到所謂的監控樹實際上就是一個同特定事件相關的所有消息,只不過這些消息是按照一定的規律以樹的形式組織起來。樹中的每一個節點稱為一個區間,區間實際上就是一條記錄,所有這些記錄聯系在一起就構成了對某個事件的完整監控。從圖2-31不難看出,每個區間包括如下的內蓉:區間名(Span Name)、區間id(Span id)、父id(Parent id)和監控id(Trace id)。區間名主要是為了方便人們記憶和理解,因此要求這個區間名是人們可以讀懂的。區間id是為了在一棵監控樹中區分不同的區間。父id是區間中非常重要的一個內容,正是通過父id才能夠對樹中不同區間的關系進行重建,沒有父id的區間稱為根區間(Root Span)。圖2-31中的Fnmtend Request就是一個根區間。在圖中還能看出,區間的長度實際上包括了區間的開始及結束時間信息。
監控id在圖2-31中并沒有列出,一棵監控樹中所有區間的監控id是相同的,這個監控id是隨機分配的,且在整個Dapper監控系統中是唯一的。正如區間id是用來在某個監控樹中區分不同的區間一樣,監控id是用來在整個Dapper監控系統中區分不同的監控。注釋主要要用來輔助推斷區間關系,也可以包含一些自定義的內容。圖2-32展示了圖2-31中Helper.Call區間的更詳細信息。
在圖2-32中可以清楚地看到這個區間的區間名是“Helper.Call”,監控id是100,區間id是5,父id是3。一個區間既可以只有一臺主機的信息,也可以包含來源于多個主機的信息;事實上,每個RPC區間都包含來自客戶端(Client)和服務器租用端(Server)的注釋,這使得雙主機區間(Two-host Span)成為最常見的一種。圖2-32中的區間就包含了來自客戶端的注釋信息:“<Start>”、
“Client Send”、“Client Recv” 和“<End>”,也包含了來自服務器端的注釋信息:“Server Recv”、“foo”和“Server Send”。除了“foo”是用戶自定義的注釋外,其他的注釋信息都是和時間相關的信息。Dapper不但支持用戶進行 簡單的文本方式的注釋,還支持鍵-值對方式的注釋,這賦予了開發者更多的自由。
“Client Send”、“Client Recv” 和“<End>”,也包含了來自服務器端的注釋信息:“Server Recv”、“foo”和“Server Send”。除了“foo”是用戶自定義的注釋外,其他的注釋信息都是和時間相關的信息。Dapper不但支持用戶進行 簡單的文本方式的注釋,還支持鍵-值對方式的注釋,這賦予了開發者更多的自由。

2.監控信息的匯總
Dapper對幾乎所有的Google后臺服務器進行監控。海量的消息記錄必須通過一定的方式匯集在一起才能產生有效的監控信息。在實際中,Dapper監控信息的匯總需要經過三個步驟,如圖2-33所示。
(1)將區間的數據被寫入到本地的日志文件。
(2)利用Dapper守護進程(Dapper daemon)和Dapper收集器(Dapper Collectors)將所有機器上的本地日志文件匯集在一起。
(3)將匯集后的數據寫入到Bigtable存儲庫中。
從圖中也很容易地看出,(1)和(2)是一個讀的過程,而(3)是一個寫的過程。選擇Bigtable主要是因為區間的數目非常多,而且各個區間的長度變化很大,Bigtable對于這種很松散的表結構能夠很好地進行支持。寫入數據后的Bigtable中,單獨的一行表示一個記錄,而一列則相當于一個區間。這些監控數據的匯總是單獨進行的,而不是伴隨系統對用戶的應答一起返回的。如此選擇主要有如下的兩個原因:首先,一個內置的匯總方案 (監控數據隨RPC應答頭返回)會影響網絡動態。一般來說,RPC應答數據規模比較的小,通常不超過10kb。而區間數據往往非常的龐大,如果將二者放在一起傳輸,會使這些RPC應答數據相對“矮化”進而影響后期的分析。另一方面,內置的匯總方案需要保證所有的RPC都是完全嵌套的,但有許多的中間件系統在其所有的后臺返回最終結果之前就對調用者返回結果,這樣有些監控信息就無法被收集。基于這兩個考慮,Google選擇將監控數據和應答信息分開傳輸。

安全問題是所有系統都必須考慮的問題,為了防止未授權用戶對于RPC信息的訪問,信息匯總過程中Dapper只存儲RPC方法的名稱卻不存儲任何RPC負載數據,取而代之的是,應用層注釋提供了一種方便的選擇機制(OPt-in Mechanism):應用程序開發者可以將任何對后期分析有益的數據和區間關聯起來。