Shopify如何在瀏覽器之外使用WebAssembly?

來(lái)源: InfoQ
作者:Duncan Uszkay
時(shí)間:2021-05-11
16729
Shopify 致力于讓大多數(shù)商家都需要的功能變得簡(jiǎn)單易用,并通過(guò)接口在 Shopify 平臺(tái)上執(zhí)行查詢、擴(kuò)展和更改,進(jìn)而為商家提供更多可能。借助這些接口,我們豐富的合作伙伴生態(tài)系統(tǒng)可以解決諸多問(wèn)題。這一生態(tài)系統(tǒng)主要借助“App”(一個(gè)獨(dú)立托管的 Web 服務(wù))來(lái)運(yùn)作。該 App 通過(guò)網(wǎng)絡(luò)與 Shopify 進(jìn)行通信。盡管這種模式很強(qiáng)大,但會(huì)帶來(lái)一系列技術(shù)問(wèn)題。我們的合作伙伴需要打造能夠隨 Shopify 規(guī)模擴(kuò)展的 Web 服務(wù),這讓一些本就資源有限的合作伙伴越發(fā)捉襟見(jiàn)肘。即便合作伙伴有無(wú)限的

Shopify 致力于讓大多數(shù)商家都需要的功能變得簡(jiǎn)單易用,并通過(guò)接口在 Shopify 平臺(tái)上執(zhí)行查詢、擴(kuò)展和更改,進(jìn)而為商家提供更多可能。借助這些接口,我們豐富的合作伙伴生態(tài)系統(tǒng)可以解決諸多問(wèn)題。這一生態(tài)系統(tǒng)主要借助“App”(一個(gè)獨(dú)立托管的 Web 服務(wù))來(lái)運(yùn)作。該 App 通過(guò)網(wǎng)絡(luò)與 Shopify 進(jìn)行通信。盡管這種模式很強(qiáng)大,但會(huì)帶來(lái)一系列技術(shù)問(wèn)題。我們的合作伙伴需要打造能夠隨 Shopify 規(guī)模擴(kuò)展的 Web 服務(wù),這讓一些本就資源有限的合作伙伴越發(fā)捉襟見(jiàn)肘。即便合作伙伴有無(wú)限的資源,在與 Shopify 通信時(shí)產(chǎn)生的網(wǎng)絡(luò)延遲也足以讓我們的 App 在對(duì)時(shí)效性要求很高的用例中敗下陣來(lái)。

我們希望我們的合作伙伴能夠?qū)W⒂诶盟麄兊膶iL(zhǎng)來(lái)解決問(wèn)題,而不用花費(fèi)時(shí)間管理可擴(kuò)展的 Web 服務(wù)。為實(shí)現(xiàn)這一目標(biāo),我們保留了不受信任的合作伙伴代碼的靈活性,并將其在我們的基礎(chǔ)設(shè)施上運(yùn)行。為了確保這些代碼的性能、安全性與靈活性,我們選擇了 WebAssembly 這種通用格式。

WebAssembly

什么是 WebAssembly?WebAssembly.org 給出了如下定義:

”WebAssembly(縮寫(xiě)為 Wasm)是一種基于堆棧虛擬機(jī)的二進(jìn)制指令格式。Wasm 是為編程語(yǔ)言設(shè)計(jì)的可移植編譯目標(biāo),使客戶端和服務(wù)器應(yīng)用程序能夠在 Web 上部署?!?/p>

如需詳細(xì)了解 WebAssembly 及其歷史,可以瀏覽由 Mozilla 的 Lin Clark 撰寫(xiě)的這篇圖文并茂的文章。本文在此不做詳述。

Wasm 通常都是與 JavaScript 一起在瀏覽器內(nèi)運(yùn)行,但 Shopify 卻另辟蹊徑,在瀏覽器之外運(yùn)行 Wasm,并且不用到 JavaScirpt。作為一款高性能語(yǔ)言,Wasm 絕非 JavaScript 的單純替代品:它面向 Web 和非 Web 的嵌入而設(shè)計(jì),解決了廣泛存在于瀏覽器和代碼執(zhí)行引擎中的一個(gè)難題,即如何在不受信任的環(huán)境中高效執(zhí)行程序。Wasm 滿足了我們的三大主要技術(shù)需求:安全性、性能和靈活性。

安全性

運(yùn)行不受信任的代碼具有極大的風(fēng)險(xiǎn)。從本質(zhì)上來(lái)講,這些代碼不僅難以預(yù)測(cè),并且還很有可能對(duì)整個(gè) Shopify 平臺(tái)造成損害。盡管市面上并沒(méi)有百分之百安全可靠的應(yīng)用,但我們還是要防范安全漏洞,并且在出現(xiàn)問(wèn)題后采取措施來(lái)減輕其影響。

