SDK與問題排查

大雄45發表於2021-11-24
導讀 當我們以 sdk 的方式提供一種能力的時候,我們的實現不僅決定了業務的使用方式和成本,還決定使用者是否樂意使用它。

SDK與問題排查SDK與問題排查

SDK大家都不陌生,比如我們經常用到的 npm 包。當我們以 sdk 的方式提供一種能力的時候,我們的實現不僅決定了業務的使用方式和成本,還決定使用者是否樂意使用它。所以我們不能只考慮到功能,還需要考慮到使用方式以及 sdk 本身對業務的影響,不管是穩定性還是效能。當我們的 sdk 對業務來說是剛需時,如果 sdk 有問題,業務可能會聯絡我們處理,因為它需要這個 sdk。但是如果對業務來說這個 sdk 不是剛需時,業務可能直接 uninstall 我們的 sdk 並刪除對應的程式碼。這對於提供 sdk 的我們來說顯然不是個好事情。但是不管是否剛需,作為提供方,我們都需要努力去做好所提供的服務。

內嵌形式

我們使用的 sdk 大多數都是引入業務程式碼中,然後使用它提供的功能,這種情況下,有兩種模式,第一種是業務要感知 sdk 提供的 API。我們需要知道什麼時候使用什麼 API。第二種是業務不需要感知 sdk 提供的 API,或者說這時候 sdk 不提供 API,它本身就像一個黑盒子,業務引入後就內建了某些功能,比如我們提供一個定時上報業務記憶體使用情況的 sdk,那麼業務就不需要關注 sdk 的具體實現。下面以統計請求耗時為例看看如何實現這個 sdk。

1.第一種
{ 
  start(...) {} 
  end(...) {} 
}

第一種方式是比較樸素的實現,sdk 提供了一個 start 和 end 的 API,業務在開始請求和結束請求時分別執行這兩個 API,這樣 sdk 就可以計算出這個請求的耗時。但是這種方式看起來並不是那麼友好,首先會侵入業務的程式碼邏輯,其次業務還需要感知這個 sdk,需要考慮什麼時候調 start,什麼時候調 end,而且 sdk 還依賴業務傳入請求和響應的上下文,才能計算出某一個請求的耗時,總的來說,這種方式比較麻煩。

2.第二種

我們希望對業務的侵入性和感知少一點,所以決定直接劫持 Node.js 裡的 API。Node.js 裡以下面的形式可以建立一個伺服器。

http.createServer((req, res) => {})

那麼我們直接劫持這個 createServer。

const createServer = http.createServer; 
http.createServer = (cb) => { 
  return createServer((req, res) => { 
    const start = Date.now(); 
    res.on('finish', () => { 
        const cost = end - start; 
      }); 
    cb(); 
  }); 
}

通常,sdk 是提供 API,由業務主動呼叫,或者說觸發 sdk 的程式碼,因為 sdk 無法捕獲業務程式碼什麼時候需要使用 sdk 的某個功能。但是當我們可以捕獲到業務什麼時候需要我們時,就可以以更好的方式去提供這個 sdk。這種方式可以使得業務不需要過多感知 sdk,比如上面的例子中,業務只需要保證在呼叫http.createServer 之前執行我們的 sdk 就可以。sdk 內嵌業務程式碼中是非常常見的形式,但是我們希望儘量減少對業務的侵入,或者說減少業務的心智負擔,大家可能都有過這種經歷,當看到一個 sdk 提供密密麻麻的引數時,第一反應就不想用了。

脫離業務程式碼的形式

那麼是否能以一種脫離於業務程式碼的方式提供一個 sdk,這樣不僅不會影響業務程式碼,對於升級 sdk 來說也更容易。但是這種方式往往不容易,主要取決於場景,比如業務需要通過一個 sdk 上傳檔案,那麼這個 sdk 以內嵌的方式會比較合適。但是,某些場景下,脫離業務程式碼的 sdk 是可以做到的,比如排查問題類的工具。在 Node.js 裡,我們除錯或診斷程式的方式通常是在業務程式碼裡內嵌相關的程式碼,然後在必要的時候執行對應的程式碼,比如獲取堆快照。因為我們的程式碼只有置身於程式中,才能獲取到這個資訊。但是不是所有的資訊都需要置身於程式中才能獲取,比如系統級的資料。我之前碰到一個問題,就是在某個場景下,WebSocket 連線會很快底被斷開,通過再客戶端 wireshark 捕獲的流量中,發現伺服器會傳送一個 fin 包給客戶端,這樣就知道是伺服器的問題了,但是又因為從客戶端到真正的伺服器中間還隔了很多層,無法知道是哪一層伺服器主動斷開了連線,最後通過伺服器提供的工具找到了主動傳送 fin 包的伺服器從而解決了問題。但是我發現伺服器的那些工具用起來都非常複雜,如果不經常用,很快就忘了各種 和引數,像這種場景,就可以封裝 sdk 給業務使用,這種形式不僅可以幫助業務排查問題,還不需要侵入業務程式碼。

問題排查

我們排查問題通常藉助日誌,但是日誌很多時候也解決不了問題,日誌是靜態埋點,打多了不僅浪費儲存,而且消耗效能,打少了可能缺少排查問題的上下文。但是無論如何,重點是日誌是靜態埋點,如果我們要加埋點,就得重啟服務,有些問題稍縱即逝,重啟後可能就很難復現了。所以除了靜態追蹤技術外,動態追蹤技術就非常必要,也非常 cool 了,之前看了一下 ebpf,但是後來沒看了,最近重新研究了一下 ebpf 和所衍生的一些排查問題的工具,也看了一下 openresty 作者的文章《動態追蹤技術漫談》,可謂是精彩。當一個程式或者系統有問題時,我們希望保留現場,然後再慢慢分析。但是我們在程式之外怎麼能獲得程式的資料呢?除了系統本身提供的一些 外,這裡想說的是一種更復雜但更強大的技術。作業系統和我們寫的業務程式碼一樣,都是一些程式碼的邏輯,我們在寫程式碼時,經常會用到鉤子或者劫持的技術。同樣,作業系統也不例子,但是作業系統為了提供這種技術,實現上覆雜得多。這種技術就是 ebpf,ebpf 是把使用者寫的程式碼注入到核心中,核心有一個虛擬機器,滿足條件的時候就會執行我們的程式碼。

作業系統提供了鉤子機制,比如我們可以註冊一個鉤子到系統,當系統收到網路包時,就會回撥我們。另外一種就是劫持,比如 kprobe 到實現,當我們寫一段程式碼指示作業系統當有人呼叫 x 的時候回撥我們,作業系統就會把這個地址對應的指令改成 int3(x86 架構),然後執行到 x 這個函式的時候,就會觸發 int3 中斷,對應的處理函式就會執行我們註冊的回撥,然後再執行真正的函式。很多技術都依賴 ebpf,比如 tcpdump。ebpf 厲害之處在於核心程式設計可程式設計的了,真正情況下,我們可以通過基於 ebpf 的工具,從核心中查到非常多的資訊,以幫助我們排查問題。ebpf 非常流行,也非常複雜,就不討論太多,大家可以自行查閱相關資訊。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2843373/,如需轉載,請註明出處,否則將追究法律責任。

相關文章