去哪兒網前後端分離實踐

作者|興百放

編輯|覃雲

來源 | 前端之巔

本文是對去哪兒網前端業務方向負責人興百放在今年 GMTC 大會上的演講整理。

前後端分離方案

去哪兒網主要有三種前後端的分離方案。

去哪兒網前後端分離實踐

第一種是項目分離,承載頁面分離。他的特點是簡單,快速,前端只關注瀏覽器方面,除瀏覽器端之外都是後端負責。當然缺點是溝通成本高,前期,前端需要使用 ng 或者代理工具調試,後期,還要把頁面給到後端,並且新建一個對應的路由。這樣來來回回,調試非常的複雜,一旦前後端同學涉及到跨部門,跨樓層合作,這些成本又會相應的增加。

去哪兒網前後端分離實踐

第二種方式還是項目分離,只是後端的頁面,放到了前端項目裏,後端只需要配置路由,最終上線時,由發佈系統負責把前端中的頁面,自動同步到後端相應的目錄中。其中相應的目錄需要前後端提前約定,不然後端在渲染頁面的時候,就會找不到相應的文件。相比第一種方案,稍微有點進步。溝通成本會有一定的降低。不過如果需要在頁面裏做一些業務邏輯處理,還需要前端同學掌握和學習 velocity 語法,對於新同學而言看似掌握的了一門新語法,但實際操作起來並非想象中的流暢。另外考慮使用 React SSR 做頁面同構直出,這個方案還有一定的難度。

去哪兒網前後端分離實踐

第三種方案是使用 Nodejs 作爲頁面渲染層,後端只負責數據的生產工作。這也是目前階段主要的使用方式。它的優點是前端同學對於整個頁面的生命週期有完全的控制權,包括開發,調試,部署,上線以及後期的性能監控,應用監控等等。可做的事情也更多,比如使用 React SSR 做同構渲染。當然,使用此種方案,對於前端同學的要求也會很高,除學習前端知識外,還要學習後端知識。另外由於整個應用都是由前端統一負責,所以還需要接收報警電話或者短信,7*24 小時,都在待命狀態。

靜態資源離線包方案(qp)

在三種方案演變的過程中,爲了讓用戶快速的看到頁面,我們還設計了一個靜態資源包的方案,這是它的整體的流程圖:

去哪兒網前後端分離實踐

如果某項目想使用離線包,只需要簡單的兩步。

第一步,在項目的根目錄中,新建 index.yaml 配置文件。主要配置唯一的 ID,面向的 iOS 和 Android 版本,打包的內容,忽略的內容等;

第二步,進入打包平臺,選擇相應的項目,即可通過自動化工具生成 qp 文件,並且自動上傳到 qp 存放服務器中,其中會涉及到壓縮,加密,打包等一系列操作,無需人工幹預;

當用戶進入到客戶端,如果網絡環境是 wifi,會自動拉取所有的離線包,非 wifi 網絡,會選擇性的下載相應的離線包。在進入相應的頁面之前,會檢查本地是否有對應的離線包,如果沒有,會自動下載,走線上環境,反之,直接使用離線包中的資源。

用戶對離線包是完全無感知和透明的。

大家從整個流程上看相關的一些功能,可能覺得很簡單,不復雜,但實際上考慮的事情非常多:

1. 如何保證資源的安全性,不被中間人惡意篡改?

主要體現在 “傳輸安全”和“存儲安全”上。這裏我們採用的 RSA 加密方式,在打包平臺,使用私鑰對 qp 文件求 MD5,在客戶端使用公鑰對 qp 文件求 MD5 ,並和服務端所返回的 MD5 值進行對比校驗,若相等,則校驗通過。

2. 如何快速的回滾?

起初,採用的是假回滾的機制,簡單來說,一旦離線包有 BUG ,在重新發一版。這種流程看起來或聽起來沒有什麼問題,但實際操作起來,成本很高。因爲按照重發的思路,會重新從線上拉取代碼,如果這時線上代碼變了,打出的包內容也會變。

3. 如何下線和強制更新

下線:當某次發版的 qp 包有 BUG 時,可以進行下線操作。針對的是當前指定版本 qp 包。

強制更新:當某個 qp 包希望用戶下載到時,可以是用此操作,針對的是將要下載的 qp 包。

4. 如何提高更新率

不論架構多麼簡單或多麼複雜,更新率問題是最能體現出框架的好與壞。上面提到,有強制更新和普通更新,由於兩者的更新機制不同,最終的效果也不同。