Wasm 將代碼執(zhí)行放到了一個(gè)基于堆棧的沙箱環(huán)境中,依靠顯式導(dǎo)入來(lái)與主機(jī)進(jìn)行通信。因此,我們無(wú)法在 Wasm 中寫(xiě)入任何惡意代碼,只能使用提供的輸入端口操作虛擬環(huán)境。在這一點(diǎn)上 Wasm 與字節(jié)碼有所不同,字節(jié)碼在語(yǔ)法中直接引用了它們希望在其中運(yùn)行的計(jì)算機(jī)或操作系統(tǒng)。

Wasm 還有很多不同的功能,可讓用戶免受錯(cuò)誤代碼的影響,包括受保護(hù)地調(diào)用堆棧和運(yùn)行時(shí)類型檢查。WebAssembly.org 上提供了更多關(guān)于 Wasm 安全模型的詳細(xì)資料。

性能

在電商領(lǐng)域,較快的運(yùn)行速度才是商家推動(dòng)業(yè)績(jī)?cè)鲩L(zhǎng)時(shí)必備的利器。如果 Shopify 提供的功能無(wú)法兼顧加載時(shí)間和定制價(jià)值,那么這種功能壓根就沒(méi)有任何價(jià)值可言。

Wasm 本身的設(shè)計(jì)充分利用了常見(jiàn)的硬件功能,并在各種平臺(tái)上發(fā)揮出最接近原生的性能。它面向追求最高性能、優(yōu)化瀏覽器執(zhí)行的開(kāi)發(fā)者社區(qū)。因此,無(wú)論是現(xiàn)在還是未來(lái),Wasm 和它的周邊工具在設(shè)計(jì)上都會(huì)以性能優(yōu)化為中心。

靈活性

能幫助開(kāi)發(fā)者提高開(kāi)發(fā)效率的代碼執(zhí)行服務(wù)才是真正有用的服務(wù)。Wasm 作為一款字節(jié)碼格式,與多種編譯器相兼容,為代碼開(kāi)發(fā)者提供了支持多種編程語(yǔ)言的一流開(kāi)發(fā)體驗(yàn)。這也讓我們能夠在不改變底層執(zhí)行模型的情況下,提供多語(yǔ)言支持。

基于社區(qū)

Shopyify 在發(fā)展目標(biāo)和設(shè)計(jì)方面基本保持一致,這為我們選擇 Wasm 提供了技術(shù)上的理由,但事實(shí)并不僅限于此:我們對(duì) Wasm 的選擇不僅關(guān)乎于技術(shù),更關(guān)乎于人。如果 Wasm 生態(tài)系統(tǒng)無(wú)人問(wèn)津,或者它僅在生死線上垂死掙扎,那么我們不會(huì)選擇它。WebAssembly 的社區(qū)是個(gè)充滿活力的社區(qū),不斷創(chuàng)新,它的潛力是無(wú)窮的。自從加入這個(gè)充滿熱情的社區(qū),Shopify 就獲益匪淺。

同樣,我們也在為社區(qū)貢獻(xiàn)出我們的力量。通過(guò)收集用戶反饋,探討功能缺陷,以及為我們使用的開(kāi)源工具提交代碼貢獻(xiàn)。我們認(rèn)為,這為我們與 WebAssembly 社區(qū)之間建立良好的互惠合作關(guān)系打下了堅(jiān)實(shí)基礎(chǔ),我們也期望著在未來(lái)能夠繼續(xù)為這個(gè)鮮活的社區(qū)獻(xiàn)出我們的力量。

代碼執(zhí)行服務(wù)的架構(gòu)

在簡(jiǎn)單介紹過(guò) WebAssembly 以及我們選擇它的原因后,下一步就來(lái)深入探討我們的運(yùn)行方案。

我們使用的是最初由 Fastly 開(kāi)發(fā)的開(kāi)源工具 Lucet。Fastly 這家公司為大批量壽命不長(zhǎng)且不受信任的模塊提供了一個(gè)可編程的邊緣云平臺(tái),讓它們可以在盡可能接近發(fā)起請(qǐng)求的地方執(zhí)行請(qǐng)求。這與我們的合作方提供的代碼所面臨的問(wèn)題相同,因此,我們自然而然就選擇了 Lucet。

Lucet

