分散式追蹤系統的對比、實現與使用—NodeTracing
在分散式領域,有一個十分令人頭疼的問題,那就是 分散式的追蹤 ,日誌與監控。因為服務部署在不同的主機上,而實際的業務開發中,服務之間相互呼叫,尤其是伴隨著 服務微小化,單位化,微服務架構的趨勢下服務數量激增 即便是有著良好的前期規劃,但是中大型專案在實際開發中,依然會面臨著服務關係錯綜複雜,問題追蹤除錯困難的等等問題
現狀
各類trace解決方案
那這個令人頭疼的問題,是否已經有良好的解決方案了呢?答案是肯定確又是否定的... 目前商用規模的分散式追蹤方案主要有以下幾類:
- 非OpenTacing標準的分散式追蹤系統
- 採用ServiceMesh架構的服務網格治理方案
- envoy
- istio ...
- 基於OpenTraing標準的嵌入式分散式追蹤系統
- zipkin
- jaeger
- skywalking ...
第1類是採用非標準化的追蹤系統,一般為針對單一業務系統設計實現,不具備廣泛通用性和持續維護性
第2類是從架構層考慮使用服務網格治理的方案來進行分散式追蹤, 優點是功能強大,控制緯度廣,追蹤精度細,追蹤覆蓋面大。但是缺點也很明顯,目前無論是第一代以envoy為代表的解決方案的ServiceMesh,還是第二代以istio為代表的ServiceMesh,都極為重量 ,需要專門的架構和運維團隊進行細緻的前期安排和規劃後方能實施,部署成本高昂
第3類是 採用OpenTracing標準的分散式追蹤系統 ,OpenTracing是CNCF(大名鼎鼎的Cloud Native Computing Foundation)為了規範業界的分散式跟蹤系統產品的統一正規化,設計的trace標準,基於該標準, 可以解決分散式追蹤系統跨平臺和相容通用的問題 。目前twitter、uber、apple等知名企業完全遵循該標準設計trace系統。
各大廠商trace系統對比
分散式跟蹤系統各類產品,根據設計目標和標準形成綜合對比*(資料來源於網際網路,非權威)*:
產品名稱 | 廠商 | 開源 | OpenTracing標準 | 侵入性 | 應用策略 | 時效性 | 決策支援 | 視覺化 | 低消耗 | 延展性 |
---|---|---|---|---|---|---|---|---|---|---|
jaeger | uber | 開源 | 完全支援 | 部分侵入 | 策略靈活 | 時效性高, UDP協議傳輸資料(在Uber任意給定的一個Jaeger安裝可以很容易地每天處理幾十億spans) | 決策支援較好,並且底層支援metrics指標 | 報表不豐富,UI比較簡單 | 消耗低 | jaeger比較複雜,使用框架較多,比如:rpc框架採用thrift協議,不支援pb協議之類。後端儲存比較複雜。但經過uber大規模使用,延展性好 |
zipkin | 開源 | 部分支援 | 侵入性強 | 策略靈活 | 時效性好 | 決策一般(功能單一,監控維度和監控資訊不夠豐富。沒有告警功能) | 豐富的資料報表 | 系統開銷小 | 延展性好 | |
CAT | 大眾點評吳其敏 | 開源 | - | 侵入性強 | 策略靈活 | 時效性較好,rpc框架採用tcp傳輸資料 | 決策好 | 報表豐富,滿足各種需求 | 消耗較低 , 國內很多大廠都在使用 | - |
Appdash | sourcegraph | 開源 | 完全支援 | 侵入性較弱 | 取樣率支援(粒度:不能根據流量取樣,只能依賴於請求數量);沒有trace開關 | 時效性高 | 決策支援低 | 視覺化太弱,無報表分析 | 消耗方面。不支援大規模部署, 因為appdash主要依賴於memory,雖然可以持久化到磁碟,以及記憶體儲存支援hash儲存、帶有效期的map儲存、以及不加限制的記憶體儲存,前者儲存量過小、後者單機記憶體儲存無法滿足 | 延展性差 |
MTrace | 美團 | 不開源 | - | - | - | - | - | - | - | - |
CallGraph | 京東 | 不開源 | - | - | - | - | - | - | - | - |
Watchman | sina微博 | 不開源 | - | - | - | - | - | - | - | - |
EagleEye | 淘寶 | 不開源 | - | - | - | - | - | - | - | - |
skywalking | 華為 吳晟 | 開源 | 完全支援 | 侵入性很低 | 策略靈活 | 時效性較好 | 由於呼叫鏈路的更細化, 但是作者在效能和追蹤細粒度之間保持了比較好的平衡。決策好 | 豐富的資料報表 | 消耗較低 | 延展性非常好,水平理論上無限擴充套件 |
綜合上分散式追蹤系統對比
1. jaeger對於go開發者來說,可能比較合適一些,但是入手比較困難。它的rpc框架採用thrift協議,現在主流grpc並不支援。後端豐富儲存,社群正在積極適配 2. appdash對於go開發者想搭建一個小型的trace比較合適,不適合大規模使用 3. zipkin專案,github很活躍,star數量很多,屬於java系。很多大廠使用 4. CAT專案也屬於java系, github不活躍,已不太更新了。不過很多大廠使用,平安、大眾點評、攜程... 5. skywalking專案, 也屬於java系。目前已成為Apache下的專案了,github活躍,作者也非常活躍,噹噹、華為正在使用。 6. appdash專案,go語言開發,因為視覺化過於簡單、且完全記憶體儲存,不太適合大規模專案使用。 複製程式碼
以上除了閉源的分散式追蹤系統,jaeger和zipkin還有skywalking是開源中不錯的選擇,但是,可能讀者也發現了, 以上的分散式追蹤系統,對於Java平臺的支援都很不錯,但是對於其他平臺的支援,就都比較有限了,尤其是低侵入程式碼的自動探針部分 。而且,以上所有的分散式追蹤系統, 無一例外,生產部署(非簡單體驗)都十分複雜 ,有些甚至依賴於kubernetes。這讓很多想要使用分散式追蹤系統來解決實際問題的中小企業望而卻步
我很清楚的記得在去年,我瘋狂的在尋找一個開箱可生產的分散式追蹤系統,於是就有了本文以上部分,最終是以失敗告終,很遺憾我沒有尋找到任何一款開箱可生產的開源產品。但是,路是人走出來的嘛,既然已經有了OpenTracing的標準,那理論上任何人和任何組織都可以實現一套標準化的分散式追蹤系統,因為產生了這個可怕的念頭,於是就有了本文的下半部分——我決定造一個輪子, 自行實現一套OpenTracing標準的分散式追蹤系統,目標是開箱可生產
NodeTracing概覽
我希望,可以在上面的表格中插入一行:
產品名稱 | 廠商 | 開源 | OpenTracing標準 | 侵入性 | 應用策略 | 時效性 | 決策支援 | 視覺化 | 低消耗 | 延展性 |
---|---|---|---|---|---|---|---|---|---|---|
NodeTracing | cheneyxu | 開源 | 完全支援 | 自動探針,幾乎無侵入 | 策略靈活 | 時效性秒級 | 決策較好,且持續升級 | 包括服務拓撲圖,跨度甘特圖等在內的多種圖表,且持續升級 | 消耗極低 | 無狀態和關聯架構,完全容器化的追蹤節點,可輕鬆簡單叢集化,採用記憶體與LevelDB配合的持久化儲存,可應對十億以上量級的span資料 |
最終,包括構思,架構,開發,測試等等兩個多月的時間,終於完成了NodeTracing的初個開箱可生產版本!這是至今為止我個人最滿意的作品, 同時也期望NodeTracing在以後可以持續進化成為至少是NodeJS領域最好的分散式追蹤系統 ,以下是架構圖和演示demo






