Observability 之深度取樣 Sampling 場景和落地案例(下)

語言: CN / TW / HK

案例分享和深度思考

整理過去自己專案和查閱了圈內一些朋友分享案例。

一方面,通過案例給大家一些技術實現和選型的參考。

另一方面,做可觀測系統取樣設計,從產品、業務、技術不同的維度去做一些思考。

希望對大家有幫助。

取樣規則策略:要不要做全量取樣?

業務系統接入全鏈路監控

伴魚案例

2020 年,我們不斷收到業務研發的反饋:能不能全量採集 trace?這促使我們開始重新思考如何改進呼叫鏈追蹤系統。我們做了一個簡單的容量預估:目前 Jaeger 每天寫入 ES 的資料量接近 100GB/天,如果要全量採集 trace 資料,保守假設平均每個 HTTP API 服務的總 QPS 為 100,那麼完整存下全量資料需要 10TB/天;樂觀假設 100 名伺服器研發每人每天檢視 1 條 trace,每條 trace 的平均大小為 1KB,則整體信噪比千萬分之一。可以看出,這件事情本身的 ROI 很低,考慮到未來業務會持續增長,儲存這些資料的價值也會繼續降低,因此全量採集的方案被放棄。退一步想:全量採集真的是本質需求嗎?實際上並非如此,我們想要的其實是「有意思」的 trace 全採,「沒意思」的 trace 不採。

貨拉拉案例

2.0 架構雖然能滿足高吞吐量,但是也存在儲存成本浪費的問題。其實從實踐經驗看,我們會發現 80~90%的 Trace 資料都是無價值的、無意義的資料,或者說是使用者不關心的。那麼使用者關心哪些資料呢?關心鏈路中錯、慢的請求以及部分核心服務的請求。那麼我們是不是可以通過某些方式,把這些有價值的資料給過濾取樣出來從而降低整體儲存成本?

在這個背景下,我們進行 3.0 的改造,實現了差異化的完成鏈路取樣,保證 1H 以內的資料全量儲存,我定義它為熱資料,而一小時以外的資料,只保留錯、慢、核心服務請求 Trace,定義為冷資料,這樣就將整體的儲存成本降低了 60%

從伴魚和貨拉拉的落地實踐看出:有一定資料量的鏈路系統,全量取樣對於業務系統並非有價值,甚至說完全沒必要。從業務角度,少量取樣是一個很合理節省資源成本方案,這在生產環境是經過驗證的。

阿里鷹眼

鷹眼是基於日誌的分散式呼叫跟蹤系統,其理念來自於 Google Dapper 論文,其關鍵核心在於呼叫鏈,為每個請求生成全域性唯一的 ID(Traceld)目前支撐阿里集團泛電商、高德、優酷等業務,技術層面覆蓋前端閘道器接入層、遠端服務呼叫框架(RPC)、訊息佇列、資料庫、分散式快取、自定義元件(如支付、搜尋 SDK、本地方法埋點等)

鷹眼在面對海量資料,取樣比較簡單: 百分比取樣

https://www.infoq.cn/article/kMPZTgJqs7VJC5vkVCR2

90%鏈路資料採集意義不大

思考「沒意思」的 trace 不採,「有意思」的 trace 全採

生產環境絕大部分鏈路資料平時需要採集麼?往往我們的生產環境就正如伴魚情況類似:

1、監控系統採集了成千上萬的鏈路資料,花了大量伺服器資源儲存資料,同時還不得不面對高併發帶來的效能問題,投入不菲的研發人力做效能調優。

2、監控系統真正研發看的時間很低頻,可能在請求量大的活動,或者重大發布時,研發團隊會

只有在真正系統異常時候會關注鏈路資料,而且對每一個鏈路節點的資訊都如視珍寶,很有可能從一些細節定位到故障和原因。