Lucet 是 Wasm 的運(yùn)行時(shí)和編譯器。Wasm 中的模塊確保了系統(tǒng)的安全性,由于我們無(wú)法在 Wasm 中寫(xiě)入惡意代碼,因此 Lucet 利用 Wasm 模塊的驗(yàn)證進(jìn)行安全檢查。在驗(yàn)證之后,模塊會(huì)被編譯為一個(gè)可執(zhí)行的文件,其性能可以達(dá)到原生狀態(tài)。另外,Wasm 還支持提前編譯,可避免執(zhí)行運(yùn)行時(shí)編譯帶來(lái)的延遲。Lucet 容器在啟動(dòng)時(shí)無(wú)需執(zhí)行任何操作,這讓它擁有了令人驚嘆的 35μs 啟動(dòng)時(shí)間。如果您對(duì) Lucet 及其工作原理感興趣,可以去看看 Fastly 的 CTO Tyler McMullan 的演講視頻。

Shopify 中 Wasm 引擎的工作原理流程圖

我們將 Lucet 包裝在一個(gè)管理 I/O 和模塊存儲(chǔ)的 Rust Web 服務(wù)里,并將其稱作是 Wasm 引擎。在運(yùn)行時(shí),Shopify 通過(guò) Web 請(qǐng)求調(diào)用 Wasm 引擎以處理部分功能。引擎之后再調(diào)用站點(diǎn)的上下文中應(yīng)用輸出,這里的上下文可能會(huì)涉及到創(chuàng)建折扣、執(zhí)行約束,或者是任何商家想要在平臺(tái)中私人定制的同步服務(wù)。

運(yùn)行性能

下圖中是我們?cè)谧罱淮蔚男阅軠y(cè)試中提取到的一些指標(biāo)。我們選擇了一個(gè)很小的功能及逆行測(cè)試:讓模塊對(duì)用戶購(gòu)物車(chē)中添加的物品數(shù)量進(jìn)行限制。在測(cè)試期間,每分鐘執(zhí)行十萬(wàn)個(gè)模塊,持續(xù)時(shí)間約 5 分鐘。

模塊執(zhí)行所需時(shí)間

該圖表展示了執(zhí)行一個(gè)模塊所需時(shí)間的詳細(xì)情況,其中包括容器的 I/O 和模塊的執(zhí)行。y 軸代表時(shí)間(單位:ms),x 軸代表測(cè)試運(yùn)行的具體時(shí)間。

圖中的淺紫色圖例代表 Lucet 中執(zhí)行模塊需要的時(shí)間,其寬度大約在 100μs 左右徘徊。其余圖標(biāo)則是 I/O 的處理和引擎的具體情況,可以看出執(zhí)行的全部時(shí)間大約在 4ms 左右。所有的時(shí)間顯示都是第 99 位百分比(99p)。為了能更好地理解圖中時(shí)間的含義,下面讓我們將用 Shopify 中性能卓越的在線商店渲染服務(wù):Storefront Renderer 的測(cè)試請(qǐng)求時(shí)間做比較。

Storefront Renderer 響應(yīng)時(shí)間

這張圖表中展示了 Storefront Renderer 在一段時(shí)間內(nèi)的請(qǐng)求時(shí)間。y 軸代表請(qǐng)求時(shí)間(單位:秒),x 軸代表返回?cái)?shù)值時(shí)的具體時(shí)間。淺藍(lán)色線條代表在 700 毫秒左右的第 99 百分比。

如果將模塊處理時(shí)間大致估算在 5 毫秒內(nèi),那么可以說(shuō) Lucet 執(zhí)行時(shí)間帶來(lái)的性能影響幾乎可以忽略不計(jì)。

生成 WebAssembly

為了讓我們性能卓越的執(zhí)行引擎發(fā)揮作用,我們還需要授權(quán)開(kāi)發(fā)者創(chuàng)建兼容的 Wasm 模塊。Wasm 的作用并不是讓用戶親自編寫(xiě)(想寫(xiě)當(dāng)然是可以寫(xiě)的)代碼,而是作為一個(gè)編譯目標(biāo)存在。這就會(huì)讓我們思考以下問(wèn)題:我們支持哪些編程語(yǔ)言,具體又要支持到什么程度。

理論上來(lái)說(shuō),任何有 Wasm 支持的開(kāi)發(fā)語(yǔ)言都是可以的。但是,我們更希望開(kāi)發(fā)者可以將精力集中在為商家解決問(wèn)題上,而不是研究要如何符合我們的 API。這也是我們選擇單一語(yǔ)言 Ruby 支持,并為開(kāi)發(fā)者提供快速啟動(dòng)工具的原因。然而,由于 Ruby 動(dòng)態(tài)語(yǔ)言的特性,我們并不能將其直接編譯為 Wasm,而涉及編譯解釋器的解決方案會(huì)有嚴(yán)苛的性能懲罰。正因如此,我們最終決定采用靜態(tài)編譯的語(yǔ)言,并將動(dòng)態(tài)語(yǔ)言編譯的可能性留待未來(lái)。

