APM和調用鏈跟蹤
隨着企業經營規模的擴大,以及對內快速診斷效率和對外SLA(服務品質協議,service-level agreement)的追求,對於業務系統的掌控度的要求越來越高,主要體現在:
- 對於第三方依賴的監控,實時/準實時瞭解第三方的健康狀況/服務品質,降低第三方依賴對於自身系統的擾動(服務降級、故障轉移)
- 對於容器的監控,實時/準實時的瞭解應用部署環境(CPU、內存、進程、線程、網絡、帶寬)情況,以便快速擴容/縮容、流量控制、業務遷移
- 業務方對於自己的調用情況,方便作容量規劃,同時對於突發的請求也能進行異常告警和應急準備
- 自己業務的健康、性能監控,實時/準實時的瞭解自身的業務運行情況,排查業務瓶頸,快速診斷和定位異常,增加對自己業務的掌控力
同時,對於企業來說,能夠更精確的瞭解資源的使用情況,對於成本覈算和控制也有非常大的裨益。
在這種情況下,一般都會引入APM(Application Performance Management & Monitoring)系統,通過各種探針採集數據,收集關鍵指標,同時搭配數據呈現和監控告警,能夠解決上述的大部分問題。
然而隨着RPC框架、微服務、雲計算、大數據的發展,同時業務的規模和深度相比過往也都增加了很多,一次業務可能橫跨多個模塊/服務/容器,依賴的中間件也越來越多,其中任何一個節點出現異常,都可能導致業務出現波動或者異常,這就導致服務質量監控和異常診斷/定位變得異常複雜,於是催生了新的業務監控模式:調用鏈跟蹤
- 能夠分佈式的抓取多個節點的業務記錄,並且通過統一的業務id(traceId,messageId,requestId等)將一次業務在各個節點的記錄串聯起來,方便排查業務的瓶頸或者異常點
產品對比
APM和調用鏈跟蹤均不是新誕生事務,很多公司已經有了大量的實踐,不過開源的並且能夠開箱即用的產品並不多,這裏主要選取了Pinpoint,Skywalking,CAT來進行對比(當然也有其他的例如Zipkin,Jaeger等產品,不過總體來說不如前面選取的3個完成度高),瞭解一下APM和調用鏈跟蹤在開源方面的發展狀態。
Pinpoint
Pinpoint是一個比較早並且成熟度也非常高的APM+調用鏈監控的項目,在全世界範圍內均有用戶使用,支持Java和PHP的探針,數據容器爲HBase,其界面參考:
Skywalking
Skywalking是一個新晉的項目,最近一兩年發展非常迅猛,本身支持OpenTracing規範,優秀的設計提供了良好的擴展性,支持Java、PHP、.Net、NodeJs探針,數據容器爲ElasticSearch,其界面參考:
CAT
CAT是由美團開源的一個APM項目,也歷經了多年的迭代升級,擁有大量的企業級用戶,對於監控和報警整合比較緊密,支持Java、C/C++、.Net、Python、Go、NodeJs,不過CAT目前主要通過侵入性的方式接入,數據容器包括HDFS(存儲原始數據)和mysql(二次統計),其界面參考:
橫向對比
上面只是做了一個簡介,那這三個項目各自有什麼特色或者優勢/劣勢呢(三者的主要產品均針對Java,這裏也主要針對Java的特性)?
- Pinpoint
- 優勢
- 大企業/長時間驗證,穩定性和完成度高
- 探針收集的數據粒度比較細
- HBase的數據密度較大,支持PB級別下的數據查詢
- 代碼設計考慮的擴展性較弱,二次開發難度較大(探針爲插件式,開發比較簡單)
- 擁有完整的APM和調用鏈跟蹤功能
- 劣勢
- 代碼針對性強,擴展較難
- 容器爲HBase,查詢功能較弱(主要爲時間維度)
- 探針的額外消耗較多(探針採集粒度細,大概10%~20%)
- 項目趨於成熟,而擴展難度較大,目前社區活躍度偏低,基本只進行探針的增加或者升級
- 缺少自定義指標的設計
- 優勢
- Skywalking
- 優勢
- 數據容器爲ES,查詢支持的維度較多並且擴展潛力大
- 項目設計採用微內核+插件,易讀性和擴展性都比較強
- 主要的研發人員爲華人並且均比較活躍,能夠進行更加直接的溝通
- 擁有完整的APM和調用鏈跟蹤功能
- 劣勢
- 項目發展非常快,穩定性有待驗證
- ES數據密度較小,在PB級別可能會有性能壓力
- 缺少自定義指標的設計
- 優勢
- CAT
- 優勢
- 大企業/長時間驗證,穩定性和完成度高
- 採用手動數據埋點而不是探針,數據採集的靈活性更強
- 支持自定義指標
- 代碼設計考慮的擴展性較弱,並且數據結構複雜,二次開發難度較大
- 擁有完善的監控告警機制
- 劣勢
- 代碼針對性強,擴展較難
- 需要手動接入埋點,代碼侵入性強
- APM功能完善,但是不支持調用鏈跟蹤
- 優勢
基本組件
如果分別去看Pinpoint/Skywalking/CAT的整體設計,我們會發現三者更像是一個規範的三種實現,雖然各自有不同的機制和特性,但是從模塊劃分和功能基本是一致的:
當然也有一些微小的區別:
- Pinpoint基本沒有aggregator,同時query和alarm集成在了web中,只有agent,collector和web
- Skywalking則是把collector、aggregator、alarm集成爲OAP(Observability Analysis Platform),並且可以通過集羣部署,不同的實例可以分別承擔collector或者aggregator+alarm的角色
- CAT則和Skywalking類似,把collector、aggregator、alarm集成爲cat-consumer,而由於CAT有比較複雜的配置管理,所以query和配置一起集成爲cat-home
- 當然最大的區別是Pinpoint和Skywalking均是通過javaagent做字節碼的擴展,通過切面編程採集數據,類似於探針,而CAT的agent則更像是一個工具集,用於手動埋點
Skywalking
前戲這麼多,終於開始進入主題,介紹今天的主角:Skywalking,不過通過之前的鋪墊,我們基本都知道了Skywalking期望解決的問題以及總體的結構,下面我們則從細節來看Skywalking是怎麼一步一步實現的。
模塊構成
首先,Skywalking進行了精準的領域模型劃分:
整個系統分爲三部分:
- agent:採集tracing(調用鏈數據)和metric(指標)信息並上報
- OAP:收集tracing和metric信息通過analysis core模塊將數據放入持久化容器中(ES,H2(內存數據庫),mysql等等),並進行二次統計和監控告警
- webapp:前後端分離,前端負責呈現,並將查詢請求封裝爲graphQL提交給後端,後端通過ribbon做負載均衡轉發給OAP集羣,再將查詢結果渲染展示
而整個Skywalking(包括agent和OAP,而webapp後端業務非常簡單主要就是認證和請求轉發)均通過微內核+插件式的模式進行編碼,代碼結構和擴展性均非常強,具體設計可以參考: 從Skywalking看如何設計一個微核+插件式擴展的高擴展框架 ,Spring Cloud Gateway的GatewayFilterFactory的擴展也是通過這種plugin define的方式來實現的。
Skywalking也提供了其他的一些特性:
- 配置重載:支持通過jvm參數覆寫默認配置,支持動態配置管理
- 集羣管理:這個主要體現在OAP,通過集羣部署分擔數據上報的流量壓力和二次計算的計算壓力,同時集羣也可以通過配置切換角色,分別面向數據採集(collector)和計算(aggregator,alarm),需要注意的是agent目前不支持多collector負載均衡,而是隨機從集羣中選擇一個實例進行數據上報
- 支持k8s和mesh
- 支持數據容器的擴展,例如官方主推是ES,通過擴展接口,也可以實現插件去支持其他的數據容器
- 支持數據上報receiver的擴展,例如目前主要是支持gRPC接受agent的上報,但是也可以實現插件支持其他類型的數據上報(官方默認實現了對Zipkin,telemetry和envoy的支持)
- 支持客戶端採樣和服務端採樣,不過服務端採樣最有意義
- 官方制定了一個數據查詢腳本規範:OAL(Observability Analysis Language),語法類似Linq,以簡化數據查詢擴展的工作量
- 支持監控預警,通過OAL獲取數據指標和閾值進行對比來觸發告警,支持webhook擴展告警方式,支持統計週期的自定義,以及告警靜默防止重複告警
數據容器
由於Skywalking並沒有自己定製的數據容器或者使用多種數據容器增加複雜度,而是主要使用ElasticSearch(當然開源的基本上都是這樣來保持簡潔,例如Pinpoint也只使用了HBase),所以數據容器的特性以及自己數據結構基本上就限制了業務的上限,以ES爲例:
- ES查詢功能異常強大,在數據篩選方面碾壓其他所有容器,在數據篩選潛力巨大(Skywalking默認的查詢維度就比使用HBase的Pinpoint強很多)
- 支持sharding分片和replicas數據備份,在高可用/高性能/大數據支持都非常好
- 支持批量插入,高併發下的插入性能大大增強
- 數據密度低,源於ES會提前構建大量的索引來優化搜索查詢,這是查詢功能強大和性能好的代價,但是鏈路跟蹤往往有非常多的上下文需要記錄,所以Skywalking把這些上下文二進制化然後通過Base64編碼放入data_binary字段並且將字段標記爲not_analyzed來避免進行預處理建立查詢索引
總體來說,Skywalking儘量使用ES在大數據和查詢方面的優勢,同時儘量減少ES數據密度低的劣勢帶來的影響,從目前來看,ES在調用鏈跟蹤方面是不二的數據容器,而在數據指標方面,ES也能中規中矩的完成業務,雖然和時序數據庫相比要弱一些,但在PB級以下的數據支持也不會有太大問題。
數據結構
如果說數據容器決定了上限,那麼數據結構則決定了實際到達的高度。Skywalking的數據結構主要爲:
- 數據維度(ES索引爲skywalking_*_inventory)
- service:服務
- instance:實例
- endpoint:接口
- network_adress:外部依賴
- 數據內容
- 原始數據
- 調用鏈跟蹤數據(調用鏈的trace信息,ES索引爲skywalking_segment,Skywalking主要的數據消耗都在這裏)
- 指標(主要是jvm或者envoy的運行時指標,例如ES索引skywalking_instance_jvm_cpu)
- 二次統計指標
- 指標(按維度/時間二次統計出來的例如pxx、sla等指標,例如ES索引skywalking_database_access_p75_month)
- 數據庫慢查詢記錄(數據庫索引:skywalking_top_n_database_statement)
- 關聯關係(維度/指標之間的關聯關係,ES索引爲skywalking_*_relation_*)
- 特別記錄
- 告警信息(ES索引爲skywalking_alarm_record)
- 併發控制(ES索引爲skywalking_register_lock)
- 原始數據
其中數量佔比最大的就是調用鏈跟蹤數據和各種指標,而這些數據均可以通過OAP設置過期時間,以降低歷史數據的對磁盤佔用和查詢效率的影響。
調用鏈跟蹤數據
作爲Skywalking的核心數據,調用鏈跟蹤數據(skywalking_segment)基本上奠定了整個系統的基礎,而如果要詳細的瞭解調用鏈跟蹤的話,就不得不提到openTracing。
openTracing基本上是目前開源調用鏈跟蹤系統的一個事實標準,它制定了調用鏈跟蹤的基本流程和基本的數據結構,同時也提供了各個語言的實現。如果用一張圖來表現openTracing,則是如下:
其中:
- SpanContext:一個類似於MDC(Slfj)或者ThreadLocal的組件,負責整個調用鏈數據採集過程中的上下文保持和傳遞
- Trace:一次調用的完整記錄
- Span:一次調用中的某個節點/步驟,類似於一層堆棧信息,Trace是由多個Span組成,Span和Span之間也有父子或者並列的關係來標誌這個節點/步驟在整個調用中的位置
- Tag:節點/步驟中的關鍵信息
- Log:節點/步驟中的詳細記錄,例如異常時的異常堆棧
- Baggage:和SpanContext一樣並不屬於數據結構而是一種機制,主要用於跨Span或者跨實例的上下文傳遞,Baggage的數據更多是用於運行時,而不會進行持久化
- Span:一次調用中的某個節點/步驟,類似於一層堆棧信息,Trace是由多個Span組成,Span和Span之間也有父子或者並列的關係來標誌這個節點/步驟在整個調用中的位置
以一個Trace爲例:
首先是外部請求調用A,然後A依次同步調用了B和C,而B被調用時會去同步調用D,C被調用的時候會依次同步調用E和F,F被調用的時候會通過異步調用G,G則會異步調用H,最終完成一次調用。
上圖是通過Span之間的依賴關係來表現一個Trace,而在時間線上,則可以有如下的表達:
當然,如果是同步調用的話,父Span的時間佔用是包括子Span的時間消耗的。
而落地到Skywalking中,我們以一條skywalking_segment的記錄爲例:
{
"trace_id": "52.70.15530767312125341",
"endpoint_name": "Mysql/JDBI/Connection/commit",
"latency": 0,
"end_time": 1553076731212,
"endpoint_id": 96142,
"service_instance_id": 52,
"version": 2,
"start_time": 1553076731212,
"data_binary": "CgwKCjRGnPvp5eikyxsSXhD///////////8BGMz62NSZLSDM+tjUmS0wju8FQChQAVgBYCF6DgoHZGIudHlwZRIDc3FsehcKC2RiLmluc3RhbmNlEghyaXNrZGF0YXoOCgxkYi5zdGF0ZW1lbnQYAiA0",
"service_id": 2,
"time_bucket": 20190320181211,
"is_error": 0,
"segment_id": "52.70.15530767312125340"
}
其中:
- trace_id:本次調用的唯一id,通過snowflake模式生成
- endpoint_name:被調用的接口
- latency:耗時
- end_time:結束時間戳
- endpoint_id:被調用的接口的唯一id
- service_instance_id:被調用的實例的唯一id
- version:本數據結構的版本號
- start_time:開始時間戳
- data_binary:裏面保存了本次調用的所有Span的數據,序列化並用Base64編碼,不會進行分析和用於查詢
- service_id:服務的唯一id
- time_bucket:調用所處的時段
- is_error:是否失敗
- segment_id:數據本身的唯一id,類似於主鍵,通過snowflake模式生成
這裏可以看到,目前Skywalking雖然相較於Pinpoint來說查詢的維度要多一些,但是也很有限,而且除了endPoint,並沒有和業務有關聯的字段,只能通過時間/服務/實例/接口/成功標誌/耗時來進行非業務相關的查詢,如果後續要增強業務相關的搜索查詢的話,應該還需要增加一些用於保存動態內容(如messageId,orderId等業務關鍵字)的字段用於快速定位。
指標
指標數據相對於Tracing則要簡單得多了,一般來說就是指標標誌、時間戳、指標值,而Skywalking中的指標有兩種:一種是採集的原始指標值,例如jvm的各種運行時指標(例如cpu消耗、內存結構、GC信息等);一種是各種二次統計指標(例如tp性能指標、SLA等,當然也有爲了便於查詢的更高時間維度的指標,例如基於分鐘、小時、天、周、月)
例如以下是索引skywalking_endpoint_cpm_hour中的一條記錄,用於標誌一個小時內某個接口的cpm指標:
{
"total": 8900,
"service_id": 5,
"time_bucket": 2019031816,
"service_instance_id": 5,
"entity_id": "7",
"value": 148
}
各個字段的釋義如下:
- total:一分鐘內的調用總量
- service_id:所屬服務的唯一id
- time_bucket:統計的時段
- service_instance_id:所屬實例的唯一id
- entity_id:接口(endpoint)的唯一id
- value:cpm的指標值(cpm=call per minute,即total/60)
工程實現
Skywalking的工程實現堪比Dubbo,框架設計和代碼質量都達到非常高的水準,以dubbo爲例,即使2012年發佈的老版本放到當今,其設計和編碼看起來也依然賞心悅目,設計簡潔但是覆蓋了所有的核心需求,同時又具備非常強的擴展性,二次開發非常簡單,然而卻又不會像Spring那樣過度封裝(當然Spring作爲一個更加高度通用的框架,更高的封裝也是有必要的)導致代碼閱讀異常困難。
agent
agent(apm-sniffer)是Skywalking的Java探針實現,主要負責:
- 採集應用實例的jvm指標
- 通過切向編程進行數據埋點,採集調用鏈數據
- 通過RPC將採集的數據上報
當然,agent還實現了客戶端採樣,不過在APM監控系統裏進行客戶端數據採樣都是沒有靈魂的,所以這裏就不再贅述了。
首先,agent通過 org.apache.skywalking.apm.agent.core.boot.BootService 實現了整體的插件化,agent啓動會加載所有的BootService實現,並通過 ServiceManager 來管理這些插件的生命週期,採集jvm指標、gRPC連接管理、調用鏈數據維護、數據上報OAP這些服務均是通過這種方式擴展。
然後,agent還通過bytebuddy以javaagent的模式,通過字節碼增強的機制來構造AOP環境,再提供PluginDefine的規範方便探針的開發,最終實現非侵入性的數據埋點,採集調用鏈數據。
最終落地到代碼上則異常清晰:
//通過bytebuddy的AgentBuilder構造javaagent增強classLoader
new AgentBuilder.Default(byteBuddy)
.ignore( //忽略這些包的內容,不進行增強
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.apache.logging."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.<TypeDescription>isSynthetic()))
//通過pluginFinder加載所有的探針擴展,並獲取所有可以增強的class
.type(pluginFinder.buildMatch())
//按照pluginFinder的實現,去改變字節碼增強類
.transform(new Transformer(pluginFinder))
//通過listener訂閱增強的操作記錄,方便調試
.with(new Listener())
.installOn(instrumentation);
try {
//加載所有的service實現並啓動
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
logger.error(e, "Skywalking agent boot failure.");
}
agent也提供了非常簡單的擴展實現機制,以增強一個普通類的方法爲例,首先你需要定義一個切向點:
public interface InstanceMethodsInterceptPoint {
//定義切向方法的適配器,符合適配器的class將被增強
ElementMatcher<MethodDescription> getMethodsMatcher();
//增強的具體實現類,classReference
String getMethodsInterceptor();
//是否重寫參數
boolean isOverrideArgs();
}
然後你還需要一個增強的實現類:
public interface InstanceMethodsAroundInterceptor {
//方法真正執行前執行
void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable;
//方法真正執行後執行
Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable;
//當異常發生時執行
void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes,
Throwable t);
}
一般在執行前和執行後進行數據埋點,就可以採集到想要的數據,當然實際編程要稍微複雜一點,不過官方也實現了對應的abstract類和數據埋點工具類,所以探針的二次開發在Skywalking這個級別確實是非常簡單,只需要處理好資源佔用和併發問題即可。真正的難點是要對需要增強的對象非常瞭解,熟悉其運作機制,才能找準切向點,既要所有的流程都需要經過這個點,又可以抓取到期望抓取的上下文信息。同時,多版本的適配和測試也是非常大的工作量,官方雖然提供witness的機制(通過驗證某個class是否存在來驗證版本),但是作爲影響全局的探針,開發和測試都是需要慎之又慎的。
OAP
同agent類似,OAP作爲Skywalking最核心的模塊,也實現了自己的擴展機制,不過在這裏叫做Module,具體可以參考library-module,在module的機制下,Skywalking實現了自己必須核心組件:
- core:整個OAP核心業務(remoting、cluster、storage、analysis、query、alarm)的規範和接口
- cluster:集羣管理的具體實現
- storage:數據容器的具體實現
- query:爲前端提供的查詢接口的具體實現
- receiver:接收探針上報數據的接收器的具體實現
- alarm:監控告警的具體實現
以及一個可選組件:
- telemetry:用於監控OAP自身的健康狀況
而前面提到的OAP的高擴展性則體現在覈心業務的規範均定義在了core中,如果有需要自己擴展的,只需要自己單獨做自己的實現,而不需要做侵入式的改動,最典型的示例則是官方支持的storage,不僅支持單機demo的內存數據庫H2和經典的ES,連目前開源的Tidb都可以接入。
初步實踐
對於Skywalking的實踐我們經歷了三個階段
- 線下測試
- 第一次生產環境小規模測試
- 第二次生產環境小規模測試+全量接入
線下測試
環境
由於是線下測試,所以我們直接使用物理機(E5-2680v2 x2, 128G)虛擬了一個集羣(實際性能相比雲服務器應該偏好一些):
- ES:單機實例,v6.5,4C8G,jvm內存分配爲4G
- OAP:單機實例,v6.1.0-SNAPSHOT,4C8G,jvm內存分配爲4G
- 應用:基於SpringCloud的4個測試實例,調用關係爲A->B->C->D,QPS爲200
測試結果
拓撲圖:
OAP機器監控:
ES機器監控:
服務監控面板:
其中一個調用鏈記錄:
可以看出,Skywalking非常依賴CPU(不論是OAP還是ES),同時對於網絡IO也有一定的要求,至於ES的文件IO在可接受範圍內,畢竟確實有大量內容需要持久化。測試結果也基本達到預期要求,調用鏈和各個指標的監控都工作良好。
第一次生產環境測試
在線下測試之後,我們再進行了一次基於實際業務針對探針的測試,測試沒有發現探針的異常問題,也沒有影響業務的正常運作,同時對於jvm實例影響也不是很大,CPU大概提高了5%左右,並不很明顯。在這個基礎上我們選擇了線上的一臺服務器,進行了我們第一次生產環境的測試。
環境
- ES:基於現有的一個ES集羣,node x 3,v6.0
- OAP:2C4G x 2,v6.1.0-SNAPSHOT,jvm內存分配爲2G
- 應用:兩個jvm實例
測試時間:03.11-03.16
測試結果
業務機器負載情況:
從最敏感的CPU指標上來看,增加agent並沒有導致可見的CPU使用率的變化,而其他的內存、網絡IO、連接數也基本沒有變化。
OAP負載情況:
可以看到機器的CPU和網絡均有較大的波動,但是也都沒有真正打爆服務器,但是我們的實例卻經常出現兩種日誌:
One trace segment has been abandoned, cause by buffer is full.
Collector traceSegment service doesn’t response in xxx seconds.
通過閱讀源碼發現:
- agent和OAP只會使用一個長連接阻塞式的交換數據,如果某次數據交換沒有得到響應,則會阻塞後續的上報流程(一般長連接的RPC請求會在數據傳輸期間互相阻塞,但是不會在等待期間互相阻塞,當然這也是源於agent並沒有併發上報的機制),所以一旦OAP在接收數據的過程中發生阻塞,就會導致agent本地的緩衝區滿,最終只能將監控數據直接丟棄防止內存泄漏
而導致OAP沒有及時響應的一方面是OAP本身性能不夠(OAP需要承擔大量的二次統計工作,通過Jstack統計,長期有超過幾十個線程處於RUNNABLE狀態,據吳晟描述目前OAP都是高性能模式,後續將會提供配置來支持低性能模式),另一方面可能是ES批量插入效率不夠,因此我們修改了OAP的批量插入參數來增加插入頻率,降低單次插入數量:
- bulkActions: ${SW_STORAGE_ES_BULK_ACTIONS:2000 -> 20} # Execute the bulk every 2000 requests
- bulkSize: ${SW_STORAGE_ES_BULK_SIZE:20 -> 2} # flush the bulk every 20mb
- flushInterval: ${SW_STORAGE_ES_FLUSH_INTERVAL:10 -> 2} # flush the bulk every 10 seconds whatever the number of requests
雖然 service doesn’t response 出現的頻率明顯降低,但是依然還是會偶爾出現,而每一次出現都會伴隨大量的 trace segment has been abandoned ,推測OAP和ES可能都存在性能瓶頸(應該進行更進一步的診斷確定問題,不過當時直接和吳晟溝通,確認確實OAP非常消耗CPU資源,考慮到當時部署只是2C,並且還部署有其他業務,就沒有進一步的測試)。
同時,在頻繁的數據丟棄過程中,也偶發了一個bug:當agent上報數據超時並且大量丟棄數據之後,即使後續恢復正常也能通過日誌看到數據正常上報,在查詢界面查詢的時候,會查不到這個實例上報的數據,不過在重啓OAP和agent之後,之前上報的數據又能查詢到,這個也和吳晟溝通過,沒有其他的案例,後續想重現卻也一直沒有成功。
而同時還發現兩個更加嚴重的問題:
- 我們使用的是線上已經部署好的ES集羣,其版本只有6.0,而新的Skywalking使用了6.3的查詢特性,導致很多查詢執行報錯,只能使用最簡單的查詢
- 我們的kafka集羣版本也非常古老,不支持v1或者更高版本的header,而kafka的探針強依賴header來傳輸上下文信息,導致kafka客戶端直接報錯影響業務,所以也立即移除了kafka的探針
在這一次測試中,我們基本確認了agent對於應用的影響,同時也發現了一些我們和Skywalking的一些問題,留待後續測試確認。
第二次生產環境測試
爲了排除性能和ES版本的影響,測試Skywalking本身的可用性,參考吳晟的建議(這也是在最初技術選型的時候沒有選擇Pinpoint和CAT的部分原因:一方面Skywalking的功能符合我們的要求,更重要的是有更加直接和效率的和項目維護者直接溝通的渠道),所以這一次我們新申請了ES集羣和OAP機器。
環境
- ES:騰訊雲託管ES集羣,4C16G x 3 SSD,v6.4
- OAP:16C32G,standalone,jvm分配24G
- 應用:2~8個jvm實例
測試時間:03.18-至今
測試結果
OAP負載情況:
ES集羣負載:
測試過程中,我們先接入了一臺機器上的兩個實例,完全沒有遇到一測中的延遲或者數據丟棄的問題,三天後我們又接入了另外兩臺機器的4個實例,這之後兩天我們又接入了另外兩臺機器的2個實例。依然沒有遇到一測中的延遲或者數據丟棄的問題。
而ES負載的監控也基本驗證了一測延遲的問題,Skywalking由於較高的併發插入,對於ES的性能壓力很大(批量插入時需要針對每條數據分析並且構建查詢索引),大概率是ES批量插入性能不夠導致延遲,考慮到我們僅僅接入了8個實例,日均segment插入量大概5000萬條(即日均5000萬次獨立調用),如果想支持更大規模的監控,對於ES容量規劃勢必要留夠足夠的冗餘。同時OAP和ES集羣的網絡開銷也不容忽視,在支撐大規模的監控時,需要集羣並且receiver和aggregattor分離部署來分擔網絡IO的壓力。
而在磁盤容量佔用上,我們設置的原始數據7天過期,目前剛剛開始滾動過期,目前segment索引已經累計了314757240條記錄總計158G數據,當然我們目前異常記錄較少,如果異常記錄較多的話,其磁盤開銷將會急劇增加(span中會記錄異常堆棧信息)。而由於選擇的SSD,磁盤的寫入和查詢性能都很高,即使只有3個節點,也完全沒有任何壓力。
而在新版本的ES集羣下,Skywalking的所有查詢功能都變得可用,和我們之前自己的單獨編寫的異常指標監控都能完美對照。當然我們也遇到一個問題:Skywalking僅採集了調用記錄,但是對於調用過程中的過程數據,除了異常堆棧其他均沒有采集,導致真的出現異常也缺少充足的上下文信息還原現場,於是我們擴展了Skywalking的兩個探針(我們項目目前重度依賴的組件):OkHttp(增加對requestBody和responseBody的採集)和SpringMVC(增加了對requestBody的採集),目前工作正常,如果進一步的增加其他的探針,採集到足夠的數據,那麼我們基本可以脫離ELK了。
而OAP方面,CPU和內存的消耗遠遠低於預期的估計,CPU佔用率一直較低,而分配的24G內存也僅使用了10+G,完全可以支持更大規模的接入量,不過在網絡IO方面可能存在一定的風險,推測應該8C16G的容器就足以支持十萬CPM級別的數據接入。
當然我們在查詢也遇到了一些瓶頸,最大的問題就是無法精確的命中某一條調用記錄,就如前面的分析,因爲segment的數據結構問題,無法進行面向業務的查詢(例如messageId、requestId、orderId等),所以如果想精確匹配某一次調用請求,需要通過各個維度的條件約束慢慢縮小範圍最後定位。
Skywalking展望
通過上述對Skywalking的剖析和實踐,Skywalking確實是一個優秀的APM+調用鏈跟蹤監控系統,能夠覆蓋大部分使用場景,讓研發和運維能夠更加實時/準實時的瞭解線上服務的運行情況。當然Skywailking也不是盡善盡美,例如下面就是個人覺得目前可見的不滿足我們期望的:
- 數據準實時通過gRPC上報,本地緩存的瓶頸(當然官方主要是爲了簡化模型,減少依賴,否則Skywalking還依賴ELK就玩得有點大了)
- 緩存隊列的長度,過長佔據內存,過短容易buffer滿丟棄數據
- 優雅停機同時又不丟失緩存
- 數據上報需要在起點上報,鏈路回傳的時候需要攜帶SPAN及子SPAN的信息,當鏈路較長或者SPAN保存的信息較多時,會額外消耗一定的帶寬
- skywalking更多是一個APM系統而不是分佈式調用鏈跟蹤系統
- 在整個鏈路的探針上均缺少輸入輸出的抓取
- 在調用鏈的篩查上並沒用進行增強,並且體現在數據結構的設計,例如TAG信息均保存在SPAN信息中,而SPAN信息均被BASE64編碼作爲數據保存,無法檢索,最終trace的篩查只能通過時間/traceId/service/endPoint/state進行非業務相關的搜索
- skywalking缺少對三方接口依賴的指標,這個對於系統穩定往往非常重要
而作爲一個初級的使用者,個人覺得我們可以使用有限的人力在以下方向進行擴展:
- 增加receiver:整合ELK,通過日誌採集採集數據,降低異構系統的採集開發成本
- 優化數據結構,提供基於業務關鍵數據的查詢接口
- 優化探針,採集更多的業務數據,爭取代替傳統的ELK日誌簡單查詢,絕大部分異常診斷和定位均可以通過Skywalking即可完成
- 增加業務指標監控的模式,能夠自定義業務指標(目前官方已經在實現 Metric Exporter )