所以,生產環境是 穩定性遠遠大於故障時間 ,我們很多公司甚至要求 SLO 達到 99% 可用性。從另外一個維度反映出:平時,系統穩定態,95%以上鍊路資料是正常的,所以它們並不需要那麼關注。當然,並不是說你可以完全無視這些鏈路資料,它的價值體現在你怎麼用它,比如下面一些場景:

1、鏈路資料用做使用者行為分析的大資料來源。它往往不需要實時性計算,離線處理。

2、歷史鏈路資料可以作為週期性異常預測的大資料來源。

只不過,95%鏈路資料價值已經很小了。如果你的場景不涉及 1、2,其實完全可以考慮節約成本。甚至在異常預測場景,也可以很小部分的全取樣,只要達到你想要的資料樣品範圍足夠。

哪些是應該全量採集的鏈路

關注的呼叫鏈全取樣:研發在分析、排障過程中想查詢的任何呼叫鏈都是重要呼叫鏈。比如伴魚提到日常排障經驗,總結以下三種優先順序高場景:

A、在呼叫鏈上列印過 ERROR 級別日誌

B、在呼叫鏈上出現過大於 200ms 的資料庫查詢

C、整個呼叫鏈請求耗時超過 1s

關心的呼叫鏈,從日誌級別、響應時間、核心元件的效能指標(這裡舉例資料庫)幾個維度入手

只要服務列印了 ERROR 級別的日誌就會觸發報警,研發人員就會收到 im 訊息或電話報警,如果能保證觸發報警的呼叫鏈資料必採,研發人員的排障體驗就會有很大的提升;

我們的 DBA 團隊認為超過 200ms 的查詢請求都被判定為慢查詢,如果能保證這些請求的呼叫鏈必採,就能大大方便研發排查導致慢查詢的請求;

對於線上服務來說,時延過高會令使用者體驗下降,但具體高到什麼程度會引發明顯的體驗下降我們暫時沒有資料支撐,因此先配置為 1s,支援隨時修改閾值。當然,以上條件並不絕對,我們完全可以在之後的實踐中根據反饋調整、新增規則,如單個請求引起的資料庫、快取查詢次數超過某閾值等

尾部取樣的好處

呼叫鏈追蹤系統支援了穩態分析,而業務研發亟需的是異常檢測。要同時支援這兩種場景,採用尾部連貫取樣 (tail-based coherent sampling)。相對於頭部連貫取樣在第一個 span 處就做出是否取樣的決定,尾部連貫取樣可以讓我們在獲取完整的 trace 資訊後再做出判斷

我們再看看貨拉拉對「有意思」的 trace 策略 :錯、慢、核心服務取樣

提出另外一種方案,就是更深入一點,從 Trace 詳情資料入手。我們可以看到這個 Trace 的結構是由多個 Span 組成,Trace 維度包含 APPID、Latency 資訊 Span 維度又包含耗時、型別等更細粒度的資訊,根據不同的 Span 型別設定不同的閾值,比如遠端呼叫 SOA 耗時大於 500ms 可以認為是慢請求,而如果是 Redis 請求,閾值就需要設定小一些,比如 20ms,通過這種方式可以將慢請求規則更精細化,同樣可以通過判斷 APPID 是否為核心服務來過濾保留核心服務 Trace 資料

可以看出,貨拉拉取樣維度和伴魚場景類似,相同的響應時間,核心元件的效能指標,還加了高階擴充套件: 精細化取樣 ,通過 APPID 來獲取想要服務的全鏈路取樣。

常用取樣策略

根據 TraceId 中的順序數進行取樣,提供了多種取樣策略搭配:

  • 百分比取樣:主要用在鏈路最開始節點

  • 固定閾值取樣:全域性或租戶內統一控制

  • 限速取樣:在入口處按固定頻率取樣若干條呼叫鏈;

  • 異常優先採樣:調用出錯時優先採樣;

  • 個性化取樣:按使用者 ID、入口 IP、應用、呼叫鏈入口、業務標識等配置開啟取樣

鏈路完整取樣