通過(guò)我們的調(diào)研發(fā)現(xiàn),Shopify 生態(tài)系統(tǒng)中的開(kāi)發(fā)者大多能對(duì) JavaScript 熟練應(yīng)用??上У氖?,由于 JavaScript 與 Ruby 一樣是動(dòng)態(tài)語(yǔ)言,只得被排除在外。最終,我們選擇了一種語(yǔ)法類似于 TypeScript 的開(kāi)發(fā)語(yǔ)言:AssemblyScript。

使用 AssemblyScript

雖然 WebAssembly 支持大量開(kāi)發(fā)語(yǔ)言,但其中有兩大類編譯器是我們無(wú)法使用的:

  • 生成環(huán)境或開(kāi)發(fā)語(yǔ)言特定產(chǎn)物的編譯器,即節(jié)點(diǎn)或?yàn)g覽器。(例如 Asterius、Blazor)

  • 只適用于特定運(yùn)行時(shí)的編譯器。這些編譯器生成的模塊依賴于特定語(yǔ)言的特定導(dǎo)入,通常是為了支持某些特定語(yǔ)言的標(biāo)準(zhǔn)庫(kù),讓他們能夠在系統(tǒng)調(diào)用或運(yùn)行時(shí)功能可用而存在的。因?yàn)槲覀儾⒉幌氡绘i死在某一特定語(yǔ)言上,所以這類編譯器就不在我們的考慮范圍內(nèi)了。(例如 Lumen)

這些功能強(qiáng)大的編譯器在其他情況下或許能夠發(fā)揮奇效,但可惜無(wú)法為我們所用。我們需要能夠生成 WebAssembly 的工具,而不是由 WebAssembly 支持的工具。AssemblyScript 便是被我們選中的工具。

與 WebAssembly 中的其他工具一樣,AssemblyScript 還在開(kāi)發(fā)過(guò)程中。它缺乏一些諸如閉包支持等關(guān)鍵功能,在邊緣情況下仍會(huì)報(bào)錯(cuò)。這時(shí)候就顯現(xiàn)出了社區(qū)的重要性。

開(kāi)發(fā)語(yǔ)言 AssemblyScript 和它的周邊工具擁有一個(gè)用戶活躍的愛(ài)好者和維護(hù)者社區(qū),自從 2019 年 Shopify 首次使用 AssemblyScript 以來(lái),他們就一直在支持著我們。而我們也通過(guò) OpenCollective 長(zhǎng)期貢獻(xiàn)代碼以支持社區(qū)。我們編寫(xiě)完成了一個(gè)語(yǔ)言服務(wù)器,在實(shí)現(xiàn)閉包方面也取得里一些進(jìn)展,也為編譯器和周邊工具提供了錯(cuò)誤修復(fù)。

我們還將 AssemblyScript 融入了我們?cè)缙诘墓ぞ咧小T?Shopify CLI 中,我們通過(guò)集成 AssemblyScript,允許開(kāi)發(fā)者通過(guò)命令行創(chuàng)建、測(cè)試和部署模塊。為了提高開(kāi)發(fā)效率,我們提供了可以處理 Shopify 定義對(duì)象(例如“Money”)底層實(shí)現(xiàn)的 SDK。除了這些工具,我們還搭建了一個(gè)允許合作伙伴監(jiān)控模塊的系統(tǒng),方便他們?cè)谀K出現(xiàn)故障時(shí)收到警報(bào)。我們的最終目標(biāo)是讓合作伙伴們能夠在不失去代碼在原生平臺(tái)上靈活性和可觀察性的前提下,將他們的代碼遷移到我們的平臺(tái)之上。

立即登錄,閱讀全文
版權(quán)說(shuō)明:
本文內(nèi)容來(lái)自于InfoQ,本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個(gè)人觀點(diǎn),不代表快出海對(duì)觀點(diǎn)贊同或支持。如有侵權(quán),請(qǐng)聯(lián)系管理員(zzx@kchuhai.com)刪除!
優(yōu)質(zhì)服務(wù)商推薦
更多
掃碼登錄
打開(kāi)掃一掃, 關(guān)注公眾號(hào)后即可登錄/注冊(cè)
加載中
二維碼已失效 請(qǐng)重試
刷新
賬號(hào)登錄/注冊(cè)
個(gè)人VIP
小程序
快出海小程序
公眾號(hào)
快出海公眾號(hào)
商務(wù)合作
商務(wù)合作
投稿采訪
投稿采訪
出海管家
出海管家