跳至主要內容
蒼時弦也
蒼時弦也
資深軟體工程師
發表於

Kobako:從 Ruby 到 mruby

這篇文章是 Kobako 系列的一部分。

雖然是受到 ruby.wasm 的限制才選擇 mruby 來作為沙盒(Sandbox)的語言,但也不代表跟 CRuby 都使用相同的語言標準(ISO/IEC 30170:2012)就讓這個目標輕鬆實現,相比 CRuby 在 mruby 上有很多限制存在。

這是 mruby 為了能夠在嵌入式系統這類環境運作必要的取捨,相對的輕量的特性剛好也是一種 Embedded Sandbox 的優勢,我們把 Ruby 語言沙盒嵌入到任何語言的這種想法,變成可行的選項。

Marshal

dRuby 之所以能利用非常少量的程式碼實作 RPC(Remote Procedure Call)是善用了許多語言特性,我在上一篇文章提過,利用 method_missing 的機制可以在遠端沒有定義方法的前提下,正常呼叫。

當遇到資料傳輸時,就輪到 Ruby 的 Marshal 機制出場,當我們需要傳輸一個物件時,只需要使用 .dump 方法就可以轉換成一個二進位的資料。

1args = [1, 2, 3]
2bin = Marshal.dump(args) # => \x00 ...

基於這樣的特性,原本要呼叫遠端的方法會需要的參數,就能夠被傳遞到遠端。

 1class Proxy
 2  def method_missing(method, *args, **kwargs)
 3     raise if @target.respond_to(method)
 4     
 5     # @server.send(method, *args, **kwargs)
 6     bin = Marshal.dump([method, args, kwargs])
 7     @server.write(bin)
 8     res = Marshal.load(@server.read)
 9  end
10end

這樣就讓整個傳輸都解決了,換到 Ruby 和 mruby 之間的溝通,直覺會覺得像這樣,應該是很單純用 Marshal 來解決就好,但現實不會這麼順利。

mruby 沒有原生的 Marshal 機制,而且這個機制很可能因為 CRuby 和 mruby 在 C 語言層級的記憶體結構不同,不一定能直接使用。

大部分語言都有類似的標準函式庫,像是 Golang 的 gob 或者 Python 的 pickle 可以使用

JSON

既然 Ruby 原生的 Marshal 無法使用,我們只需要找到類似的機制替代,使用 JSON 很可能就是一個非常直覺的做法。

在大多數語言中,對 JSON 的處理已經非常成熟,而且 mruby 也有 mruby-json 這樣的 Gem 存在,理論上可以很簡單的在 Ruby 使用 #to_json 並且在 mruby 用 JSON.parse 就順利的還原回來。

將 JSON 應用在 RPC 也不是沒有先例,像是 JSON-RPC 就是一個例子,在不同語言的支持度 JSON 都是相當不錯的,未來想要跨語言也沒有問題。

然而 JSON 也有著一些限制,像是在這幾年為了讓 AI 的 Token 效率更好,大家開始想找其他格式表示,就是因為 JSON 在可讀性上的優勢,是用空間來換的,跟其他同類型工具,通常會佔用更多空間(如:" 包覆表示字串等等)

也因為這種結構的特性,在處理上通常也會比較慢,像是要確認 " 到下一個 " 的位置,才能區分是否為字串,如果使用二進位表示,通常只需要 [type][len][data] 兩個位元標記搭配資料,就不需要多記憶一個 " 開啟的旗標,也不一定需要回退來確認。

至少,設計初期或者考慮到通用性 JSON 是很不錯的,但是 Kobako 的定位在更底層一些,有其他選擇。

MessagePack

MessagePack 在介紹中直接明講是一種「類似 JSON」的格式,但是更小也更快,他是使用了上面我提到的 [type][len][data] 的結構來處理,光是省下大量的 {" 這類符號,我們就可以節省不少傳輸、編碼、解碼的時間。

然而,在 Kobako 的選項中,如果把 JSON 排除掉幾乎也只剩下 MessagePack 可以選,原因是更快的 Protobuf 是因為有 Schema(樣式)的約定,因此連 [type] 都不需要,只要知道是屬於什麼的資料,就一定是特定的方式讀取,那就可以更快。

Ruby 和 mruby 都具備直譯(Interpreter)的能力,因此我們的程式在執行階段可以任意地改變資料結構,如果用 Protobuf 的方式設計,等於要求使用者每次都先自己編譯 Ruby Gem 和搭配的 mruby 才能確定如何溝通,這對 Ruby 或者 JavaScript 這類型語言的優勢來說,直接變成缺點。

也因此,在「速度」跟「彈性」之間,選擇 MessagePack 可以兼顧兩者,和 JSON 相比又更好一些,至少沒有什麼好挑剔的。

Kobako 最後選擇 MessagePack 還有一些額外的理由,主要是 Fluent Bitmruby-engine 兩個由大公司主導的 Ruby 專案,也都選擇 MessagePack 當傳輸格式,至少是被驗證過可行的做法。

接下來,我們要解決跟 MessagePack 一起被驗證過的問題,Ruby 如何跟被隔離在 WebAssembly VM 的 mruby 互相傳輸資料。