#NodeTracing使用 ##下載
git clone https://github.com/cheneyweb/nodetracing cd nodetracing && npm i 複製程式碼
##快速開始&單例啟動
cd server && npm run standalone 複製程式碼
##生產部署&叢集啟動
docker stack deploy --prune -c docker-compose.yml nodetracing 複製程式碼
**NodeTracing的部署就是這麼簡單!**根據需要選擇單例啟動/叢集啟動之後,開啟瀏覽器訪問: http://localhost:3636/nodetracing/web/index.html 便可以看到系統管理頁,預設帳號密碼:admin/123456
##安裝自動探針
npm i nodetracing 複製程式碼
探針初始化(在應用入口首行)
const nodetracing = require('nodetracing') const tracer = new nodetracing.Tracer({ serviceName: 'S1',// 必須,服務名稱 rpcAddress: 'localhost',// 必須,後臺追蹤收集服務地址 rpcPort: '36361',// 可選,後臺追蹤收集服務埠,預設:36361 auto: true,// 可選,是否啟用自動追蹤,預設:false stackLog: false,// 可選,是否記錄詳細堆疊資訊(包括程式碼行號位置等,啟用記憶體消耗較大),預設:false maxDuration: 30000// 可選,最大函式執行時間(垃圾回收時間間隔),預設:30000 }) 複製程式碼
由此便完成了nodetracing的載入工作,接下來您可以根據您的服務型別選擇以下自動探針/手動探針...
async自動探針(支援async函式)
async function func1(){ ... } async function func2(){ ... } func1 = nodetracing.aop(func1) func2 = nodetracing.aop(func2) ... 複製程式碼
http請求自動探針(axios)
axios.interceptors.request.use(nodetracing.axiosMiddleware()) 複製程式碼
http響應自動探針(koa/express)
//koa app.use(nodetracing.koaMiddleware()) //express app.use(nodetracing.expressMiddleware()) 複製程式碼
grpc-client自動探針(原生)
const grpc = require('grpc') const Service = grpc.loadPackageDefinition(...)[packageName][serviceName] new Service("ip:port", grpc.credentials.createInsecure(), { interceptors: [nodetracing.grpcClientMiddleware()] }) 複製程式碼
grpc-server自動探針(原生)
const grpc = require('grpc') const interceptors = require('@echo-health/grpc-interceptors') let server = new grpc.Server() server = interceptors.serverProxy(this.server) server.use(nodetracing.grpcClientMiddleware()) 複製程式碼
grpc-client自動探針( x-grpc框架 )
const RPCClient = require('x-grpc').RPCClient const rpcClient = new RPCClient({ port: 3333, protosDir: "/grpc/protos/", implsDir: "/grpc/impls/", serverAddress: "localhost" }) rpcClient.use(nodetracing.grpcClientMiddleware()) rpcClient.connect() let result = await rpcClient.invoke('demo.User.login', { username: 'cheney', password: '123456' } , optionMeta?) 複製程式碼
grpc-server自動探針( x-grpc框架 )
const RPCServer = require('x-grpc').RPCServer const rpcServer = new RPCServer({ port: 3333, protosDir: "/grpc/protos/", implsDir: "/grpc/impls/", serverAddress: "localhost" }) rpcServer.use(nodetracing.grpcServerMiddleware()) rpcServer.listen() 複製程式碼
實現思路
簡單的使用說明之後,下面重點講解一下整個系統的實現方案 一個分散式追蹤系統,由三大部分組成,分別是探針,追蹤服務,視覺化服務
- 探針:嵌入與被監控追蹤的服務之中,伴隨服務啟動而執行,需要追蹤服務上下文,以及即時上報span給追蹤服務
- 追蹤服務:獨立部署,接收探針上報的span,計算分析處理後提供給視覺化服務用作資料展示
- 視覺化服務:接收追蹤服務的處理計算結果,視覺化呈現資料報表,提供最終決策參考
實現難點
這三大部分,其實每一個部分都是難啃的骨頭,根據實踐下來的經驗,每一部分的難點如下:
- 手動探針對程式碼侵入相當大,幾乎很難要求開發人員時刻謹記進行探針埋點,而且手動埋點也容易出錯,容易造成結果呈現不正確
- 相對於手動探針,自動探針是更好的選擇,但是自動探針實現難度高,且根據實現程度,支援覆蓋面有限
- 追蹤服務,如何叢集化部署是在架構設計時就需要考慮的,儘可能簡單的部署是第一優先考慮
- 視覺化服務,資料持久化方案如何選擇?如何在簡單部署和高效能可靠性之間平衡是難點
- 整體方案開發語言選擇,高網路效能,高非同步效能,低平臺依賴,低部署難度是優先考慮,所以毋庸置疑,nodejs是最佳選擇
設計&實現詳解
NodeJS探針
OpenTracing API
首先針對探針的實現,OpenTracing的標準其實已經給出了介面,目前初版優先考慮目前尚未有成熟分散式追蹤系統的nodejs平臺。所以第一步需要基於 opentracing-javascript 實現OpenTracing的API介面 在這一步中,很遺憾OpenTracing給出的官方文件實在有限,想要完整的實現全套API,必須耐下心來閱讀OpenTracing的JS原始碼,沒有別的辦法
Async Hook
在實現OpenTracing的API之後,也僅僅是完成了手動探針的實現,因為 OpenTracing其實只是指定了介面標準與追蹤資料標準,並沒有提供自動探針的實現思路 所以,其實自動探針的實現其實是依賴於各語言平臺自由的特性。不過萬變不離其中, 任何想要實現自動探針的語言平臺,就一定要實現AOP和上下文追蹤,否則幾乎不可能 因為首選支援nodejs平臺,所以在nodejs上實現自動探針主要會依賴兩個關鍵技術:
- async hook
- function merging
其中, async hook是node8.x之後版本推出的非同步資源追蹤方案 ,直至今日node11.x,已經初步成熟。利用async hook可以追蹤所有非同步資源關係,而這正是自動探針的必備條件
不過這裡需要注意的是, nodejs目前沒有同步資源的追蹤方案(我沒有找到,如果有讀者知道有的,希望能告知,不勝感激) ,所以,目前在nodejs中,目前僅可以對非同步呼叫進行自動探針追蹤。但是其實問題不大,因為nodejs中幾乎全是非同步資源,而且同步資源其實追蹤意義不大
function merging主要是用於在nodejs平臺中實現AOP,這裡主要需要一些語法技巧來實現,AOP對於自動探針非常關鍵,有了AOP,才有跨服務追蹤的可能
Java探針(規劃中...)
追蹤服務
追蹤服務用於接收探針上傳的span,很明顯,如果追蹤服務的架構設計不好,那這將會整個追蹤系統的效能瓶頸。而一個APM監控系統,本身怎麼效能瓶頸呢? 所以, 追蹤服務叢集化是必然的 。但是叢集化一定會面臨部署複雜難度高的問題,我不希望每一個使用NodeTracing的人都感概其難以部署,而且 開箱可生產是製作NodeTracing的初心 。 所以,在這一步的選擇上, Docker Swarm是第一優先選擇 ,Docker Swarm的極簡優雅性實在令人印象深刻,這一次,我依然決定使用容器叢集來解決追蹤服務的效能瓶頸問題
- 首先,需求追蹤服務可簡單平行擴充套件,那就得要求每個獨立服務無狀態,彼此之間無關聯。 在這裡採用的是之前自己造好的輪子 x-grpc框架 。這是一個獨立的grpc服務框架,非常適合於這種簡單快速高效能的rpc資料吞吐
- 然後,將追蹤服務打包製作成映象
- 最後,我們可以通過簡單的一句 docker stack 命令,部署任意節點的追蹤叢集
視覺化服務
到視覺化服務這一步,已經最後的闖關了,這意味著span資料已經收集完畢,我們需要考慮如何將其 持久化儲存和視覺化展示提供決策 在這裡最後的一道門檻是持久化儲存,因為大規模資料的持久化儲存方案一般都是非常複雜的,單點資料庫效能有瓶頸,但是如果採用類似於分散式資料庫之類的方案,會極大的提高部署難度。本身使用者要安裝自動探針,安裝追蹤服務已經要花些時間了,最後還需要部署一個分散式資料庫? 很明顯這裡的平衡取捨是難點。但是所幸最終還是找到了比較完美的解決方案,**那就是——LevelDB。這是一個谷歌實現的能支援十億級別資料規模的kv資料庫,特點是支援極高的寫入吞吐。**它的作者是Jeaf Dean和 Sanjay Ghemawat,是谷歌的傳奇工程師
LevelDB的發現讓我很激動,因為它幾乎就是為分散式追蹤系統而生的,所有特效都極其符合追蹤系統的持久化需求。**作為內嵌型資料庫,可以跟隨服務啟動,無需額外部署,寫入吞吐又極高。**在對LevelDB進行了基準測試後,我立馬採用了它作為NodeTracing的持久化方案,因為它的效能表現實在太搶眼了
測試
在初版完成後,針對NodeTracing的span吞吐做了一些基準測試:
- 單個節點,雙核i5+4G記憶體的追蹤服務,可每秒處理一萬個span(測試其實更多是受限於網路速度)
- 平行拓展到10個節點後,系統執行穩定(後續會進行更大規模節點部署測試)
- 大約平均1GB容量可以持久化儲存高達 四百萬個span (會根據實際執行的上下文大小的不同而不同,該值僅供參考)