最後,關於更新率的效果:

去哪兒網前後端分離實踐

去哪兒網前後端分離實踐

強制更新和普通更新這兩個機制實現的方式不一樣,所以它的更新效果也不一樣,強制更新的效果最明顯,它能在兩個小時之內達到一個 90% 的水平,普通更新得七八個小時之後才能穩定到 75% 左右。

Node.js 實踐

爲什麼 Node 沒有大規模使用呢?我總結了大概的原因:

  • 一些前端開發,只關注瀏覽器端,服務器端開發關注很少,或者根本就不關注 ;
  • 認爲 Node.js 只適合開發一些工具類的功能,對於後端開發是個玩具 ;
  • Node.js 的生態不如其他後端語言生態健全 ;
  • 涉及到後端開發的知識面比較廣,在沒有這些基礎知識或者經驗積累的基礎上,考慮問題比較片面,最終做出的系統問題比較多,容易被後端鄙視 ;
  • 對於 Node.js 開發後端,對項目負責人要求比較高(項目的目錄規範,開發規範,系統的安全性,穩定性,可靠性,擴展性,維護成本等);
  • 以往前端不需要 7 x 24 保持待命狀態,但是接觸後端後,需要接收報警短信,有時出現問題還需要馬上隨時隨地解決 ;

看似問題很多,但實質上只有兩個原因,一方面,自身知識儲備不夠。第二方面,對 Node.js 瞭解不深,不敢應用在生成環境中,即使應用到生產環境,一旦出現問題,不能快速及時的處理,導致高層認爲還不如其他後端語言穩定,降低了我們的話語權。

Node.js 到底能解決我們哪些的問題和痛點呢?

首先,提高開發效率,因爲有了 Node 之後就不需要配置 Nginx 了,也不需要配置一些代理工具了,所有的頁面生命週期都是由前端統一去管理的,這時候不需要其他人進行合作。

第二,降低溝通成本,除了接口格式外,不需要和後端進行交互了;

第三,前後端職責也更爲清晰,因爲這時候,界限更爲清晰了,後端只負責生產數據,它只提供數據就可以了,至於數據怎麼消費,以及怎麼用,都由前端去做;

第四,可以同時使用 React SSR 技術,做到首屏渲染,提高用戶體驗,除了首屏之外,還可以做異步的加載、SEO 等操作。

最後,Node.js 可提供一些服務,不僅能讓我們使用,還可以對外使用,如 RESTful API,這樣就不用有求於後端了。

三年前,公司內部就搞了一套基於 Express 的 Node.js 解決方案,包含日誌收集,監控,多進程,異常,模板等插件,方案本身也很全面,但在實際項目使用過程中,或多或少的有些不便,主要體現:

  • 如何確定項⽬目⽬目錄劃分的規範,命名規範 (view or views);
  • 確定規範後,如何保證⼤大家都認可,並且嚴格遵守;
  • 如何保證系統的安全性、穩定性和擴展性,怎麼保證和我們內部系統做很無縫的去對接,這就要求有很好的擴展性;
  • 守護進程程序的選擇 (pm2 or supervisor);
  • 怎麼保證多環境運⾏行行規則 (local / beta / prod),因爲在我們實際項目中,可能對我們的 Local 或者對 Bata 或者對 PID 都有不同的規則,如果這時候沒有去做這件事,就有可能對我們的實際應用有可能造成一定的障礙;
  • 如何利利⽤用系統 cpu 多核,以及多進程之間的通信。

針對這些問題,內部也進行了一些改進,但有些功能還是有些不盡人意。

在 17 年 4 月份,團隊內部又重新開始 Review 和調研。發現國內有兩個框架做的比較好,一個是 360 團隊的 Thinkjs ,另一個是阿里的 Eggjs ,兩個框架實現目的也是一致,只是使用的方式有些差別。

團隊內部針對這兩款框架,分別做了不同嘗試,最終從框架擴展的易用性,插件數量,以及部署等方面,選擇使用的是 Eggjs 作爲團隊內部的框架,以替代之前的框架。

插件開發

爲了對接我們的內部系統,我們還開發了不同功能的一些插件。

  • egg-qversion,作用是關聯前後端靜態資源版本號
  • egg-qconfig,對接公司內部的 qconfig 系統
  • egg-qwatcher,對接公司內容的 watercher 系統
  • egg-accesslog,產生 access.log 日誌
  • egg-swift,對接 swift 系統
  • egg-healthcheck,系統健康檢查
  • egg-checkurl,應用存活檢查

