jaeger的技術演進之路

cdh0805010118發表於2018-07-14

jaeger uber 的分散式跟蹤系統,它是 CNCF 基金會的第 15 個專案。在 16 年前,uber 內部使用的分散式跟蹤系統,是採用的 twitter 公司的解決方案——Zipkin。 後來,uber 內部想把 Zipkin 的拉取改為推送架構,就逐漸的形成了自己的分散式跟蹤系統,最終演變為產品:jaeger。

Merckx

uber 早期的追蹤系統叫做 Merckx,它應該是使用了 Zipkin 解決方案,Merckx 採用了拉取架構,可以從 kafka 佇列中拉取資料流。但是它最大的不足之處表現在兩個方面:

  1. 它的設計主要面向 Uber 使用整體式 API 的年代。缺乏分散式上下文傳播 context 的概念,雖然可以記錄 SQL 查詢、Redis 呼叫,甚至對其他服務的呼叫,但是無法進一步深入。(具體不太清楚)
  2. 另一個有趣的侷限是資料儲存在全域性執行緒的本地儲存中,隨著 Tornado web 框架的引入,這種方式變得不可行。

TChannel

隨後,隨著微服務的需要求到來,RPC 框架變得越來越重要,2015 年初內部開始開發 RPC 框架——TChannel。其中設計目標之一是將類似於 Dapper 分散式追蹤能力融入到協議中,而 OpenTracing 標準產生於 2016 年 11 月份左右,所以 TChannel 一開始並不遵循 OpenTracing 標準。至於後面的發展,後面再看。::TODO

雖然 TChannel 是與 Zipkin 解決方案完全無關,但是還是借鑑了後者的一些追蹤設計。從內部來看,TChannel 的 Span 在格式上與 Zipkin 幾乎完全相同,也使用了 Zipkin 所定義的註釋,例如:"cs"(Client Send) 和"cr"(Client Receive)。

TChannel 使用追蹤報告程式(Reporter)介面將收集到的程式外追蹤 Span 傳送至追蹤系統的後端。該技術自帶的庫預設包含一個使用 TChannel 本身和 Hyperbahn 實現的報告程式以及發現和路由層,藉此將 Thrift 格式的 Span 傳送至收集器叢集。

TChannel 客戶端庫接近我們所需要的分散式追蹤系統,該客戶端庫提供了下列模組:

  1. 追蹤上下文傳播以及帶內請求(IPC/RPC)
  2. 通過編排 API 記錄追蹤 Span;(也就是,把 trace 跟蹤進行元件封裝)
  3. 追蹤上下文的程式內傳播;(這個一般都很少使用)
  4. 將程式外追蹤資料包告至追蹤後端所需的格式和機制(也就是,Span 相關資料轉換為 Collector 和 Stoage 能夠接收和儲存的資料格式,這個工作既可以交給 Collector 來做,也可以 Agent 主動做好再推送)

該系統唯獨缺少了追蹤後端本身,即 Collector 和 Storage。追蹤上下文的傳輸格式和報表程式使用的預設 Thrift 格式在設計上都可以非常簡單直接地將 TChannel 和 Zipkin 後端整合。然而當時只能通過 Scribe 將 Span 傳送至 Zipkin,而 Zipkin 只支援 Cassandra 格式的資料儲存。因為當時 Uber 對這個儲存沒有什麼技術經驗,所以,他們自己開發了一套後端原型系統,並結合 Zipkin UI 的一些自定義元件構建了一個完成的分散式跟蹤系統。

也即,uber 開發的後端原型系統 Collector 和 Storage 分別是 tcollector(node.js) 和 Riak 儲存 (Spans),Solr 索引庫 (Indexing), 然後通過 Zipkin UI 來進行查詢。

但是隨著業務迅猛發展, 後端原型系統架構所使用的 Riak/Solr 儲存系統無法妥善縮放以適應 Uber 的流量,同時很多查詢功能依然無法與 Zipkin UI 實現足夠好的互動操作。同時 Uber 內部系統語言上的異構,以及還有很多核心業務使用的自己 RPC 框架,這些異構的技術環境使得分散式追蹤系統的構建變得困難。

Jaeger

針對 Merckx 產品和 Uber 內部技術的異構,使得需要更專職的團隊做分散式跟蹤系統——Jaeger。Jaeger 的目標:將現有的 Merckx 原型系統轉換為可以全域性運用的生產系統,讓分散式追蹤功能可以適用並適應 Uber 的微服務