鏈路完整性:

這裡舉個例子,如圖現在有一條遠端呼叫,經過 ABC 和分支 AD。這裡有個前提就是 ABCD 它是 4 個不同的服務,獨立非同步上報 Trace 資料沒有嚴格的時間順序。在 B 呼叫 C 出現異常時,我們能輕鬆識別到並將 B 和 C 的 Trace 資料段取樣到,只保留 B 和 C 的這種情況,稱為部分取樣。但是在實際的一個排障過程中,我們還需要 A 和 D 這條鏈路資料作為輔助資訊來支援排障,所以最好的方式是把 ABCD 都取樣到,作為一個完整的異常鏈路儲存起來,這稱為完整取樣。

取樣是如何保證鏈路的完整性?

我們的目標是完整取樣,如何實現完整取樣也是業界的一個難點,當前行業有一些解決方案,比如阿里鷹眼和位元組的方案

阿里鷹眼方案

阿里鷹眼採用一直純記憶體的解決方案:

例如現在有個鏈路經過 ABCD 和分支 AE,B 呼叫 C 出現異常時,他會做一個染色標記,那麼 C 到 D 自然也攜帶了染色標記,理論上 BCD 會被取樣的儲存起來,但是 A 和 E 是前置的節點也沒有異常也沒被染色,該怎麼辦呢?他引入一個取樣決策點的角色,假設 B 出了異常,取樣決策點感知到,最後在記憶體裡查是否存在像 A 和 E 這種異常鏈路的前置節點,然後將它儲存起來。

這裡需要注意這種場景對查詢的 QPS 要求非常的高,那如果不是存在記憶體而是類似 HBase 這種服務裡的話是很難滿足這種高 QPS 的需求。所以他選擇將一小時或者半小時內的 Trace 資料放記憶體中,來滿足取樣決策點快速查詢的要求。

但基於記憶體儲存也存在弊端,因為記憶體資源是比較昂貴的。我們做個簡單的計算,如果想儲存 1 小時以內的 Trace 資料,單條 Trace 2K 大小,要支撐百萬 TPS,大約需要 6T 記憶體,成本比較高

位元組跳動方案

提出 PostTrace 後置取樣方案。

當一個 Trace 一開始未命中取樣,但在執行過程中發生了一些令人感興趣的事(例如出錯或時延毛刺)時,在 Trace 中間狀態發起取樣。PostTrace 的缺點只能採集到 PostTrace 時刻尚未結束的 Span,因此資料完整性相較前置取樣有一定損失。

發生錯誤的服務將取樣決定強制進行翻轉,如果這條鏈路沒有進行取樣的話。但這樣的話會丟失取樣決策改變之前的所有鏈路以及其他分支鏈路的資料。

我們結合一個示例來更好的理解什麼是 PostTrace。左圖是一個請求,按照阿拉伯數字標識的順序在微服務間發生了呼叫,本來這條 trace 沒有采樣,但是在階段 5 時發生了異常,觸發了 posttrace,這個 posttrace 資訊可以從 5 回傳到 4,並傳播給後續發生的 6 和 7,最後再回傳到 1,最終可以採集到 1,4,5,6,7 這幾個環節的資料,但是之前已經結束了的 2、3 環節則採集不到。右圖是我們線上的一個實際的 posttrace 展示效果,錯誤層層向上傳播最終採集到的鏈路的樣子。PostTrace 對於錯誤鏈傳播分析、強弱依賴分析等場景有很好的應用

染色取樣:對特定的請求新增染色標記,SDK 檢測到染色標對該請求進行強制取樣

這些取樣策略可以同時組合使用。取樣不影響 Metrics 和 Log。Metrics 是全量資料的聚合計算結果,不受取樣影響。業務日誌也是全量採集,不受取樣影響

貨拉拉方案

基於 Kafka 延遲消費+布隆過濾器實現:

實時消費佇列:根據取樣規則寫入 Bloom 過濾器,熱資料全量寫入熱儲存;

