[說明:本文是閱讀Google論文「Dapper, a Large-Scale Distributed Systems Tracing Infrastructure」以後的一個簡要總結,完整譯文可參考此處。 另論文「Uncertainty in Aggregate Estimates from Sampled Distributed Traces」中有關於採樣的更詳細分析。此外,Twitter開源的Zipkin就是參考Google Dapper而開發。]html
Dapper最初是爲了追蹤在線服務系統的請求處理過程。好比在搜索系統中,用戶的一個請求在系統中會通過多個子系統的處理,並且這些處理是發生在不一樣機器甚至是不一樣集羣上的,當請求處理髮生異常時,須要快速發現問題,並準肯定位到是哪一個環節出了問題,這是很是重要的,Dapper就是爲了解決這樣的問題。前端
對系統行爲進行跟蹤必須是持續進行的,由於異常的發生是沒法預料的,並且多是難以重現的。同時跟蹤須要是無所不在,遍及各處的,不然可能會遺漏某些重要的點。基於此Dapper有以下三個最重要的設計目標:低的額外開銷,對應用的透明性,可擴展。同時產生的跟蹤數據須要能夠被快速分析,這樣能夠幫助用戶實時獲取在線服務狀態。git
實現方法github
低的額外開銷:不是對全部的請求進行跟蹤而是要採樣,收集跟蹤數據時進行二次採樣web
對應用的透明性:修改多線程,控制流,RPC等基礎庫代碼,插入負責跟蹤的代碼。在Google,應用使用的是相同的多線程,控制流,RPC等基礎庫代碼,因此僅經過修改它們就能夠實現跟蹤功能。當線程進行一個被跟蹤的處理時,Dapper會將一個trace context關聯到線程本地化存儲中。trace context中包含了span相關屬性,好比trace和span id。對於須要進行異步處理的狀況,Google開發者一般都會採用一個通用的控制流庫來實現回調,並將它們調度到一個線程池或是執行器中調用。Dapper保證全部回調都會保存它們建立者的trace context,同時在該回調被調用時該trace context也會被關聯到對應線程。這樣,Dapper就能夠實現這種異步處理過程的跟蹤。對於被跟蹤的RPC調用,span和trace id也會跟着被從客戶端傳到服務端。從功能上看這部分代碼主要包括span的建立,採樣,本地磁盤日誌寫入,可是由於它會被不少應用所依賴,維護和bug fix難度高,須要很是高的穩定性和健壯性。同時還要輕量,實際上這部分代碼的C++實現也總共不到1000行。緩存
Dapper支持用戶直接獲取Tracer對象,並輸出本身的自定義信息,用戶能夠輸出本身任意想輸出的內容,爲防止用戶過分輸出,提供用戶可配置參數來控制其上限。服務器
跟蹤時須要對請求進行標記,會產生一個惟一ID(在Dapper中是一個64位整數)用來標識該請求。對於Dapper來講,一個trace(跟蹤過程)其實是一顆樹,樹中的節點被稱爲一個span,根節點被稱爲root span。以下圖所述:網絡
須要注意的是一個span可能包含來自多個主機的信息;實際上每一個RPC span包含了來自客戶端和服務端的信息。可是客戶端和服務端的時鐘是有誤差的,文中並未指明如何解決這個問題,只是說能夠利用以下事實"RPC的發起客戶端是在服務端接收到前進行地,針對該RPC的響應則是由服務端在客戶端收到前發出的",肯定時間戳的一個上下界。多線程
Dapper的整個數據收集過程以下圖所示:首先將span數據寫入本地日誌文件,而後將數據收集並寫入Bigtable,每一個trace記錄將會被做爲表中的一行,Bigtable的稀疏表結構很是適合存儲trace記錄,由於每條記錄可能有任意個的span。整個收集過程是out-of-band的,與請求處理是徹底不相干的兩個獨立過程,這樣就不會影響到請求的處理。若是改爲in-band的,即將trace數據與RPC響應報文一塊發送回來,會影響到應用的網絡情況,同時RPC調用也有可能不是完美嵌套的,某些RPC調用可能會在它自己依賴的那些返回前就提早返回了。app
Dapper提供API容許用戶直接訪問這些跟蹤數據。Google內部開發人員能夠基於這些API開發通用的或者面向具體應用的分析工具。這一點,對於提升Dapper的做用和影響力起到了出人意料的效果。
跟蹤開銷
若是跟蹤帶來的額外開銷過高,用戶一般會選擇關掉它,所以低開銷很是重要。採樣能夠下降開銷,可是簡單的採樣可能致使採樣結果無表明性,Dapper經過採用自適應的採樣機制來知足性能和表明性兩方面的需求。
trace的生成開銷對Dapper來講是最關鍵的,由於數據的收集和分析能夠臨時關掉,只要數據一直生成就能夠後面再進行收集分析。在trace生成中,最大頭的地方在於span和annotation的建立和銷燬上。根span的建立和銷燬平均須要204ns,普通span只須要176ns,區別在於根span須要產生一個全局惟一的trace id。若是span沒有被採樣到,那麼對它添加annotation的開銷基本可忽略,大概須要9ns。可是若是是被採樣到的話,那麼平均開銷是40ns。這些測試是在一個2.2GHZ的x86服務器上進行的。本地磁盤寫入是Dapper運行庫中最昂貴的操做,可是它們能夠異步化,批量化,所以它們基本上只會影響到那些高吞吐率的應用。
將跟蹤數據經過Dapper後臺進程讀出也會產生一些開銷。可是根據咱們的觀察,Dapper daemon的CPU開銷始終在0.3%之下,內存佔用也不多,此外也會帶來輕量的網絡開銷,可是每一個span平均只有426字節大小,網絡開銷只佔了整個產品系統流量的0.01%不到。
對於那些每一個請求均可能產生大量跟蹤數據的應用來講,咱們還會經過採樣來下降開銷。咱們經過保證跟蹤開銷能夠始終保持在很低的水平上,使得用戶能夠放心大膽的使用它。最初咱們的採樣策略很簡單,是在每1024個請求中選擇一個進行跟蹤,這種模式對於那種請求量很高的服務來講,能夠保證跟蹤到有價值的信息,可是對於那些負載不高的服務來講,可能會致使採樣頻率太低,從而遺漏重要信息。所以咱們決定以時間爲採樣單位,保證單位時間內能夠進行固定次數的採樣,這樣採樣頻率和開銷都更好控制了。
應用
用戶能夠經過DAPI(Dapper「Depot API」)直接訪問跟蹤數據。DAPI提供以下幾種訪問方式:指定trace id進行訪問;大規模批量訪問,用戶能夠經過MapReduce job並行訪問,用戶只須要實現一個以Dapper trace爲參數的虛函數,在該函數內完成本身的處理便可,框架負責爲用戶指定時間段內的全部trace調用該函數;經過索引進行訪問,Dapper會爲跟蹤數據創建索引,用戶可經過索引進行查詢,由於trace id是隨機生成的,所以用戶一般須要經過服務名或機器名進行檢索(實際上Dapper是按照(服務名,機器名,時間戳)進行索引的)。
大部分用戶都是經過一個交互式web接口來使用Dapper的,典型流程以下圖所示:
1.用戶輸入他感興趣的服務和時間窗口,選擇相應跟蹤模式(這裏是span名稱),以及他最關心的某個度量參數(這裏是服務延遲)
2.頁面上會展現一個指定服務的全部分佈式執行過程的性能摘要,用戶可能會對這些執行過程根據須要進行排序,而後選擇一個詳細看
3.一旦用戶選定了某個執行過程後,將會有一個關於該執行過程的圖形化描述展示出來,用戶能夠點擊選擇本身關心的那個過程
4.系統根據用戶在1中選擇的度量參數,以及3中選擇的具體過程,顯示一個直方圖。在這裏顯示的是有關getdocs的延遲分佈的直方圖,用戶能夠點擊右側的example,選擇具體的一個執行過程進行查看
5.顯示關於該執行過程的具體信息,上方是一個時間軸,下方用戶能夠進行展開或摺疊,查看該執行過程各個組成部分的開銷,其中綠色表明處理時間,藍色表明花在網絡上的時間。
經驗教訓
在開發過程當中使用Dapper,能夠幫助用戶提升性能(分析請求延遲,發現關鍵路徑上沒必要要的串行化),進行正確性檢查(用戶請求是否正確發送給了服務提供者),理解系統(請求處理可能依賴不少其餘系統,Dapper幫助用戶瞭解整體延遲,從新設計最小化依賴),測試(新代碼release前要經過一個Dapper跟蹤測試,驗證系統行爲和性能)。好比,經過使用Dapper,Ads Review團隊將延遲下降了兩個數量級。
同時咱們還將跟蹤系統與異常監控系統進行集成,若是異常發生在一個採樣到的Dapper tracer上下文中,那麼相應的trace和span id還會被做爲異常報告的元數據,前端的異常監控服務還會提供相應連接指向跟蹤系統。這樣能夠幫助用戶瞭解異常發生時的狀況。
解決長尾延遲,幫助用戶分析複雜系統環境下的延遲問題。網絡性能的瞬時降低不會影響系統的吞吐率,可是對延遲有很大影響。不少開銷昂貴的查詢模式是因爲未預料到的服務間的交互形成的,Dapper的出現使得這種問題的發現變得很是容易。
幫助用戶進行服務間依賴的推理。Google維護了很是多的集羣,每一個集羣上承載了各類各樣的任務,而任務間可能存在依賴關係。可是各個任務須要精確知道它所依賴的服務信息,以幫助發現瓶頸或進行服務的移動。服務間的依賴是複雜的並且是動態的,單純依賴配置文件很難判斷。可是經過使用Dapper的trace信息和DAPI MapReduce接口能夠自動化的肯定服務間依賴關係。
幫助網絡管理員對跨集羣的網絡活動進行應用層的分析。幫助發現某些昂貴的網絡請求開銷的產生緣由。
不少存儲系統都是共享的。好比GFS,會有不少用戶,有的多是直接訪問GFS,有的多是好比經過Bigtable產生對GFS的訪問,若是沒有Dapper,對這種共享式系統將會很難調試,經過Dapper提供的數據,共享服務的owner能夠方便的對用戶根據各項指標(好比網絡負載,請求耗時)進行排序。
救火。可是不是全部的救火,均可以使用Dapper來完成。好比,那些正在救火的Dapper用戶須要訪問最新的數據,可是他可能根本沒有時間寫一個新的DAPI代碼或者等待週期性報告的產生。對於那些正在經歷高延遲的服務,Dapper的用戶接口並不適於用來快速定位延遲瓶頸。可是能夠直接與Dapper daemon進行交互,經過它能夠很容易地收集最新數據。在發生災難性的故障時,一般沒有必要去看統計結果,單個例子就能夠說明問題。可是對於那些共享存儲服務來講,信息聚合會很重要。對於共享服務來講,Dapper的聚合信息能夠用來作過後分析。可是若是這些信息的聚合沒法在問題爆發後的10分鐘以內完成,那麼它的做用將會大大削弱,所以對於共享服務來講,Dapper在救火時的效果沒有想象中的那麼好。
經過開放跟蹤數據給用戶,激發了用戶創造力,產生了不少意料以外的應用。那些沒有跟蹤功能的應用只須要用新的庫從新編譯下它們的程序,就得到了跟蹤功能,遷移很是方便。
可是還存在以下一些須要改進的地方:
合併產生的影響。咱們一般假設各類子系統會一次處理一個請求。可是某些狀況下,請求會被緩存,而後一次性地在一組請求上執行某個操做(好比磁盤寫入)。在這種狀況下,被追蹤的請求拿到的實際上並非它自己的處理過程。
對批量處理進行跟蹤。雖然Dapper是爲在線服務系統而設計,最初是爲了理解用戶發送請求給Google後產生的一系列系統行爲。可是離線的數據處理實際上也有這樣的需求。在這種狀況下,咱們能夠將trace id與某些有意義的工做單元進行關聯,好比輸入數據裏的一個key(或者是一個key range)。
尋找根本緣由。Dapper能夠迅速找到系統短板,可是在尋找根本緣由時並不那麼高效。好比某個請求變慢可能並非由於它本身的緣由,而是由於在它以前已經有不少請求在排隊。用戶能夠在應用層將某些參數,好比隊列大小交給跟蹤系統。
記錄內核級信息。咱們有不少工具能夠進行內核執行過程的跟蹤和profiling,可是直接將內核級信息捆綁到一個應用層的trace context中很難優雅的實現。咱們打算採用一種折中的解決方案,經過在應用層獲取內核活動參數的快照,而後將它們與一個活動span關聯起來。
http://bigbully.github.io/Dapper-translation/