BFF層提升業務效能實際解決方案,以及nodeJs和KOA框架介紹
本文乾貨滿滿,介紹了用BFF層(Back-end For Front-end)中間層提升效能的整體解決方案和思路,涉及前期技術調研,聚合業務分析,聚合方法,驗收,最後向同學們普及node、koa基礎知識,以及如何通過中間層做優化等一系列的內容,形成 發起問題——解決問題——覆盤學習推廣 的完整閉環。
業務介紹
業務背景
我們負責的系統,既有內部各細分領域的功能分支,也有大量的功能交集,展示和互動要求極高。
- 後端微服務化,使得資料更加碎片化,請求數量增加,建立更多連線,且資料包大,傳輸耗費頻寬。
- 微前端技術的應用,使前端檔案也變得碎片化。
最終導致:
- 頁面有效內容的展示發生延遲。
- 並且功能互動產生卡頓。
通過以下截圖,可以看到,一個頁面的請求數可輕鬆超過100個,其中介面類的請求有30個。
中間層目標
減少請求數量,以及縮小資料包體積,可以有效提升使用者體驗。
想要減少請求,可以建設BFF層。
通過建設BFF層,提升使用者體驗:
- 提升載入效能,更快地呈現核心內容。
- 提升互動的流暢度,更快地響應使用者操作。
驗收目標:聚合後,頁面效能,TTI提升10%
中間層在架構中的位置:(server層—web server層)
我在此次迭代中做的工作:
- node,koa技術調研
- 用koa框架搭建web server中間層
- 系統需要聚合業務和介面調研,以及文件整理
- 出具所有聚合介面方案
- 分配到人進行介面聚合
- 配合後端和運維,域名轉發處理
- 效能監控,對比,與測試配合驗收
聚合介面注意事項:
介面聚合在開發時要注意的原則:
a. 儘量聚合同一檢視層級的請求
b. 在邏輯有相關性,引數相同的介面,比較適合做聚合
c. 不要把過慢的介面和其他正常介面放在一起,會影響整體效能
最終效果,通過我們的努力,BFF層聚合介面以後,請求時間和介面數降低,達到了預期的目標。
原理是,node採用事件驅動、非同步程式設計,為網路服務而設計。Node.js非阻塞模式的IO處理給Node.js帶來在相對低系統資源耗用下的高效能與出眾的負載能力,非常適合用作依賴其它IO資源的中間層服務。使用者,特別是電腦配置特別低的使用者,通過這波優化,他們體驗會更好(cpu資源降低,請求時長縮小),用服務端資源換使用者的時間和空間,最終提升了使用者體驗。
在此次專案完成以後,進行了前端分享,本次分享的目的:
- 讓大家對node不再陌生,瞭解bff層構成
- 能上手利用node,koa參與一些效能優化,
- 提升個人的技術能力和技術視野
nodeJS介紹
- 前言
- 一、概念
- 非阻塞非同步
- 事件驅動
- 能做什麼?
- 伺服器Node.js和瀏覽器js的區別是什麼?
- 二、優缺點
- 三、應用場景
- 四、延伸
前言
Node.js 是 JavaScript 後端開發語言。從誕生之初就備受關注,到如今說到最火的後端 Web 開發,Node 說自己是第二,沒有人敢說他是第一。
前端有必要進行技術賦能,使用node對業務和團隊進行提升,包括以下幾個方面的提升:
1、 一切為了使用者——提升使用者體驗
2、職責明確——提高開發團隊生產力
3、團隊賦能——提高前端團隊的想象和成長空間
以下內容,是對node介紹。
一、概念
Node.js 是一個開源與跨平臺的 JavaScript 執行時環境。
在瀏覽器外執行 V8 JavaScript 引擎(Google Chrome 的核心),利用事件驅動、非阻塞和非同步輸入輸出模型等技術提高效能。
可以理解為 Node.js 就是一個伺服器端的、非阻塞式I/O的、事件驅動的JavaScript執行環境。
非阻塞非同步
Nodejs採用了非阻塞型I/O機制(基本原理結合伺服器演進史,詳細看思維導圖),在做I/O操作的時候不會造成任何的阻塞,當完成之後,以時間的形式通知執行操作。
例如在執行了訪問資料庫的程式碼之後,將立即轉而執行其後面的程式碼,把資料庫返回結果的處理程式碼放在回撥函式中,從而提高了程式的執行效率。
事件驅動
事件驅動就是當進來一個新的請求的時,請求將會被壓入一個事件佇列中,然後通過一個迴圈來檢測佇列中的事件狀態變化,如果檢測到有狀態變化的事件,那麼就執行該事件對應的處理程式碼,一般都是回撥函式。
比如讀取一個檔案,檔案讀取完畢後,就會觸發對應的狀態,然後通過對應的回撥函式來進行處理。
能做什麼?
1、提供資料給瀏覽器展示
2、儲存使用者提交過來的資料
3、資料統計與分析
伺服器Node.js和瀏覽器js的區別是什麼?
1、node.js是平臺,JavaScript是程式語言;
2、javascript是客戶端程式語言,需要瀏覽器的javascript直譯器進行解釋執行;
3、node.js是一個基於Chrome JavaScript執行時建立的平臺,它是對Google V8引擎進行了封裝的執行環境;
4、node.js就是把瀏覽器的直譯器封裝起來作為伺服器執行平臺,用類似javascript的結構語法進行程式設計,在node.js上執行。
二、優缺點
優點:
1、採用事件驅動、非同步程式設計,為網路服務而設計。其實Javascript的匿名函式和閉包特性非常適合事件驅動、非同步程式設計。而且JavaScript也簡單易學,很多前端設計人員可以很快上手做後端設計。
2、Node.js非阻塞模式的IO處理給Node.js帶來在相對低系統資源耗用下的高效能與出眾的負載能力,非常適合用作依賴其它IO資源的中間層服務。
3、Node.js輕量高效,可以認為是資料密集型分散式部署環境下的實時應用系統的完美解決方案。Node非常適合如下情況:在響應客戶端之前,您預計可能有很高的流量,但所需的伺服器端邏輯和處理不一定很多。
4、前後端語言互通。
缺點:
因為Nodejs是單執行緒,帶來的缺點有:
-
不適合CPU密集型應用
-
只支援單核CPU,不能充分利用CPU
-
可靠性低,一旦程式碼某個環節崩潰,整個系統都崩潰
三、應用場景
藉助Nodejs的特點和弊端,其應用場景分類如下:
-
善於I/O,不善於計算。因為Nodejs是一個單執行緒,如果計算(同步)太多,則會阻塞這個執行緒
-
大量併發的I/O,應用程式內部並不需要進行非常複雜的處理
-
與 websocket 配合,開發長連線的實時互動應用程式
具體場景可以表現為如下:
-
第一大類:使用者表單收集系統、後臺管理系統、實時互動系統、考試系統、聯網軟體、高併發量的web應用程式
-
第二大類:基於web、canvas等多人聯網遊戲
-
第三大類:基於web的多人實時聊天客戶端、聊天室、圖文直播
-
第五大類:操作資料庫、為前端和移動端提供基於json的API
其實,Nodejs能實現幾乎一切的應用,只考慮適不適合使用它。
以上。
四、延伸
以上內容,僅僅是node.js的一點皮毛,下面推薦部分內容,有興趣的同學可以進行深入瞭解和探究實踐。
KOA介紹
Koa 是由 Express 原班人馬打造的,致力於成為一個更小、更富有表現力、更健壯的Web 框架。使用 koa 編寫 web 應用,通過組合不同的 generator,可以免除重複繁瑣的回撥函式巢狀,並極大地提升錯誤處理的效率。koa 不在核心方法中繫結任何中介軟體,它僅僅提供了一個輕量優雅的函式庫,使得編寫 Web 應用變得得心應手。
Koa是一種簡單好用的 Web 框架,node可以在ssr服務端渲染,bff層,介面聚合,削減api,或處理api資料等方面應用,減小前端程式碼複雜度,為企業節省成本,讓吞吐率更高。
二、KOA程式碼結構
koa 非常小巧,總共就 4 個檔案,每個檔案的功能也十分單一,檔名也清楚的反應了檔案功能。
├── application.js
├── context.js
├── request.js
└── response.js
複製程式碼
-
request.js
主要針對 http 的 request 物件提供了改物件的大量的 get 方法,檔案主要是用來獲取request 物件屬性。
-
response.js
主要針對 http 的 response 物件提供了該物件的大量 set 方法;該檔案主要是用來設定response 物件屬性。
-
context.js
koa 引入了上下文物件的概念,即 ctx,這裡所謂的上下文物件實際上是 request 和 response 兩個物件的並集,request 和 response 分別通過代理的形式,將自己的方法委託給 ctx。那樣我們就可以用 ctx 同時操作兩個物件,來簡化操作。
-
application.js
該檔案是整個 koa 的核心,簡單來說主要有兩大功能:掛載真實請求到 ctx 下,封裝中介軟體的執行順序
三、KOA和express的區別
於是二者的使用區別通過表格展示如下:
koa(Router = require('koa-router')) | express(假設不使用app.get之類的方法) | |
---|---|---|
koa(Router = require('koa-router')) | express(假設不使用app.get之類的方法) | |
初始化 | const app = new koa() | const app = express() |
例項化路由 | const router = Router() | const router = express.Router() |
app級別的中介軟體 | app.use | app.use |
路由級別的中介軟體 | router.get | router.get |
路由中介軟體掛載 | app.use(router.routes()) | app.use('/', router) |
監聽埠 | app.listen(3000) | app.listen(3000) |
上表展示了二者的使用區別,從初始化就看出koa語法都是用的新標準。在掛載路由中介軟體上也有一定的差異性,這是因為二者內部實現機制的不同。其他都是大同小異的了。
與 express,hapi,eggjs 比起來,koa 真的十分小巧,以至於不能稱作一種框架,可以看做一種庫,但這並不妨礙 koa 生態的發展。
express 當初也是大而全的框架,慢慢的把各種功能已中介軟體的形式抽離出來,koa 可以看做這種思想的一種實現。大而全的框架主要存在起初的學習成本高,功能冗餘等問題,使用 koa 對於初次使用 nodejs 開發 web 的人員非常友好,對於初學者來說,建議從 koa 入手,使用不同的中介軟體來實現不同的功能,對於瞭解 web 開發有很大幫助。
四、中介軟體
1、什麼是中介軟體?
中介軟體就是匹配路由(匹配任何路由或者特定的路由,其作用比如列印日誌,檢視許可權)之前或者匹配路由完成之後所得一系列操作,功能有:1.執行任何程式碼
2.修改請求和和響應物件
3.終結請求-響應迴圈
4.呼叫堆疊中的下一個中介軟體
通過next來實現
在express 中介軟體(Middleware) 是一個函式,它可以訪問請求物件(request object(req)),響應物件(response object()res)和web應用中處理請求-相應迴圈流程中的中介軟體,一般被命名為next的變數。在Koa中中介軟體和express有點類似。
經典的洋蔥圖概念能很好的解釋next的執行,請求從最外層進去,又從最裡層出來。
中介軟體的功能包括:
-
執行任何程式碼
-
修改請求和響應請求物件
-
終結請求-響應迴圈
-
呼叫堆疊中的下一個中介軟體
如果get、post回撥函式中,沒有next引數,那麼就匹配上第一個路由,就不會往下匹配了。如果想往下匹配的話,那麼就需要寫next()。
app.use('/',function(){});
Koa應用可以使用如下幾種中介軟體:
-
應用級中介軟體
-
路由級中介軟體
-
錯誤處理中介軟體
-
第三方中介軟體
可以寫兩個引數,第一個是匹配的路徑,第二個是回撥函式,第一個引數可以省略
五、實踐
1、安裝
檢查node版本
$ node -v v14.15.1
Koa 必須使用 7.6 以上的版本。如果你的版本低於這個要求,就要先升級 Node。
你可以使用自己喜歡的版本管理器快速安裝支援的 node 版本:
$ nvm install 7
$ npm i koa
$ node my-koa-app.js
2、架設HTTP服務
const Koa = require('koa'); const app = new Koa(); // 本地服務 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
顯示Not Found,因為我們沒有給內容,所以顯示這個。
3、Context 物件
Koa 提供一個 Context 物件,表示一次對話的上下文(包括 HTTP 請求和 HTTP 回覆)。
demo如下:
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.body = 'Hello 星火組'; }; app.use(main); // 本地服務 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
HTTP Response型別:
const Koa = require('koa'); const app = new Koa(); const fs = require('fs'); // const main = ctx => { // ctx.body = 'Hello 星火組'; // }; //HTTP Response 的型別 // const main = ctx => { // if (ctx.request.accepts('xml')) { // ctx.response.type = 'xml'; // ctx.response.body = '<data>Hello World</data>'; // } else if (ctx.request.accepts('json')) { // ctx.response.type = 'json'; // ctx.response.body = { data: 'Hello World' }; // } else if (ctx.request.accepts('html')) { // ctx.response.type = 'html'; // ctx.response.body = '<p>Hello World</p>'; // } else { // ctx.response.type = 'text'; // ctx.response.body = 'Hello World'; // } // }; const main = ctx => { ctx.response.type = 'html'; ctx.response.body = fs.createReadStream('template.html'); }; app.use(main); // 本地服務 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
node 專案實操檢視ctx返回
https://koajs.com/#context。官方文件介紹。
4、router 路由
上面程式碼中,根路徑/
的處理函式是main
,/about
路徑的處理函式是about
。
const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const about = ctx => { ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>'; }; const main = ctx => { ctx.response.body = 'Hello World'; }; app.use(route.get('/', main)); app.use(route.get('/about', about)); app.listen(3000, ()=>{ console.log('http://localhost:3000')
})
5、中介軟體
// 引入Koa模組 const Koa = require('koa'); // 引入Koa-router const Router = require('koa-router'); // 例項化Koa模組 const app = new Koa(); // 例項化路由模組 const router = new Router(); // Koa 中介軟體 // app.use('/',function(){}); //可以寫兩個引數,第一個是匹配的路徑,第二個是回撥函式,第一個引數可以省略 // 匹配任何路由之前列印日期 app.use(async (ctx,next)=>{ console.log(new Date()); await next(); //當前路由匹配完成以後繼續向下匹配 }); // 配置路由 // ctx 上下文 context, 包含了request和response等資訊 router.get('/',async (ctx)=>{ ctx.body = '網站首頁'; //返回資料 相當於:原生裡面的res.writeHead() res.end() }); // 路由級中介軟體 // 匹配帶news路由以後繼續向下匹配路由 router.get('/news',async (ctx,next)=>{ console.log('這是一個新聞路由'); await next(); }); router.get('/news',async (ctx)=>{ ctx.body = '新聞列表頁面'; }); router.get('/login',async (ctx)=>{ ctx.body = '網站登入頁面'; }); // 啟動路由 app .use(router.routes()) /*啟動路由*/ .use(router.allowedMethods()); //作用:當請求出錯時處理邏輯 /* * router.allowedMethods()作用: 這是官方文件的推薦用法,我們可以 * 看到 router.allowedMethods()用在了路由匹配 router.routes()之後,所以在當所有 * 路由中介軟體最後呼叫.此時根據 ctx.status 設定 response 響應頭 * */ // 監聽3000埠 app.listen(3000,()=>{ console.log('starting at port 3000'); });
6、獲取請求資料
const Koa = require('koa') const app = new Koa() const Router = require('koa-router') const router = new Router() router.get('/data', async (ctx , next)=> { let url = ctx.url // 從ctx的request中拿到我們想要的資料 let data = ctx.request.query let dataQueryString = ctx.request.querystring ctx.body = { url, data, dataQueryString } }) app.use(router.routes()) app.listen(3333, ()=>{ console.log('server is running at http://localhost:3333') })
在瀏覽器裡輸入http://localhost:3333/data?user=wuyanzu&id=123456,可以看到執行結果:
可以看到區別,.query
返回的結果是物件,而.querystring
返回的是字串,這個很好理解。(chrome外掛顯示成json格式)
如果遵從 RESTful 規範,比如請求要以 '/user/:id'的方式發出的話,我們可以用下面的例子來獲取到想要的資料。
router.get('/data/:id', async (ctx, next) => { // 也從ctx中拿到我們想要的資料,不過使用的是params物件 let data = ctx.params ctx.body = data })
以上,我們通過上面的程式碼和描述,已經對koa及node有一個初步的印象和概念。
課後作業:
用koa框架實現一個web頁面。
進階:本地json儲存,實現增刪改查。
參考:
【完】
資訊創造價值, 知識就是力量。