去哪兒網原來的部署流程(service 方式) 問題

  • 不能利⽤發佈系統中相應的端口和⽬錄字段,只能在 qunar_xx 服務中寫死, 不友好
  • 不能區分多環境策略如 beta 環境和 prod 環境配置規則不一樣
  • 啓動過程中出現錯誤,不方便定位問題,需要到機器上排查
  • 寫系統服務需要了解 shell 命令和系統服務格式,對於前端開發同學,成本稍高
  • 除了端口、項目路徑、運行環境,node.js 啓動方式外,處理邏輯相似

改進過的部署方式

  • 在項目中建立 deploy_scripts 目錄,新增 start.sh (名稱可以隨便命名)
  • 在 start.sh 中填⼊Node.js 啓動邏輯,比如 node index.js (之前是 N 行,如今最多兩⾏)
  • 在發佈系統選擇 node 發佈方式,填⼊端⼝號,發佈路徑,以及啓動腳本名稱(start.sh),停止腳本填入發佈系統內置的 stop.sh(按照端口殺掉進程)

這是 start.sh 的一個樣例:

去哪兒網前後端分離實踐

React SSR 實踐:

這是大致的結構:

去哪兒網前後端分離實踐

去哪兒網前後端分離實踐

這裏我們沒有使用高大上的技術,只是簡單使用了 Redux ,原因有兩個。一方面,學習成本底,不管對於新同學還是老同學,都能快速上手。第二方面,即使不使用 SSR ,前端代碼照常能運行。

去哪兒網前後端分離實踐

這是 reactRender 的寫法。這裏額外附加了一個嗅探功能,以便前端能提前獲取設備信息。

去哪兒網前後端分離實踐

再看視圖的寫法。

這裏把狀態數據,掛在到了 window 全局變量上了,當然這也是一個缺點吧。

React SSR 遇到的問題

共享代碼如何處理請求

去哪兒網前後端分離實踐

因爲前後端共同使用一個 action,後端 dispatch 的時候,需要同步的自身調用自身,所以在請求時,需要配置一個完整的請求 URL 。同樣是自身調用自身,本身是沒有 cookie 等信息的,所以還需要透傳這些信息,方便後端使用。比如判斷登陸等

共享代碼如何處理錯誤

去哪兒網前後端分離實踐

同樣,同一個 action 可能被後端調用,也可能被前端調用,如果不處理異常的話,對於定位和處理問題也是非常棘手。我們的做法是,最後一個參數,傳遞後端的 context ,在處理異常時,區分環境,有針對性的處理。

後端代碼獲得設備信息

去哪兒網前後端分離實踐

有時,在後端渲染的時候,需要明確知道一些環境信息,比如是否在 APP 內,是否是是 IphoneX 等等,以便在初始渲染的時候,設置額外的信息。所以這裏使用的是高階組件,把這些檢測信息統一注入到組件中。這樣開發同學就不用在每個頁面重複寫這些信息了。

性能監控

對於性能方面,我們做得不是太多,因爲 eggjs 本身已經經歷過淘寶雙十一的洗禮, 相信在阿⾥內部對這塊已經做了不少優化,所以簡單使用的是公司級別的機器監控。

去哪兒網前後端分離實踐

應用監控,分兩個方向。

  • 第一個方向,是應用程序級別,比如應用程序錯誤數,請求後端接口時間消耗和異常信息,accesslog 日誌等等。
  • 第二個方面,是前端頁面級別,比如腳本全局錯誤,靜態資源文件加載的錯誤,異步接口錯誤,頁面渲染時長等等

針對這兩方面的錯誤,我們有兩套系統,一個是日誌系統,一個是 Watcher 系統,這兩個系統是搭配合作的。

Watcher 系統,它主要的功能是打數,計數,圖形化展示,以及設置報警等功能。實時主動的提醒我們系統運行情況,能夠在第一時間發現問題,使故障影響範圍降到到最小。

去哪兒網前後端分離實踐

大家可以看出,雖然在特定的時間點報出問題,但只限數量上的程度,具體什麼問題,Watcher 系統就不行了,還需要藉助第二套「日誌系統」。通過 kibana 可以實時查看所有錯誤信息。

去哪兒網前後端分離實踐

相關文章