Guide 開髮指南

fibjs 是什麼?

fibjs 是一個主要為web 後端開發而設計的應用服務器開發框架,它建立在Google v8 JavaScript 引擎基礎上,並且選擇了和傳統的callback 不同的並發解決方案。fibjs 利用fiber 在框架層隔離了異步調用帶來的業務複雜性,極大降低了開發難度,並減少因為用戶空間頻繁異步處理帶來的性能問題。

由於歷史原因,JavaScript 主要被用於瀏覽器的UI 處理,UI 開發是典型的單線程事件驅動模式,因此JavaScript 也形成了以異步處理為主要編程範式。

隨著JavaScript 的成功,越來越多的人開始將JavaScript 應用到其它的場景。與此同時,人們也越來越發現在很多場景下異步處理並不是最合適的選擇。

返璞歸真,敏捷開發

fibjs 在框架層使用fiber 隔離了異步調用帶來的業務複雜性,將io 的異步處理封裝為更加直觀的同步調用,工程師只需要按照通常的同步業務邏輯編寫代碼,即可享有異步處理帶來的巨大便利。

以下這段代碼摘自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 支持你所見過的任何一種異步編程範式,並且可以靈活地在同步風格和異步風格之間切換。

無論是callback 還是async,都有一個致命的缺陷,那就是傳染性。只要一個函數是callback 或者async,那麼所有依賴它的其它函數都必須是callback 或者async。這在大規模軟件開發中將帶來巨大的開發成本。

以一個簡單的服務器開發場景為例。在項目初期,我們選擇了內存作為session 數據存儲,此時,我們可以使用sync 方式直接讀取和存儲數據,並基於此開發出完整業務。隨著業務規模的發展,我們需要把session 數據存儲到redis 或者mongodb 裡,此時,我們就需要把session 相關的操作修改為async 模式。

理論上,我們可以依次修改每一個函數,讓它們符合所依賴的函數的要求,但是這樣就要求我們完全了解所有的模塊並且有能力對其作出修改。這在多人協作開發時或者使用第三方模塊時是完全不可能完成的。

因此,所有的通用模塊,都需要同時提供sync 和async 接口,以均衡處理異步和性能之間的平衡。更多的普通開發者則會選擇只提供async 接口。從而引發性能災難。

在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 將callback 或者async 函數轉變為sync 函數,並且直接調用。通過這種方式,我們可以很方便地整合不同編程範式的模塊,並且以最小的開發成本將其轉變為sync 範式,有效地避免範式傳染帶來的災難。

開始體驗

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

👉 【安裝運行環境