被低估的 Deno

這個週末,我一直在把玩 deno 的 rusty_v8 以及 deno_core(錄了幾個 rusty_v8 的視頻,預計四月第二週發)。rusty_v8 是 google v8 engine 的 Rust 零成本封裝,而 deno_core 在 rusty_v8 的基礎上進一步封裝了一些額外的功能。衆所周知,v8 是 chrome 內部的 javascript 執行引擎,它優異的 JIT 能力,以及高效的垃圾回收,使得 chrome 成爲最快最成功的瀏覽器。v8 僅僅被用在瀏覽器中有些暴殄天物,於是十多年前(2009),Ryan Dahl 把 v8 引入了服務端,創建了 node.js —— node 以簡單容易上手的編程模型(單線程,異步處理)和大量的前端擁躉一舉成爲廣受歡迎的服務端開發工具;而 3 年前,Ryan Dahl 自我革命,重新用 v8 從零打造 deno,意欲讓 deno 成爲下一代服務器開發的王者。

如果你沒聽過 deno,或者並不瞭解 deno,建議你去看看 2018 年 Ryan 那個顛覆性的演講:10 Things I regret about Node.js [1]。這個演講對 node 的信仰者的打擊是巨大的,對他們而言,這就好像普羅米修斯把火送到了人間,讓世人在黑暗中得到了光明和溫暖,卻又對沖着自己頂禮膜拜的人們說我錯了,火是不好的。這些年來,很多憤怒的 node 工程師還沒真正瞭解 deno(甚至連 Ryan 的演講都沒看過),就急忙人云亦云地指出 deno 成百上千的不足,來掩飾自己心中的不安和對未來的迷茫。

可以用 deno 做下一代的沙箱麼?

在 Ryan 的演講中,第二個 regret 是 Security,我認爲可能是 deno 相對於 node 做出的最重要的架構上的重塑。v8 傾盡全力打造了一個安全的沙箱,node 卻只關心其 javascript 解釋器,對沙箱所帶來的安全性棄若敝履。站在 09 年那個 web 2.0 纔剛剛開蒙的時代,這麼做無可厚非 —— 誰能預料到十年後,服務器的世界就進入了一個沙箱(VM / container / wasm)橫行的時代呢?

相對於 VM 巨大的資源佔用和難以忍受的啓動時間,容器技術在效率和安全性隔離性之間達到了一個不錯的平衡。然而,雖然通過多種手段優化,容器的冷啓動時間還是在數秒(fargate 60-90s,aws lambda 5s)[2],而容器的打包也是一個相對緩慢的過程。我們迫切需要新的沙箱技術來讓冷啓動和打包更上一個臺階:於是,有了尚在發展的 WASM/WASI 技術。相對於包含了語言運行時和各種庫文件的容器來說,WASM 非常精簡,啓動很快(毫秒級),且不需要額外的打包。

不過 WASM 還需要一個編譯的過程。一個小型的 Rust 項目,全量編譯成 WASM,需要幾十秒到幾分鐘時間,雖然比容器的打包快了不少,但還不是即時的。有沒有一種技術,在保證安全性和隔離性的同時,冷啓動時間足夠短,執行效率足夠高,還可以即時部署,即時更新?

有!這就是我們在瀏覽器上跑了將近 30 年的 javascript。毫不誇張地說,瀏覽器中的 JS 引擎承受的安全壓力是頂級的,比如 chrome 中的 v8,每天要面對全球 26 億用戶(chrome 目前是 26.5 億用戶的主瀏覽器 [3])的各種各樣的 javascript 請求,迄今爲止,v8 並未遭受過使用戶蒙受巨大的損失的安全漏洞,因此它的沙箱的安全性也是頂級的。除安全外,v8 從誕生到現在一直琢磨的就是如何讓 javascript 加載和運行地更快一點。Cloudflare 在其邊緣計算的 cloudflare worker 中使用了 v8 來跑用戶的腳本,可以讓冷啓動的時間低至 0-5ms [4]。這個時間低得難以置信,但考慮到相對於其它解決方案,v8 運行一個用戶腳本,只是創建一個 isolate 運行這個腳本而已,所以效率很高。

正因爲 Ryan 在 2018 年看到了沙箱化的服務器世界的巨大潛力,因此 deno 才義無反顧地擁抱了 v8 提供的沙箱(isolate),並把圍繞着 v8 構建的一系列 op(比如文件的讀寫)都加入了權限的控制。

