聊聊 Sdk 和問題排查
不分享什么知識,聊一下最近的一些思考和看到的一些內(nèi)容。這兩個(gè)內(nèi)容看起來沒什么關(guān)系,其實(shí)也有關(guān)系。
sdk 大家都不陌生,比如我們經(jīng)常用到的 npm 包。當(dāng)我們以 sdk 的方式提供一種能力的時(shí)候,我們的實(shí)現(xiàn)不僅決定了業(yè)務(wù)的使用方式和成本,還決定用戶是否樂意使用它。所以我們不能只考慮到功能,還需要考慮到使用方式以及 sdk 本身對業(yè)務(wù)的影響,不管是穩(wěn)定性還是性能。當(dāng)我們的 sdk 對業(yè)務(wù)來說是剛需時(shí),如果 sdk 有問題,業(yè)務(wù)可能會(huì)聯(lián)系我們處理,因?yàn)樗枰@個(gè) sdk。但是如果對業(yè)務(wù)來說這個(gè) sdk 不是剛需時(shí),業(yè)務(wù)可能直接 uninstall 我們的 sdk 并刪除對應(yīng)的代碼。這對于提供 sdk 的我們來說顯然不是個(gè)好事情。但是不管是否剛需,作為提供方,我們都需要努力去做好所提供的服務(wù)。
1 內(nèi)嵌形式
## 1.1 內(nèi)嵌于業(yè)務(wù)代碼的形式
我們使用的 sdk 大多數(shù)都是引入業(yè)務(wù)代碼中,然后使用它提供的功能,這種情況下,有兩種模式,第一種是業(yè)務(wù)要感知 sdk 提供的 API。我們需要知道什么時(shí)候使用什么 API。第二種是業(yè)務(wù)不需要感知 sdk 提供的 API,或者說這時(shí)候 sdk 不提供 API,它本身就像一個(gè)黑盒子,業(yè)務(wù)引入后就內(nèi)置了某些功能,比如我們提供一個(gè)定時(shí)上報(bào)業(yè)務(wù)內(nèi)存使用情況的 sdk,那么業(yè)務(wù)就不需要關(guān)注 sdk 的具體實(shí)現(xiàn)。下面以統(tǒng)計(jì)請求耗時(shí)為例看看如何實(shí)現(xiàn)這個(gè) sdk。
1.第一種
- {
- start(...) {}
- end(...) {}
- }
第一種方式是比較樸素的實(shí)現(xiàn),sdk 提供了一個(gè) start 和 end 的 API,業(yè)務(wù)在開始請求和結(jié)束請求時(shí)分別執(zhí)行這兩個(gè) API,這樣 sdk 就可以計(jì)算出這個(gè)請求的耗時(shí)。但是這種方式看起來并不是那么友好,首先會(huì)侵入業(yè)務(wù)的代碼邏輯,其次業(yè)務(wù)還需要感知這個(gè) sdk,需要考慮什么時(shí)候調(diào) start,什么時(shí)候調(diào) end,而且 sdk 還依賴業(yè)務(wù)傳入請求和響應(yīng)的上下文,才能計(jì)算出某一個(gè)請求的耗時(shí),總的來說,這種方式比較麻煩。
2 第二種
我們希望對業(yè)務(wù)的侵入性和感知少一點(diǎn),所以決定直接劫持 Node.js 里的 API。Node.js 里以下面的形式可以創(chuàng)建一個(gè)服務(wù)器。
- http.createServer((req, res) => {})
那么我們直接劫持這個(gè) 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,由業(yè)務(wù)主動(dòng)調(diào)用,或者說觸發(fā) sdk 的代碼,因?yàn)?sdk 無法捕獲業(yè)務(wù)代碼什么時(shí)候需要使用 sdk 的某個(gè)功能。但是當(dāng)我們可以捕獲到業(yè)務(wù)什么時(shí)候需要我們時(shí),就可以以更好的方式去提供這個(gè) sdk。這種方式可以使得業(yè)務(wù)不需要過多感知 sdk,比如上面的例子中,業(yè)務(wù)只需要保證在調(diào)用http.createServer 之前執(zhí)行我們的 sdk 就可以。sdk 內(nèi)嵌業(yè)務(wù)代碼中是非常常見的形式,但是我們希望盡量減少對業(yè)務(wù)的侵入,或者說減少業(yè)務(wù)的心智負(fù)擔(dān),大家可能都有過這種經(jīng)歷,當(dāng)看到一個(gè) sdk 提供密密麻麻的參數(shù)時(shí),第一反應(yīng)就不想用了。
2 脫離業(yè)務(wù)代碼的形式
那么是否能以一種脫離于業(yè)務(wù)代碼的方式提供一個(gè) sdk,這樣不僅不會(huì)影響業(yè)務(wù)代碼,對于升級 sdk 來說也更容易。但是這種方式往往不容易,主要取決于場景,比如業(yè)務(wù)需要通過一個(gè) sdk 上傳文件,那么這個(gè) sdk 以內(nèi)嵌的方式會(huì)比較合適。但是,某些場景下,脫離業(yè)務(wù)代碼的 sdk 是可以做到的,比如排查問題類的工具。在 Node.js 里,我們調(diào)試或診斷進(jìn)程的方式通常是在業(yè)務(wù)代碼里內(nèi)嵌相關(guān)的代碼,然后在必要的時(shí)候執(zhí)行對應(yīng)的代碼,比如獲取堆快照。因?yàn)槲覀兊拇a只有置身于進(jìn)程中,才能獲取到這個(gè)信息。但是不是所有的信息都需要置身于進(jìn)程中才能獲取,比如系統(tǒng)級的數(shù)據(jù)。我之前碰到一個(gè)問題,就是在某個(gè)場景下,WebSocket 連接會(huì)很快底被斷開,通過再客戶端 wireshark 捕獲的流量中,發(fā)現(xiàn)服務(wù)器會(huì)發(fā)送一個(gè) fin 包給客戶端,這樣就知道是服務(wù)器的問題了,但是又因?yàn)閺目蛻舳说秸嬲姆?wù)器中間還隔了很多層,無法知道是哪一層服務(wù)器主動(dòng)斷開了連接,最后通過服務(wù)器提供的工具找到了主動(dòng)發(fā)送 fin 包的服務(wù)器從而解決了問題。但是我發(fā)現(xiàn)服務(wù)器的那些工具用起來都非常復(fù)雜,如果不經(jīng)常用,很快就忘了各種命令和參數(shù),像這種場景,就可以封裝 sdk 給業(yè)務(wù)使用,這種形式不僅可以幫助業(yè)務(wù)排查問題,還不需要侵入業(yè)務(wù)代碼。
3 問題排查
我們排查問題通常借助日志,但是日志很多時(shí)候也解決不了問題,日志是靜態(tài)埋點(diǎn),打多了不僅浪費(fèi)存儲(chǔ),而且消耗性能,打少了可能缺少排查問題的上下文。但是無論如何,重點(diǎn)是日志是靜態(tài)埋點(diǎn),如果我們要加埋點(diǎn),就得重啟服務(wù),有些問題稍縱即逝,重啟后可能就很難復(fù)現(xiàn)了。所以除了靜態(tài)追蹤技術(shù)外,動(dòng)態(tài)追蹤技術(shù)就非常必要,也非常 cool 了,之前看了一下 ebpf,但是后來沒看了,最近重新研究了一下 ebpf 和所衍生的一些排查問題的工具,也看了一下 openresty 作者的文章《動(dòng)態(tài)追蹤技術(shù)漫談》,可謂是精彩。當(dāng)一個(gè)進(jìn)程或者系統(tǒng)有問題時(shí),我們希望保留現(xiàn)場,然后再慢慢分析。但是我們在進(jìn)程之外怎么能獲得進(jìn)程的數(shù)據(jù)呢?除了系統(tǒng)本身提供的一些命令外,這里想說的是一種更復(fù)雜但更強(qiáng)大的技術(shù)。操作系統(tǒng)和我們寫的業(yè)務(wù)代碼一樣,都是一些代碼的邏輯,我們在寫代碼時(shí),經(jīng)常會(huì)用到鉤子或者劫持的技術(shù)。同樣,操作系統(tǒng)也不例子,但是操作系統(tǒng)為了提供這種技術(shù),實(shí)現(xiàn)上復(fù)雜得多。這種技術(shù)就是 ebpf,ebpf 是把用戶寫的代碼注入到內(nèi)核中,內(nèi)核有一個(gè)虛擬機(jī),滿足條件的時(shí)候就會(huì)執(zhí)行我們的代碼。
操作系統(tǒng)提供了鉤子機(jī)制,比如我們可以注冊一個(gè)鉤子到系統(tǒng),當(dāng)系統(tǒng)收到網(wǎng)絡(luò)包時(shí),就會(huì)回調(diào)我們。另外一種就是劫持,比如 kprobe 到實(shí)現(xiàn),當(dāng)我們寫一段代碼指示操作系統(tǒng)當(dāng)有人調(diào)用 x 的時(shí)候回調(diào)我們,操作系統(tǒng)就會(huì)把這個(gè)地址對應(yīng)的指令改成 int3(x86 架構(gòu)),然后執(zhí)行到 x 這個(gè)函數(shù)的時(shí)候,就會(huì)觸發(fā) int3 中斷,對應(yīng)的處理函數(shù)就會(huì)執(zhí)行我們注冊的回調(diào),然后再執(zhí)行真正的函數(shù)。很多技術(shù)都依賴 ebpf,比如 tcpdump。ebpf 厲害之處在于內(nèi)核編程可編程的了,真正情況下,我們可以通過基于 ebpf 的工具,從內(nèi)核中查到非常多的信息,以幫助我們排查問題。ebpf 非常流行,也非常復(fù)雜,就不討論太多,大家可以自行查閱相關(guān)信息。