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
32conn.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
6conn.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
4conn.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
39var 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
3async: 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
17var 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 範式,有效地避免範式傳染帶來的災難。
開始體驗
準備好開始一場愉快的開發經歷了嗎?那麼,從安裝開始吧。
👉 【安裝運行環境】