使用Rust 加速前端監控

blank

使用Rust 加速前端監控

使用Rust加速前端監控|太狼的部落格
項目地址: github.com/Brooooooklyn

Intro

前陣子在公司內搭建了一個Log Service,用來記錄前端的報錯訊息,代碼一頓亂寫搞的七七八八之後實現了第一版的功能。

流程很簡單,前端將以下格式的訊息用get 發到Log Service:

{"url":"https://www.arkie.cn/scenarios","channel":"frontend","level":"FATAL","crashId":"02x32f3","stack":"base64 string ......",...}

Log Service接受到這個請求以後,將Stack解析成JSON : JSON.parse(decodeURIComponent(Buffer.from(query.stack, 'base64').toString())) ,解析後的stack是這樣的:

[{"filename":"https://arkie-public.oss-cn-hangzhou.aliyuncs.com/js/main.c3600f3f.js",line:1,column:334222},{"filename":"https://arkie-public.oss-cn-hangzhou.aliyuncs.com/js/common.752d2f13.js",line:1,column:113242},]

然後Log service 會根據文件對應的sourcemap (前端各項目deploy 的時候已經上傳到私有CDN 了) 解析出原始報錯位置。比如:

{filename:'./src/modules/design/design.container.tsx',line:102}

最後會將這些處理後的訊息輸出到阿里雲的LogHub。

blank

優化

做完第一個脆弱的版本後發現時間僅僅過去了一天半,所以開始考慮優化的事情了。

第一個版本有兩個問題,第一個問題是在後端處理log 的流程太長導致性能消耗有點大,第二個問題是實時處理Log 在後面用戶增多之後服務器會不堪重負,而其實Log Service 的實時性要求並沒有那麼高。

對於第一個問題,可以優化代碼性能(能優化才怪),分拆步驟(這個靠譜)來解決,第二個問題也可以通過分拆數據處理步驟來解決。

而分拆處理步驟這個解決方案可以通過在Log Service 中加入一個queue 來解決。比如接受到前端請求後,直接將原始數據塞到queue 中,然後有一個consumer 按一個最大速率從queue 中取出原始日誌,處理之後再放入LogHub,這一部分的細節就不贅述了,要寫的話展開又是一個長篇大論。

blank

而優化性能這方面,我本來沒有抱什麼希望,因為實在是看不出有啥可優化的。 base64 decode --> JSON.parse --> sourcemap parse都用的是最底層的標準庫調用( sourcemap parse用的是Mozilla出品的 github.com/mozilla/sour )

然而在上線的前夕,我突然想起了前不久學習Rust的時候看到的一個庫neon-bindings

Rust!Rust!

是不是可以用更快的語言來優化Sourcemap 處理的過程呢,同時大部分主要的繁瑣的業務還是使用TypeScript 編寫。

調研了一圈發現,已經有國內的公司在項目裡面用neon寫業務了: zhihu.com/question/1990

並且大家熟悉的sentry在生產環境中也是使用Rust來parse Sourcemap segmentfault.com/a/1190 ,雖然他們是binding到了python上,但他們已經把Rust代碼開源出來了: github.com/getsentry/ru

也就是說我只需要把這部分的Rust代碼通過neon-bindgs封裝成NodeJS可調用的模塊就行了,不像sentry還要port出C API再通過python調用C的代碼,美滋滋。

寫代碼的過程和原理就省略了,代碼可以在: github.com/Brooooooklyn 看到,主要分享一些數據和踩的坑:

Benchmark

所以Rust 比JavaScript 代碼在處理同樣的Sourcemap 時parse 快多少呢?

我做了一個簡單的benchmark, 測試結果如下:

$ node benchmark JavaScript parse time 50794 microseconds Rust parse time: 39 microseconds JavaScript parse result, Source: webpack:///src/utils/logger/logger.ts, Line: 56 Rust parse result, Source: webpack:///./src/utils/logger/logger.ts, Line: 56 ✨ Done in 0.33s.

Hardware Info:

ProductName: Mac OS X ProductVersion: 10.13.3 BuildVersion: 17D47 Model Name: MacBook Pro Model Identifier: MacBookPro14,2 Processor Name: Intel Core i5 Processor Speed: 3.1 GHz Number of Processors: 1 Total Number of Cores: 2 L2 Cache (per Core): 256 KB L3 Cache: 4 MB Memory: 16 GB

Benchmark代碼: github.com/Brooooooklyn

因為每次調用Rust 的代碼會有一次bootstrap 的過程以及JavaScript 代碼在運行很多次後會被JIT 優化,在一次性運行幾萬次的情況下差距可能縮小為十幾倍,有興趣大家可以自行嘗試。

CI/CD

剛寫完打算上線的時候,想讓production 的鏡像盡量小一點(我們用的Docker),所以直接在Production 的Image 上用了node:8-alpine 作為base image,相應的,CI 的鏡像(我們使用的是Gitlab runner的Docker executor )也是用同樣的base image,然後花了三個多小時嘗試在Alpine上安裝latest rust toolchains後失敗了,最後不得不忍受100多m的體積差切換到了node:8-slim 。最終的國內可以流暢build的Dockerfile在 github.com/Brooooooklyn

Toolschains安裝

由於眾所周知的原因,CI 在剛開始build image 的時候異常的緩慢,直到超時被Gitlab kill 掉,經過一個多小時頑強的抵抗後將所有可能撞牆的步驟全部替換成了USTC 的mirror。

主要是dev 機器rustup 安裝需要:

curl https://sh.rustup.rs -sSf | sed "s/https://static.rust-lang.org/rustup/dist/https://mirrors.ustc.edu.cn/rust-static/rustup/dist/g"| sh

使用USTC 的源安裝Rustup

build 前需要:

cat > $HOME /.cargo/config << EOF [source.crates-io] registry = "https://github.com/rust-lang/crates.io-index" replace-with = 'ustc' [source.ustc] registry = "git://mirrors.ustc.edu.cn/crates.io-index" EOF

讓Cargo 也是用USTC 的源(CI 環境也需要執行同樣的命令)

在CI的Docker Image build的時候需要替換Rust下載源以及替換Rustup源

詳情請參考README

More Info

因為Rust在公司內成功應用,所以我們決定後面會盡量遷移一些CPU密集型的任務到Rust上,比如用 github.com/nical/lyon替代puppeteer做海報的後端渲染,如果你有興趣的話,歡迎聯繫我!

What do you think?

Written by marketer

blank

JavaScript 正則表達式匹配漢字

blank

前端AI之路: KerasJS初探