Observability 之聊聊採樣 sampling 場景和落地案例 - 下篇

案例分享和深度思考

上篇介紹了 Sampling 基本原理和用法。下篇整理過去自己項目經歷和參考圈內一些朋友的案例做一些分享。一方面,通過案例給大家一些技術實現和選型的參考。另一方面,做可觀測系統採樣設計,從產品、業務、技術不同維度去做一些思考。希望對大家有幫助

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

業務系統接入全鏈路監控

伴魚案例

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,因此數據完整性相較前置採樣有一定損失

                         https://www.modb.pro/db/220009

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

我們結合一個示例來更好的理解什麼是 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 Filter 來實現完整採樣

實時消費隊列:根據採樣規則寫入 Bloom 過濾器,熱數據全量寫入熱存儲;

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

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

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

除此之外,裏面其實還有一些細節,比如說 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

OpenTelemetry sampling 客服端的實現

Sampling using Javaagent 

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

日誌採樣

日誌採樣場景其實不多,"B 站日誌系統的前世今生"

參考文檔 https://gist.github.com/baymaxium/d3672f062021018d189c0490d969fe8b

某些業務的日誌量很大(大於 500GB/day),多爲業務的訪問日誌,對日誌而言,“大量數據中的一小部分就足以進行問題排查和趨勢發現”,與研發和運維進行溝通,這個觀點也得到認同。因此在數據採集源頭 log agent(collector 模塊)中增加了日誌採樣(log sample)功能:日誌採樣以 app_id 爲維度,INFO 級別以下日誌按照比例進行隨機採樣,WARN 以上日誌全部保留。log agent 接入公司配置中心,採樣比例保存在配置中心,可以動態生效。有個細節額外說明下:由於要獲取日誌內的 app_id 字段,如果直接進行 json 解析, cpu 消耗將非常之高。後續我們改進爲字符查找(bytes.Index ),解決了這個問題。針對日誌量大的業務進行採樣,在不影響使用的情況下,節省了大量的 es 資源。目前每天減少 3T + 的日誌寫入

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nxnz37VJydNHFMaEywu9KA