於是乎,deno 看上去像是一個服務端的 chrome。它用 isolate 隔離用戶的代碼,並可以在極短的時間內加載並運行幾乎不可能進行任何惡意行爲的用戶代碼(如果權限控制得當)。所以 deno deploy 用它做邊緣計算(cloudflare worker 也用了 v8,但應該不是用 deno)。

然而,當人們把 deno 作爲另一個 node 使用的時候,便註定會抱怨「node 已經足夠好,另起爐竈的 deno 有何意義」。就如同莊子講的這個故事:

宋人有善爲不龜手之藥者,世世以洴澼絖爲事。客聞之,請買其方百金。聚族而謀曰:‘我世世爲洴澼絖,不過數金,今一朝而鬻技百金,請與之。’客得之,以說吳王。越有難,吳王使之將,冬,與越人水戰,大敗越人。裂地而封之。能不龜手一也,或以封,或不免於洴澼絖,則所用之異也。

所以,deno 最重要的意義在於構建一個服務端的沙箱環境。如果 deno 的缺省功能並不滿足你的使用場景,那麼,還可以通過在 rusty_v8,deno_core,deno_runtime 各個層級進行裁剪,構建符合你需求的沙箱環境。這個沙箱可以用來做服務端和客戶端的用戶代碼執行(比如插件),也可以創建一個多租戶的使用環境。

由 deno 想到的未來的軟件開發場景...

互聯網軟件開發,對我而言,最大的痛點就在於部署。雖然 kubernetes 已經把部署的效率大大提升,但每次部署,還是以分鐘爲量級的。然而 deno deploy 給我們展示了另外一種可能:代碼在提交的同時就能夠被部署,並且,立等可用:

由此,對於業務代碼的開發者,不需要了解一堆 devOps/kubernetes 的知識,不需要維護各種部署配置,也不需要部署代碼的時候經歷漫長的等待,更不會遇到莫名其妙的部署問題。git commit,打開瀏覽器,新代碼已在線,bingo!

整個部署的過程其實就是一個狀態更新和文件拷貝的過程。甚至,文件拷貝都可以避免,因爲 deno 支持從 url import 或者運行,比如這樣:

由於部署從原來的分鐘級躍遷到秒級,且部署過程中需要的算力很少,那麼,如果不涉及數據 schema 的變動,任何業務邏輯都可以無障礙地隨時隨地部署和撤回。因爲無所謂 staging / production / test / dev 環境 —— 每個環境不過是一份份略有不同的代碼(及配置),它們在運行時對應不同的 v8 isolate,所以,我們可以用 git branch 來對應不同的環境。代碼在 master branch 上開發,隨時 commit 隨時體驗最新的版本,這是 dev 環境;需要發佈時,從 master merge 到 release branch,做各種集成測試,跑 regression,此時是 test / staging 環境;測試通過後,在 release branch 上打 tag,此時代碼被髮布到 production 環境,以 canary 方式發佈。在 ingress 側,我們可以根據用戶 id,把流量發往不同版本的代碼(isolate):

當然,這裏所描述的場景,目前的 deno 都還不直接支持,但 deno 已經爲此提供了堅實的基礎。通過對 deno_runtime / deno_core 進行二次開發,這些目標並不難實現。

由此我們還可以演進出一套高效的軟件開發流程:任何在研究階段的,或者早期的,或者確定性不太高的功能,都以 javascript/typescript 的形式快速開發,零成本快速部署,快速驗證。當假設得到驗證(實驗成功),或者功能得到確定,我們再根據需要將其核心部分用 rust 實現,部署到 runtime 中,以 op / extension 的形式暴露給 javascript/typescript,達到快速原型和極致高效的平衡 —— 其實 deno 自己,也用類似的方式演進:早期 deno 的 typescript 編譯器,使用了 javascript 版本的 tsc,當效率更高的 swc(Rust 版 ts 編譯器)出現後,就鳥槍換炮,大大提升了效率。

參考資料

[1] 10 Things I regret about Node.js: https://www.youtube.com/watch?v=M3BM9TB-8yA

[2] Should I run my container on AWS Fargate, AWS lambda, or both: https://awscloudfeed.com/whats-new/architecture/should-i-run-my-containers-on-aws-fargate-aws-lambda-or-both

[3] Google chrome statistics 2022: https://backlinko.com/chrome-users

[4] Eliminating cold starts with cloudflare workers: https://blog.cloudflare.com/eliminating-cold-starts-with-cloudflare-workers/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yovxkRC_RPg7kzGnGkUzag