延遲消費佇列:根據 Bloom 過濾器實現條件過濾邏輯,冷資料寫入冷儲存。

基於 Kafka 延遲消費+Bloom Filter 來實現完整取樣。

比如說我們 Kafka 有兩個消費組,一個是實時消費,一個是延遲消費,實時消費每條 Trace 資料時會判斷下是否滿足我們的採用規則,如果滿足就將 TraceID 放在 Bloom Filter 裡,另外一方面延時消費組在半小時(可配置)開始消費,從第一條 Trace 資料開始消費,針對每條 Trace 資料判斷 TraceID 是否在 Bloom Filter 中,如果命中了,就認為這條 Trace 應該被保留的,從而能做到整個 Trace 鏈路的完整取樣儲存

https://www.sohu.com/a/531709613_411876

除此之外,裡面其實還有一些細節,比如說 Bloom 不可能無限大,所以我們對其按分鐘進行劃分出多個小的 Bloom,又比如我們其實採用的是一個 Redis 的 Bloom,但 Redis Bloom 如果想達到百萬 QPS 預計需要 10~20 個 2C4G 的節點,但是我們實際只用了 5 個 2C4G 的節點就能滿足百萬的一個吞吐量。這裡涉及到專利保護規定,就不展開說了,大家如果感興趣,有機會可以私底下聊。整體上我們就是具有這套取樣方案實現整體成本的降低了 60%

OpenTelemetry 生產環境取樣的案例

https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/4958

OT 社群的 tailsampling 方案利用以下幾個 processor 和 exporter 實現高伸縮性:

負載均衡閘道器 loadbalancingexporter:把屬於同一個 TraceID 的所有 Trace 和 Log 分發給一組固定的下游 Collector

tailsamplingprocessor:通過預定義的組合策略進行取樣

tailsamplingprocessor 支援 4 種策略:

always_sample:全取樣

numeric_attribute:某數值屬性位於 [min_value, max_value] 之間

string_attribute:某字串屬性位於集合 [value1, value2, …] 之中

rate_limiting:按照 spans 數量限流,由引數 spans_per_second 控制

以「在呼叫鏈上如果列印了 ERROR 級別日誌」為例,按照規範我們會記錄 span.SetTag("error" , true),但 tailsamplingprocessor 並未支援 bool_attribute;此外,未來我們可能會有更復雜的組合條件,這時僅靠 numeric_attribute 和 string_attribute 也無法實現。經過再三分析,我們最終決定利用 Processors 的鏈式結構,組合多個 Processor 完成取樣,流水線如下圖所示:

其中 probattr 負責在 trace 級別按概率抽樣,anomaly 負責分析每個 trace 是否符合「有意思」的規則,如果命中二者之一,trace 就會被打上標記,即 sampling.priority。最後在 tailsamplingprocessor 上配置一條規則即可,如下所示:

tail_sampling:policies:[  {  name: sample_with_high_priority,  type: numeric_attribute,  numeric_attribute: { key: "sampling.priority", min_value: 1, max_value: 1 }  }]

複製程式碼

這裡 sampling.priority 是整數型別,當前取值只有 0 和 1。按上面的配置,所以 sampling.priority = 1 的 trace 都會被採集。後期可以增加更多的採集優先順序,在必要的時候可以多采樣 (upsampling) 或降取樣 (downsampling)。

OpenTelemetry Agent

sampling OpenTelemetry 客服端的實現

Sampling using Javaagent

https://github.com/open-telemetry/opentelemetry-java-instrumentation/discussions/5803

作者介紹

蔣志偉,愛好技術的架構師,先後就職於阿里、Qunar、美團,前 pmcaff.com CTO,目前 OpenTelemetry 中國社群發起人, https://github.com/open-telemetry/docs-cn 核心維護者

歡迎大家關注 OpenTelemetry 公眾號,這是中國區唯一官方技術公眾號