新的團隊在 Cassandra 叢集方面已經具備運維經驗,該資料庫直接為 Zipkin 後端提供支援,因此團隊決定棄用 Riak/Solr 儲存系統。同時,為了接受 TChannel 流量並將資料相容 Zipkin 的二進位制格式儲存在 Cassandra 中,使用 Golang 重新實現了 collector。這樣對於 Zipkin 的 dashboard 就無需改動,完全相容了。

同時一個很大的改進點,他們還為每個收集器構建了一套可動態配置的倍增系統 (Multiplication factor),藉此將入站流量倍增 N 次,這主要是為了分散式跟蹤系統的壓測,看看 Jaeger 的延展性。

這裡我們可以看到,Jaeger 的早期架構依然依賴於 Zipkin UI 和 Zipkin 儲存格式。

還面臨的一個業務需求,uber 內部還有很多核心業務沒有使用 TChannel RPC 框架,為了給公司內部提供透明無侵入的 trace 服務,元件或者公共服務的編排變得非常重要,各種語言的客戶端庫提供,為了用不同語言提供一致的編排 API,所有客戶端庫從一開始就採用了OpenTracing API

Jaeger 還提供了一個取樣策略,防止流量過大,trace 對業務造成抖動比較大。策略包括:1. 全量取樣;2. 基於概率的取樣;3.限速取樣

Jaeger 將有關最恰當的取樣策略決策交給追蹤後端系統 Collector 服務,服務的開發者不再需要猜測最合適的取樣速率。而後端可以根據流量變化動態地調整取樣速率。

ps: 其實這裡也有另一個問題:如果交給後端collector服務進行取樣決策,那麼agent肯定是全量trace推送給collector,那麼agent所在的業務服務壓力也大,同時TChannel網路的壓力也很大

對於上面我提到的一個問題,Jaeger是這樣回答的:

    後端collector服務可以按照流量模式的變化動態地調整取樣速率,並反饋到各個服務的agent,形成反饋環路, 線上路中叫做:Control Flow

上面的回答,非常吸引人;因為它既不需要業務方考慮流量的增長趨勢來選擇合適的取樣速率,同樣,Jaeger的取樣率是基於全域性流量控制的,所以它具有動態的調整和反饋。這樣的策略讓人非常舒適

另一個需要解決的問題是,TChannel 框架的服務發現和服務註冊,需要依賴 Hyperbahn。但是對於希望在自己的服務中運用追蹤能力的工程師,這種依賴造成了不必要的摩擦。(ps: 這句話含義不是很明白?是有自己的服務發現和服務註冊嗎?比如:etcd,zookeeper, consul 等)

為了解決上面這個問題,我們事先了一種 jaeger-agent 邊車 (Sidecar) 程式, 並將其作為基礎架構元件,與負責收集度量值的代理一起部署到所有宿主機上,所有與路由與發現有關的依賴項都封裝在這個 jaeger-agent 中。

此外 uber 還重新設計了客戶端庫,可將追蹤 Span 報告給本地 UDP 埠,並能輪詢本地會換介面上的代理獲取取樣策略。新的客戶端只需要最基本的網路庫,架構上的這種變化向著我們先追蹤後取樣的願景邁出了一大步,我們可以在代理的記憶體中對追蹤記錄進行快取,這點類似於 Appdash 的 ChunkedCollector 方法,在 agent 以時間和大小兩個維度進行快取。

目前的 Jaeger 架構:後端元件使用 Golang 實現,客戶端庫使用了四種支援 OpenTracing 標準的語言,一個機遇 React 的 Web 前端,以及一個機遇 Apache Spark 的後處理和聚合資料管道。

Jaeger UI

Zipkin UI 是 Uber 在 Jaeger 中使用的最後一個第三方軟體。由於要將 Span 以 Zipkin Thrift 格式儲存在 Cassandra 中並與 UI 相容,這對後端和資料模型都有了很大的限制,而且資料轉換也是個頻繁的操作。尤其是 Zipkin 模型不支援 OpenTracing 標準;不支援客戶端庫兩個非常重要的功能:1. 鍵值對日誌;2. 更為通用的有向無環圖而非 span 樹所代表的跟蹤。所以 uber 下決心徹底革新後端所用的資料模型,並編寫新的 UI。則表示 Collector 和 Storage 的資料儲存模型拋棄了 Zipkin,使用了 OpenTracing 標準。其他優化點這裡不展開了。

參考資料

sidecar

優步分散式追蹤技術再度精進

更多原創文章乾貨分享,請關注公眾號
  • jaeger的技術演進之路
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章