B站的前端之路

2017年即將過去了,總結一下B站的前端進階之路css

過去的開發模式中,咱們採用了之後端爲主的 MVC 架構方式。具體來講,每次項目評審後,先後端會先一塊兒約定好接口,以後分別進行開發,開發完,前端須要把頁面提供給後端,後端配置上數據,而後返回出來。正式基於這樣的開發模式,致使了總工做量的增長,同時溝通和聯調成本的消耗也十分顯著。html

先後端分離

爲了擺脫這種先後端過度依賴的狀況,(其實前端也不想每次修改或者發佈都要後端這邊發佈,後端也不想每次前端只改個標題,都要發佈一下,影響服務的穩定性),那麼先從先後端分離開始吧~前端

先後端分離,最基本的兩種模式,有中間層和沒有中間層。vue

第一種,沒有web中間層就很簡單,提供一個html模板放到靜態資源機上面,html模板裏面引用了所需的js和css ,訪問頁面的時候 把這個靜態模板返回給用戶,而後執行js 在瀏覽器端經過ajax請求api拿到數據,渲染頁面。node

(先後端分離)

第二種,有node中間層,隨着2009年,Node的橫空出世,把前端慢慢的推向了後端,有了node以後,JavaScript能夠作更多的事情。react

B站,一開始作先後端分離的時候,也確實按照第一種方式去作的,如今還有一些頁面仍然是這種模式,例如:www.bilibili.com/account/his… (可查看網頁源代碼)。對於不須要seo的頁面來講,是一個不錯的方式。前端開發完成以後,經過webpack打包出對應的js和css 上傳到cdn上面,而後將webpack打包出來的 引用了對應的資源的html文件 上傳到一臺專門的靜態機上面,而後運維配置路由 將頁面流量導過去就行了。後端的同窗只須要提供對應的api接口就能夠。先後端分開維護,本身按照本身的節奏走,下降了頁面與服務的耦合度webpack

這種方式確實是一種很快可以進行先後端分離的方法。咱們花了一段時間,在pc端使用vue 進行重構,移動端H5端 用react 進行了重構。 進度很快,可是也慢慢展示出了弊端。git

首屏的時候,由於他要等待資源加載完成,而後再進行渲染,會致使了首屏有白屏,若是是單個頁面還好,若是是spa應用 那麼 他的加載時間就會變得很長,白屏時間會很影響用戶體驗,再有就是因爲國內的搜索公司 對於spa 應用沒有很好的兼容,致使了客戶端渲染會對seo很是的不友好,有seo 需求的頁面就很迫切的須要服務端渲染。web

(B站的首頁,右邊模塊作了服務端渲染,左邊模塊沒有作服務端渲染)

那麼,依賴node 進行服務端渲染就被提上了日程。ajax

選型

首先進行node 框架的選型,市面上主流框架有三種,hapi express koa ,還有一些是通過一些封裝和定製的框架,例如 eggjs等

一開始我就把eggjs 排除在外了,第一 由於eggjs,的功能很強大,有不少功能,多到有些根本用不着,從而致使了他會重 不輕量級,第二,eggjs對於我來講是個黑盒,若是有什麼問題,我解決起來將會花費很長的時間。(可是有不少地方 我仍是借鑑了eggjs的,畢竟 很強大)

而後剩下的三種框架,express的使用相對簡單,文檔也比較多 比較全面,因此我就選擇了express(後來仍是重構掉了 = =!)

而後是前端框架的選型 由於前端框架主流的有不少,ng r v 等等,我站在用的是react和vue, 他們有個優點就是能夠進行先後端同構,同樣的邏輯不用寫兩份,很棒

(同構邏輯大概如此吧)

因爲以前先後端分離的時候,pc 上面已經再用vue 進行了重構,因此天然,此次服務端渲染也創建在vue上面 用的是vue ssr (這也爲我後面的一個想法埋下了伏筆)

