本文分享自華為雲社群《提升網路協議伺服器的定位能力》,作者:張儉。
近期,我再次涉足於協議伺服器相關的工作領域,致力於定位並解決各種問題。簡單總結一些心得給大家。如果想要定位出協議伺服器的問題,那麼這些能力可能至關重要。
注:我這裡比較偏向協議本身的問題,不涉及一些通用的網路問題(如網路吞吐量上不去、響應時間長等等)
對CPU和記憶體的通用分析能力
首先,網路協議伺服器本質上也是一個應用程式。因此,需要具備一些關於CPU和記憶體的通用分析能力。PU/記憶體火焰圖,記憶體dump分析,鎖分析,以及遠端除錯(研發態手段)這些手段都要具備
日誌和網路連線的關聯
為了有效地定位網路問題,日誌需要精確到毫秒級別。沒有毫秒級別的精度,定位網路問題就會變得極其困難。所以golang的logrus預設只有秒級別,我覺得不太好,用rfc3339就很好。
在列印日誌時,我們不能太過隨意。例如,“connection lost”這樣的日誌,在除錯階段可能看似無大礙,但當真正的業務量和連線數大幅增加時,這種模糊的日誌資訊就會讓人束手無策。
理想的日誌至少應包含網路地址資訊,這樣我們可以根據網路地址和時間點來查閱日誌。如果有抓包的話,那就更好了,可以從中獲取大量資訊。
當然,我們並不需要在所有的日誌中都包含網路地址資訊。例如,一旦完成了使用者身份的鑑定,我們就可以列印使用者的身份資訊,這樣更方便與後續的業務流程進行整合。如果需要查詢網路地址資訊,可以回溯到建立連線時的日誌。舉個🌰
2023-05-30 23:59:01.000 [INFO] 127.0.0.1:62345 connected 2023-05-30 23:59:02.000 [INFO] 127.0.0.1:62345 authed, username is Wolverine 2023-05-30 23:59:03.000 [INFO] Wolverine killed magneto
假設一條資料鏈上有大量的訊息呢?在現代的網路環境中,一條TCP連結可以輕易達到5M bit/s以上的資料流。即使我們提供了時間點資訊,仍然很難找到具有問題的報文(在同一秒內可能有上千條報文)。在這種情況下,就需要引入會話的ID資訊。許多TCP協議會攜帶這種資訊,換句話說,支援IO複用的協議都會有這種資訊(比如MQTT的messageId,Kafka的correlationId等)。此類資訊應該被正確地列印在日誌中。
針對特徵值的跟蹤能力
你可能已經在除錯日誌中包含了非常詳盡的資訊,然而在實際環境中,這可能並沒有太大用處。
原因是一旦全面開啟debug日誌,效能消耗會大幅增加。除非你的系統效能冗餘極大,否則根本無法正常執行。
為此,我們可以提升debug的能力,針對特定的特徵值開啟debug,例如網路地址、mqtt的clientId、訊息中介軟體的topic等。應用程式僅針對這些特徵值列印詳細的日誌,這樣的開銷就相對較小,而且這種方法已經在生產環境中被我多次驗證。
將網路報文與業務trace關聯起來
在網路協議伺服器中,我們需要將網路報文與業務trace關聯起來。這種關聯能力的實現可以大大提高我們定位業務端到端問題的效率和準確性。 理想情況下,我們應該能夠根據網路報文來查詢相關的業務trace,反之亦然,根據業務trace來查詢對應的網路報文。但這些手段都需要業務端的配合,比如在報文中攜帶traceId,或者在業務trace中攜帶網路地址資訊。
以mqtt協議為例,可以在payload中帶上
{ "traceId": "xxxx", "data": "xxxx" }
在這個例子中,traceId
就是我們為業務trace設定的唯一識別符號,而data
則是實際的業務資料。透過在網路報文中攜帶這些資訊,我們就可以輕鬆地將網路報文與其對應的業務trace關聯起來。
然而,這種方法在研發和測試環境中實現相對容易,但在生產環境中可能會遇到更多的困難。首先,對於在網路報文中攜帶traceId
這一做法,業界並未形成統一的規範和實踐。這導致在生產環境,極難做到。
更具挑戰性的是,如果你面對的是一個端到端的複雜系統,將traceId從系統的入口傳遞到出口可能會遇到許多難以預見的問題。例如系統不支援這類資料的專遞,這就封死了這條路。
檢視原始報文的能力
檢視原始報文的能力極其重要,特別是在協議棧的實現尚不成熟的情況下。如果無法檢視原始報文,定位問題就會變得非常困難。我曾說過:“如果拿到了原始報文,還是無法復現問題,那我們的研發能力在哪裡?”雖然這句話可能有些極端,但它準確地強調了抓包的重要性。
我們可以從抓包看出網路的連通性、網路的延遲、網路的吞吐量、報文的格式、報文的正確性等等。如果途徑了多個網元,那麼是誰的錯?(一般來說,看抓包,誰先發RST,就從誰身上找原因)
雖然抓包的命令比較簡單
就抓了,但實際想做成,最大的阻力是這兩個,TLS和複雜的現網環境
在舊版本的TLS金鑰交換演算法下,只要有私鑰和密碼,就可以順利解包,但現在的tls,都支援前向加密,什麼叫前向加密呢?簡單地來說,就是給你私鑰和密碼,你也解不出來。有tls debuginfo和ebpf能解決這兩個問題,tls debug-info的原理是將金鑰交換時的金鑰輸出持久化到某個地方,然後拿這個去解,實際很少見有人用這個方案。ebpf一需要linux核心高版本,同時還需要開啟功能,安裝kernel-debug-info,門檻也比較高。
現網環境,像抓包嗅探的這種工具,有時候可能是禁止上傳的,或者即使能上傳成功,也需要很長的時間。
也許我們可以透過“應用層抓包”來解決上述的問題,在網路層,我們支援受限的抓包能力,比如可以抓針對某個特徵值(比如網路地址、messageId)的包,因為我們在應用層,可使用的過濾條件更多,更精細,輸出到某個路徑,這個報文的組裝,完全在應用網路層,雖然看不到物理層的一些資訊,但對於應用程式來說,除非我是做nat裝置的,一般用不到這些資訊。繼續用這個報文來分析問題。實現應用層抓包,也要注意對記憶體的佔用等等,不能因為這個功能,把整個程式搞崩潰。
應用層抓包的一些思考
抓包地點的選擇
在應用層抓包,第一步就是確定抓包的地點。由於我們是在應用層進行操作,因此抓包地點一般位於應用程式與網路協議棧的交接處。例如,你可以在資料包剛被應用接收,還未被處理之前進行抓包,或者在資料包即將被應用傳送出去,還未進入網路協議棧之前進行抓包。
過濾條件的設定
設定過濾條件是抓包的關鍵,因為在實際環境中,資料流量可能非常大,如果沒有過濾條件,抓包的資料量可能會非常龐大,對應用和系統的效能產生影響。在應用層,我們可以設定更多更精細的過濾條件,如網路地址、埠、協議型別、特定的欄位等。這些過濾條件可以幫助我們更精確地定位問題,減少無效的資料。
資料儲存問題
將抓到的資料儲存起來也是很重要的一步。可以選擇將資料儲存到記憶體或者硬碟。需要注意的是,如果選擇儲存到記憶體,要考慮到記憶體的大小,避免因為抓包資料過大導致記憶體溢位。如果選擇儲存到硬碟,要考慮到硬碟的讀寫速度和容量,避免因為抓包資料過大導致硬碟滿載。
總結
本文首先闡述了網路協議伺服器的一些問題定位能力,包括CPU記憶體分析能力、日誌和網路連線的關聯能力、針對特徵值的跟蹤能力,以及檢視原始報文的能力,也討論了將網路報文與業務trace有效關聯的重要性和實現挑戰。強調了抓包的重要性和對於解密TLS報文的挑戰。為了解決網路層抓包遇到的困難,我們可以考慮應用層抓包方案。最後,我們討論了應用層抓包的一些關鍵問題,包括抓包地點的選擇、過濾條件的設定和資料儲存問題。