為什么我們的業(yè)務(wù)適合用 Node?
這些日子一直在做 Node 方面的嘗試,或多或少會收到周圍的異樣的目光甚至背后的質(zhì)疑,于是促使我好好思考為什么我在做 Node。網(wǎng)上搜下「為什么要用 Node」,找到的文章多數(shù)是介紹 Node 多么多么牛逼,無非是從 Node 本身特性來說,比如:并發(fā)、事件驅(qū)動、非阻塞 I/O、單線程、流、社區(qū)生態(tài)…… 諸如此類,很少談業(yè)務(wù)場景。
我是「實用主義」者,說過:脫離業(yè)務(wù)場景談架構(gòu)都是耍流氓。因為個人是從一線業(yè)務(wù)做起的,經(jīng)過幾年對業(yè)務(wù)的思考,我覺得可以從業(yè)務(wù)場景來說說為什么我們的業(yè)務(wù)更適合用 Node。
從業(yè)務(wù)場景說起
現(xiàn)在我們的業(yè)務(wù)模塊化越來越普遍,很少有業(yè)務(wù)比較純粹只有鏈接一個數(shù)據(jù)庫就可以搞定,往往前臺業(yè)務(wù)后面會有 N 多的 API 服務(wù)做支撐。比如:下面兩種情況在我們實際開發(fā)中經(jīng)常遇見:
- 某個頁面需要的數(shù)據(jù)來自兩個以上接口,而兩個接口來自不同的團(tuán)隊 / 部門,比如:用戶信息來自賬號部門,而 UGC 數(shù)據(jù)來自業(yè)務(wù)部門
- 某個頁面存在接口依賴,需要先調(diào)用接口 A,然后根據(jù)接口 A 數(shù)據(jù)調(diào)取接口 B,比如:個性化推薦,往往需要根據(jù)某些維度請求推薦系統(tǒng)拿到推薦數(shù)據(jù)的 ID,至于內(nèi)容,需要拿 ID 根據(jù)頁面需要去獲取具體元數(shù)據(jù)
上面兩種情況,站在后臺開發(fā)的角度來看,我們業(yè)務(wù)模塊要分開要獨立,而站在前端的角度來看,這些數(shù)據(jù)都是一個頁面需要的,前端希望是一個接口給我返回。這是一個開始。。
當(dāng)然后臺開發(fā),比如 PHP 也有并發(fā)請求的解決方案,好(上)心的后臺工程師,會幫助在后臺統(tǒng)一合并請求處理成一份數(shù)據(jù)或者接口,然后扔給頁面使用。比如在實際開發(fā)中,我們的前端會寫(并且維護(hù))一個 Template.class.php(我敢說我們 80% 的后臺工程師都沒看過這個代碼。。),在 View 層使用,然后在 Action 當(dāng)中將數(shù)據(jù)傳給 View 層做渲染,下面的代碼:
- $this->render('xxx/xx.tpl', $tplData);
這樣增加的溝通成本,降低了開發(fā)效率。為了一個頁面,需要前端根據(jù)頁面想要的數(shù)據(jù),和后臺溝通頁面的數(shù)據(jù)格式,然后后臺工程師找他們后面的 API 模塊要數(shù)據(jù)、處理數(shù)據(jù)。這個過程中會有一些「灰色地帶」,不好明確誰做更合適,完全靠自覺。
往往開發(fā)的時候會想各種方法來解耦,比如:引入后臺模板(smarty 之類),然后約定數(shù)據(jù)格式,前端根據(jù)數(shù)據(jù)格式來寫 Mock 接口,寫后臺模板的前端就叫「大前端」;再 Low 一點的團(tuán)隊,會采取前端做好頁面扔給后臺工程師「套頁面」,比如:PHP 代碼寫 HTML,各種 <?php echo xxx;?>,代碼很不友好,后臺工程師幸福感也急劇下降。
還有一種做法是,干脆后臺淪為「代理服務(wù)器」,收到請求我轉(zhuǎn)給后面的 API,拿到數(shù)據(jù)我返回給前端頁面,做成可以「跨域」的接口,所以就成了好多 webapp。
另外,站在后臺工程師的個人發(fā)展來看,可能他們覺得:這些「包接口」的重復(fù)性工作,跟自己的晉升和技術(shù)發(fā)展又有毛線關(guān)系呢?
說道這里,肯定有人心里在嘀咕:這是你們大公司才有的問題,我們小公司不會有這樣的問題!那我下面再從技術(shù)方面來說。
從技術(shù)方面說起
從性能優(yōu)化、工程化、解決方案這三個開發(fā)中最最常見的方面來說明為什么前端的事情前端做更合適。
性能優(yōu)化
前端頁面是重要的載體,出現(xiàn)問題或者頁面體驗不好會對用戶造成直接的傷害(我們都是背鍋俠)。頁面性能這些問題顯然是前端的頭等大事,但是這些事情跟后臺工程師關(guān)系多大呢?當(dāng)你發(fā)現(xiàn)該優(yōu)化的項目都優(yōu)化完了,剩下的優(yōu)化項目就需要跟后臺工程師一起優(yōu)化了,而這時候再去 push 后臺工程師一起參與前端優(yōu)化項目。
工程化方案
除了前端頁面的性能優(yōu)化這種項目,還會有一些工程化的工作,幫助提高前端的開發(fā)效率和體驗,但實際上只有前端是搞不定的,比如:
- 根據(jù)打包工具做的 resourcemap,實現(xiàn)頁面靜態(tài)資源的 CDN 地址合并(combo)輸出和分開輸出(調(diào)試階段)
- 擴(kuò)展 Smarty 語法,實現(xiàn)模板組件化
- 模擬后臺數(shù)據(jù)接口,輸出假數(shù)據(jù)渲染的頁面
這些工程化的工作本身前端自己理解的很清楚,但是后臺工程師會有多少了解呢?怎么不可能讓對前端不了解的后臺工程師參與進(jìn)來呢?解釋要做什么就花費不少時間。
解決方案
再說解決方案,簡單點如果我們要實現(xiàn)頁面 chunked 輸出,將動態(tài)和靜態(tài)數(shù)據(jù)分開,不依賴接口數(shù)據(jù)的數(shù)據(jù)首先展現(xiàn)(比如首屏的 Nav),那么也要依賴后臺工程師的代碼。再大一點,我們上個類似 Bigpipe 的方案,那么對后臺的依賴和改造更大。
技術(shù)這些問題怎么辦?
上面的這些訴求,有兩種方案:前端自己擼袖子來搞,他們或者是直接寫類似 PHP 來實現(xiàn),或者是寫不倫不類的 Smarty 擴(kuò)展代碼;再者就是可以出一個「技術(shù)產(chǎn)品經(jīng)理」,專門立項來搞這些項目,由「技術(shù)產(chǎn)品經(jīng)理」來協(xié)調(diào)兩邊需求,避免「雞同鴨講」??墒菢I(yè)務(wù)部門項目壓力是非常大的,很少有這樣的項目,而專門做技術(shù)的團(tuán)隊呢,又很難深入業(yè)務(wù),往往高高在上,搞出來的東西要么不合實際、要么太高新尖端,導(dǎo)致水土不服,強(qiáng)推起來,業(yè)務(wù)團(tuán)隊哀聲哉道。我也說過:脫離業(yè)務(wù)的架構(gòu)都是耍流氓。。
為什么用 Node
根據(jù)上面說的,大概得出使用 Node 有下面的好處:
- 天然的事件驅(qū)動可以用于處理并發(fā)
- 降低前后端協(xié)作成本,提高開發(fā)效率
- 職責(zé)分明,前端的問題前端 er 自己負(fù)責(zé),自己解決
- 前后端同構(gòu),前端解決方案同步到 Server-side
- 有利于前后端同學(xué)的個人發(fā)展
什么業(yè)務(wù)場景使用 Node
同時,大概得出什么樣子的業(yè)務(wù)場景使用 Node:
- 頁面需求大,從樣式到性能都一直迭代
- 后端接口豐富,頁面數(shù)據(jù)資源方多
- 純渲染,對安全性要求不高,無計算能力
- 最后,最重要的是團(tuán)隊的能力(我還說過:脫離團(tuán)隊的架構(gòu)都是耍流氓。。)
對 Node 的質(zhì)疑
當(dāng)談到「我們要用 Node」,往往會遭到如下具體的質(zhì)疑(注意是具體,不是純懟):
- 穩(wěn)定性:主要是單線程異常處理
- 異步回調(diào)導(dǎo)致程序復(fù)雜度提高,不利于調(diào)試
- 和后臺接口的對接能力
- 工作流程:代碼部署、上線
- 運維支持:接入、容量管理、日志、監(jiān)控等
- Node 版本升級太快了,NPM 包靠譜嗎?
先說這些前面三個問題:
- 穩(wěn)定性:選擇的 Node 框架對異常處理要友好,進(jìn)程守候有 forever/pm2 這些包可以幫我們,目前已經(jīng)成熟,而且 Node 也越來越完善和穩(wěn)定
- 調(diào)試:現(xiàn)在用前端流程的 IDE 很容易調(diào)試
- 后臺接口通信協(xié)議如果過于復(fù)雜,可以通過 C 的 Node 模塊來解決,PHP 不也是嗎?
后面兩個問題主要是跟公司運維能力有關(guān)系:
- 工作流程:目前百度內(nèi)部的工作流程做的很好,從代碼提交到打包編譯和上線都是一整套解決方案,而每個環(huán)節(jié)之間只需要按照約定進(jìn)行輸入輸出即可
- 運維:百度內(nèi)部的 ORP 是很好的 PaaS 解決方案,提供了虛擬化的實例,Node 代碼部署在實例上,通過統(tǒng)一的 nginx 反向代理分發(fā)給不同端口號的實例處理,支持資源彈性調(diào)度,日志只需要按照約定放在某個文件夾,可以配置采集任務(wù),進(jìn)行采集和監(jiān)控
所以我們廠子內(nèi)部已經(jīng)為 Node 大規(guī)模使用在流程和運維方面已經(jīng)做好了準(zhǔn)備。
最后說下 Node 生態(tài)問題,Node 版本的確升級很快,但是只關(guān)注 LTS 版本,等發(fā)版一段時間之后跟進(jìn)更新即可,目前百度內(nèi)部的 Node Runtime 是 6.10 版本,聽說很快就要生 7.0 了。對于框架和業(yè)務(wù)代碼用到的 NPM 包,完全可以做版本指定,也可以做自動測試,跑過了 case 則提交進(jìn) master,隨著下個版本回歸上線。
前端寫后臺代碼會有什么問題?
不管怎樣,我們用 Node 已經(jīng)變得順理成章,但是我們也要知道自己的不足,拓寬自己的視野。
首先是后臺思想,在前端開發(fā)中,我們的代碼是跑在每個用戶自己的瀏覽器里面,代碼之間是隔離的,所以不管你代碼寫的好壞,只要是說的過去,就不會有大的問題,比如:偶爾內(nèi)存泄漏一下,似乎也影響不大。但是 server 的代碼是長久執(zhí)行下去的,不是用戶走了就釋放的,所以一個小的內(nèi)存泄漏,長時間下去也會引起大的問題。再比如:瀏覽器的 JS 你可以偶爾使用個全局變量(少寫個 var),但是如果在 Node 的代碼,將用戶相關(guān)的個性數(shù)據(jù)放在 global,那么當(dāng)下個請求過來,而代碼還沒有處理完當(dāng)前請求,會導(dǎo)致自己用的數(shù)據(jù)不是當(dāng)前用戶的,碰到這種問題,只能把個性化的數(shù)據(jù)一層層的傳下去。還有缺乏優(yōu)化意識或者過度優(yōu)化,該用緩存的時候不用,不該用的時候亂用。
再舉個 case,看下面的代碼:
- module.exports = function (ip) {
- var ipfinder = require('ipfinder');
- ipfinder.loadData('ip.data');
- return ipfinder.findSync(ip);
- }
這個是數(shù)據(jù)實時分析項目的一段類似的代碼,ipfinder 是我寫的一個 IP 查找?guī)欤? ipfinder.loadData('ip.data’);會引入一個二進(jìn)制的 ip 數(shù)據(jù)庫,這個比較消耗資源,寫在 module.exports是沒有必要的,每次執(zhí)行 module 都加載一遍,很費資源,拿到 module.exports之外,程序的 CPU 從 99% 降到了 5%....
第二個是安全意識,之前在「Vue 項目重構(gòu)」中提到 proxy.js的代碼弊端,就是缺乏安全意識導(dǎo)致的。前端寫后臺程序,因為缺乏安全意識,往往在接口設(shè)計、頁面片段拼接等方面犯錯誤,比如:接口設(shè)計的過于簡單,缺乏校驗,容易導(dǎo)致 CSRF 攻擊,如果有數(shù)據(jù)庫操作,手動拼接 SQL 語句容易導(dǎo)致 SQL 注入。
最后是運維知識,前端工程師寫 Node 服務(wù),就不在簡單的對瀏覽器負(fù)責(zé),而還應(yīng)該對服務(wù)器負(fù)責(zé),服務(wù)器的穩(wěn)定性、各種監(jiān)控指標(biāo)都應(yīng)該有所了解,對于機(jī)房配置、資源調(diào)配、運維架構(gòu)、服務(wù)架構(gòu)都應(yīng)該了熟于心,避免出現(xiàn)線上事故了自己還不知道從哪里排查的窘態(tài)。
當(dāng)然你可能會說,剛剛開始接觸是可以允許犯錯的,但是要知道:技術(shù)的調(diào)整是不應(yīng)該損害產(chǎn)品服務(wù)的。以上三點內(nèi)容需要剛剛轉(zhuǎn) Node 開發(fā)的前端工程師注意加強(qiáng)學(xué)習(xí),能力越大責(zé)任越大!開始時候可能會犯錯和抓瞎,該請教就請教,時間長了能夠點亮新的技能點~
【本文為51CTO專欄作者“三水清”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】