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