首先 咱們選擇一個簡單的頁面來作打樣,就用tag頁吧(被神選中的孩子:www.bilibili.com/tag/3503159

開發

目錄結構

  • client 【客戶端代碼 同構代碼】

  • build 【構建相關】

  • PC 【pc 端 vue項目】

  • package.json

  • config

  • config.local.js 【本地開發配置】

  • dist 【構建目錄 掛載資源目錄】

  • server 【服務端代碼】

  • controller 【控制器】

  • PC

  • route.js

  • core [核心代碼庫]

  • service [方法庫]

  • view [視圖]

  • PC [vue 構建後文件]

  • tag.html [構建後的模板]

  • tag.json [構建後的bundle]

  • manifest.json

  • apps.js [啓動項]

在一開始設計的時候,客戶端代碼和服務端代碼放在同一個git庫裏面,client裏面是vue的代碼和webpack的打包邏輯。Server裏是服務端的代碼,用的是類mvc結構。

Client裏面的vue的開發代碼,參照的就是vue ssr 官方給的例子來作的,用的是 createBundleRender方法

const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer(serverBundle, {
... })
複製代碼

構建配置也是用的推薦的配置(參考:ssr.vuejs.org/zh/build-co…

簡單來講,就是提供兩個入口,一個entry-client.js,主要是客戶端的執行入口, 打包出來的是客戶端的引用代碼集合(manifest),另一個是entry-server.js 打包出來的是服務端運行的邏輯,整合到了bundle.json裏面。而後傳給上面的createBundleRender方法就能夠了

對於server文件夾裏面的邏輯就很是簡單了,core裏面是啓動項目的一些express的核心代碼 路由註冊什麼的邏輯,值得一說的是,這邊的路由,借鑑了eggjs的路由註冊方式,稍微作了一點修改,用的是配置化的方式

配置優於代碼,將訪問地址和對應的controller 作了關聯

這邊還有一個filter 其實就是在執行controller以前 註冊進一個middlewares 優先執行(其實這邊有點侷限性,後處理無法作)

這邊我忽略了壓力測試,壓力測試我後面再說把

上線部署

上線部署用的是docker 來部署的,配置是1C 4G的配置,用了兩個實例來運行,(以前的構建鏡像邏輯什麼的 就不具體介紹了)

上線以後 天天的訪問量大概在100W左右,服務表現挺穩定,期間出現了一個bug,就是 這邊有一個狀態與用戶的登錄狀態有關,因此在服務端請求接口的時候,須要帶上cookie去請求,當時忘記加了 後來加上,發現這個有點弊端 比較麻煩

須要在調用vuesssr的時候帶在context 裏面,而後asyncData方法裏面都要一層一層的傳遞,最後在action 裏面拿到,帶給api

這時候 咱們再來看下tag 頁

(不錯 把數據都帶上了)

重構

其實也沒過多久,大概三個月吧,node的版本漲的很快,在7.6版本以後,node 就支持了async/await 語法糖,不須要再用yield 和*函數了,那麼 無疑 koa 是對於await/async 支持最好的,咱們果斷放棄了express,選擇了koa2 進行重構

其實不僅僅是koa2對於async的支持,另一個緣由在於,咱們koa 是洋蔥式的執行方式,這樣就解決了上面我說的,只有controller的前處理,沒有後處理,這樣子我就能夠很方便的去執行先後處理。Koa的執行效率也要好於express.

上面我說過,選擇vue 對後面重構埋下了一個伏筆就在這裏

首先,我給項目接入了配置中心,配置中心是幹嗎用的呢? 用來記錄腳本的版本號,這樣子我就能夠很輕鬆的經過配置中心來控制前端頁面使用什麼版本的腳本。而不用由於改了個腳本的版本號,就須要進行一次服務的重啓更新。

而後,我對vue的打包組件進行了魔改,將他打包出來的文件 帶上了對應的版本號(版本號爲hash值)

這樣子我就能夠經過配置中心來控制,到底我須要使用什麼版本的vue 構建產物,vue 前端邏輯更新了,我也只須要經過配置中心去分發給服務端,而不須要重啓服務了。一箭雙鵰。

圖中 conf 就是配置中心,咱們的server 會與conf進行一個長鏈接,若是conf中的配置更新了,就會通知到服務,而後服務去拉去新的bundle和manifest 來進行渲染。Ok 很棒

全民SSR

重構完,那麼再接入一個項目試試吧

首頁,好,就首頁吧

首頁跟tag 頁 其實也都差很少,沒有什麼特別的地方,惟一不一樣的就是 量比較大,可能一天有千萬級的訪問量左右。那麼咱們就在CDN上面加上一層緩存,而後在咱們服務上面也加上一層緩存。破費(perfect)!~

服務端的緩存是經過文件落地來的,就是在第一個請求進來的時候 在渲染完成以後,寫一個文件到本地,而後下次訪問的時候就能夠直接用這個丟這個本地文件出去,不用再次渲染了,而後經過過時時間去控制。

這裏發現了一個問題,就是每次更新 我都會將tag 和index 都進行打包,而我須要的是對項目進行單獨的打包,單獨的更新,能不能經過參數來控制我打包哪一個呢,能夠啊,首先先把webpack.config.js 重寫,公用部分整合,而後私有的分開寫成多個,經過package.json裏面來多配置幾個script就好啦

這樣子每次更新項目的時候,我就只須要打包對應的項目就能夠了,不會由於項目接入了不少以後,打包和開發時候的熱加載變得很慢很慢。

因爲接入了兩層緩存,首頁上線的時候,咱們把服務從2個docker實例 擴容到了6個(docker擴容真方便),得益於緩存的優點,服務並無什麼壓力

固然 首頁不可能像說的那樣,這麼隨便就上線了,須要有降級方案,那麼降級方案得益於vue的強大了.

Vue 會在瀏覽器端檢驗(data-server-render=true),是否服務端渲染了,若是服務端沒有渲染,那麼客戶端會再執行一次邏輯進行渲染。這樣子咱們只要再打包的時候,將本來客戶端渲染的那個index.html 保留就能夠拉,固然別忘了,再客戶端執行的時候也要運行一下asyncData裏面的方法,否則會缺乏數據哦。So easy~

接下來 一級分區 二級分區也分別都接入了,中間也遇到了一些問題,不過最後都順利的解決了,後面有機會我再寫一篇文章來講一下其中遇到的問題。

再次重構

咱們的項目在有序的進行着從本來靜態頁 客戶端渲染,往服務端渲染遷移的同時,咱們也在公司內部進行這推廣,有幾個兄弟部門也遇到了咱們以前的seo 的問題,或者是但願首屏更快等,因此很願意使用咱們已經造好的輪子。但是咱們的項目暫時並不具備推廣性,若是兄弟部門要使用,只有把咱們的庫拷貝過去,而後把業務邏輯刪減掉,再加上本身的邏輯,成本很高,並且咱們這邊一旦更新了什麼,他們都須要手動去同步,就很麻煩。

咱們花了一點時間,首先,core 核心庫抽離出來,而且和日誌中心的鏈接方法、配置中心的鏈接方法等一些公用方法一塊兒,作成一個npm包 發佈到公司內部的npm 源上面,而後將client 從庫裏面獨立出來,變成前端庫,加上一個簡單的server.js,能夠獨立於server 進行開發,而不用在開發的時候過度依賴node server.而且得益於配置中心,咱們能夠將項目分的很散,可是最終又經過配置中心,集中到同一個服務上,又回到了先後端分離上面,可是不止於先後端分離,前端獨立開發的同事,還帶上了服務端渲染,一箭雙鵰。設計架構如圖:

順帶,咱們開發了兩個腳手架,能夠很方便的建立項目,而且加好webpack的配置和package.json的配置

這樣子拆分以後,項目就變得很清真,前端開發前端vue項目,服務端有npm包可供你們使用,升級和維護都很方便,node服務也不須要一直去重啓,經過配置便可更新邏輯,熱更新。

作完以後,不少兄弟部門也都開始了接入。

壓力測試

由於每一個公司的狀況都不同,使用組件緩存,頁面緩存等等方式,均可以達到優化的目的,使其能夠達到能承載項目流量的標準,我這邊說的狀況是沒有任何緩存的狀況下的壓測結果。

咱們作過幾回不一樣層面的壓測,畢竟性能須要達到要求才行,記得當時出版打樣上線的時候,VUE使用的版本是2.3.x 性能不是很好,由於VUE是基於虛擬DOM(VNODE)來實現的,是CPU密集型的項目,因此在壓測的時候,CPU很快就達到了100%,TPS很低,因此咱們對頁面加了緩存,像首頁這種P0級頁面都加兩層緩存,後來VUE更新到了2.4.x 性能變好了許多,可是CPU始終是一個瓶頸。若是項目複雜,組建嵌套不少的話,1C4G的服務器,CPU打滿也就40到50的TPS就封頂了,再上去,用戶等待時間就會呈指數式上升。

我看過不少文章,拿vuessr和字符串模板進行比較的文檔,可是他們的比較demo都很簡單,vue裏面都沒有組件嵌套,性能相比可能確實差很少,可是頁面複雜度上升,組件嵌套越多,那麼vuessr的性能就無法再跟字符串模板進行比較了

舉個例子把,咱們首頁一二級分區天天打到node上面的量跟文章的量差很少,可是文章就用了首頁三分之一的機器,機器的cpu和內存使用量差很少,由於文章項目用的是字符串模板。

總結

在整個的過程當中,須要前端同窗,後端同窗的通力配合才行,後端api的同窗須要將本來直接結合模板出數據的方法所有改爲api接口,這是先後端分離的基礎。至於基礎建設,能夠慢慢發展來完善,就像一開始咱們構建的時候,構建出來的配置文件的版本號都是須要手動去配置到配置中心的,這很耗時,並且容易出錯,慢慢的,配置中心開放出了api接口,咱們接入就很方便了,順利的實現了配置同步的自動化,只要上線的時候點一下發布就行了。

在用node作中間層的過程當中,也有遇到內存泄漏,性能瓶頸等問題,後面有機會,再寫篇文章介紹吧。在這一年中,B站發展的很快,前端也有意識的去在乎前端性能,讓頁面更好,更快。

腳步從未停下,咱們還在路上!

嗶哩嗶哩 (゜-゜)つロ 乾杯~

相關文章
相關標籤/搜索