Guide 開髮指南

fibjs 是什麼?

fibjs 是一個專為web 後端開發設計的應用程式伺服器開發框架。它建立在Google V8 JavaScript 引擎的基礎上,並採用與傳統回調不同的並發解決方案。 fibjs 使用fiber(纖程)在框架層面上隔離了非同步呼叫所帶來的業務複雜性,大大降低了開發難度,並減少了用戶空間頻繁的非同步處理而導致的效能問題。

由於歷史原因,JavaScript 主要被用來處理瀏覽器的UI。 UI 開發是典型的單執行緒事件驅動模式,因此JavaScript 逐漸形成了以非同步處理為主要程式設計範式。

隨著JavaScript 的成功,越來越多的人開始將JavaScript 套用到其他場景。同時,人們也越來越意識到在許多場景中,非同步處理並不是最適合的選擇。

返璞歸真,敏捷開發

fibjs 在框架層面使用fiber(纖程)隔離了非同步呼叫所帶來的業務複雜性,將I/O 的非同步處理封裝為更直觀的同步呼叫。工程師只需按照通常的同步業務邏輯編寫程式碼,即可享受非同步處理所帶來的巨大便利。

以下這段程式碼摘自mysql 模組的文檔:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
conn.beginTransaction(err => { if (err) { throw err; } conn.query('INSERT INTO posts SET title=?', title, (error, results, fields) => { if (error) { return conn.rollback(() => { throw error; }); } var log = 'Post ' + results.insertId + ' added'; conn.query('INSERT INTO log SET data=?', log, (error, results, fields) => { if (error) { return conn.rollback(() => { throw error; }); } conn.commit((err) => { if (err) { return conn.rollback(() => { throw err; }); } console.log('success!'); }); }); }); });

在fibjs 中,完成同樣的工作,程式碼如下:

1 2 3 4 5 6
conn.trans(() => { var result = conn.execute('INSERT INTO posts SET title=?', title); var log = 'Post ' + results.insertId + ' added'; conn.execute('INSERT INTO log SET data=?', log); }); console.log('success!');

如果你追求簡潔,你甚至可以把程式碼寫成這樣:

1 2 3 4
conn.trans(() => conn.execute('INSERT INTO log SET data=?', 'Post ' + conn.execute('INSERT INTO posts SET title=?', title).insertId + ' added')); console.log('success!');

透過比較,我們可以明顯看到不同程式設計風格所帶來的差異。較少的程式碼意味著較少的錯誤,隨著程式碼的減少,程式碼的邏輯也會更加清晰。在這種情況下,不論是開發還是維護工作都會獲益。

擁抱高能

儘管擴展伺服器來提高回應速度是相對容易的,但效能仍然是選擇一個開發框架時的重要考慮因素之一。隨著ES7 的推出,async 作為一種新的非同步開發模式被引入到JavaScript 中。然而,當我們享受async 帶來的同步程式風格時,也必須面對它對效能的影響。

為了比較不同程式設計風格所帶來的效能差異,我們可以使用以下測試程式碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
var count = 1000; async function test_async(n) { if (n == count) return; await test_async(n + 1); } function test_callback(n, cb) { if (n == count) return cb(); test_callback(n + 1, () => { cb(); }); } function test_sync(n) { if (n == count) return; test_sync(n + 1); } async function test() { console.time("async"); await test_async(0); console.timeEnd("async"); console.time("callback"); test_callback(0, () => { console.timeEnd("callback"); }); console.time("sync"); test_sync(0); console.timeEnd("sync"); } test();

在最新的v8 下,這段程式碼的運行結果如下:

1 2 3
async: 0.539ms callback: 0.221ms sync: 0.061ms

從測試結果中,我們可以清楚地看到,當廣泛應用async 後,伺服器將花費大量時間來處理async 函數的呼叫和返回。我們在一些實際的服務端應用程式測試中也發現了這一點。然而,這種急劇下降的表現是完全無法接受的。

與此相比,fibjs 使用fiber 技術,充分利用了JavaScript 語言本身的特性,並最大限度地發揮了V8 引擎的優越性能。工程師可以輕易地將伺服器的效能發揮到極致。

靈活選擇範式而不被綁架

選擇使用fibjs 並不代表你必須使用同步的開發風格。實際上,fibjs 支援你所熟悉的各種非同步程式設計範式,並且可以靈活地在同步風格和非同步風格之間切換。

然而,無論是回呼函數還是async,它們都有一個致命的缺陷,那就是傳染性。如果函數是回呼函數或async 函數,那麼所有依賴它的其他函數也必須是回呼函數或async 函數。在大規模軟體開發中,這將導致巨大的開發成本。

以一個簡單的伺服器開發場景為例。在專案初期,我們選擇將session 資料儲存在記憶體中,此時,我們可以使用同步方式直接讀取和儲存數據,並基於此開發完整的業務功能。隨著業務規模的擴大,我們需要將session 資料儲存到Redis 或MongoDB 中,此時,我們需要將與session 相關的操作改為非同步模式。

理論上,我們可以逐一修改每個函數,使它們符合所依賴函數的要求,但這要求我們完全了解所有模組並具備修改它們的能力。當多人協作開發或使用第三方模組時,這幾乎是不可能的。

因此,在所有通用模組中,都應同時提供同步和非同步接口,以平衡非同步和效能之間的關係。而普通開發者通常會選擇僅提供非同步接口,從而導致效能問題。

在fibjs 中,你可以輕鬆解決類似問題,避免不受控制地傳播顯式非同步:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
var util = require('util'); function session_get(sid) { return sdata; } async function async_session_get(sid) { return sdata; } function callback_session_get(sid, cb) { cb(null, sdata); } data = session_get(sid); data = util.sync(async_session_get)(sid); data = util.sync(callback_session_get)(sid);

fibjs 提供了util.sync 函數,可以將回呼函數或非同步函數轉換為同步函數並直接呼叫。借助這種方式,我們可以輕鬆地整合不同程式設計範式的模組,並最小化開發成本,將它們轉變為同步範式,有效地避免範式傳染帶來的災難。

開始體驗

準備好開始一場愉快的開發經驗了嗎?那麼,就從安裝開始吧。

👉 【安裝運作環境