日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

嗶哩嗶哩 (B 站) 前端進(jìn)階之路

 萬(wàn)皇之皇 2018-01-10

轉(zhuǎn)載聲明:本文轉(zhuǎn)自【架構(gòu)文摘】
原文鏈接:http://mp.weixin.qq.com/s/1KgFDYKyZm3KTVOsJ2NhGw

2017 年已經(jīng)過(guò)去了,總結(jié)一下 B 站的前端進(jìn)階之路。

過(guò)去的開發(fā)模式中,我們采用了以后端為主的 MVC 架構(gòu)方式。具體來(lái)說(shuō),每次項(xiàng)目評(píng)審后,前后端會(huì)先一起約定好接口,之后分別進(jìn)行開發(fā),開發(fā)完,前端需要把頁(yè)面提供給后端,后端配置上數(shù)據(jù),然后返回出來(lái)。正式基于這樣的開發(fā)模式,導(dǎo)致了總工作量的增加,同時(shí)溝通和聯(lián)調(diào)成本的消耗也十分顯著。

前后端分離

為了擺脫這種前后端過(guò)分依賴的情況,(其實(shí)前端也不想每次修改或者發(fā)布都要后端這邊發(fā)布,后端也不想每次前端只改個(gè)標(biāo)題,都要發(fā)布一下, 影響服務(wù)的穩(wěn)定性),那么先從前后端分離開始吧 ~

前后端分離,最基本的兩種模式,有中間層和沒(méi)有中間層。

第一種,沒(méi)有 web 中間層就很簡(jiǎn)單,提供一個(gè) html 模板放到靜態(tài)資源機(jī)上面,html 模板里面引用了所需的 js 和 css,訪問(wèn)頁(yè)面的時(shí)候 把這個(gè)靜態(tài)模板返回給用戶,然后執(zhí)行 js 在瀏覽器端通過(guò) ajax 請(qǐng)求 api 拿到數(shù)據(jù),渲染頁(yè)面。
c1cd6b21781046ea80fae9e5fe9b238a.png

第二種,有 node 中間層,隨著 2009 年,Node 的橫空出世,把前端慢慢的推向了后端,有了 node 之后,JavaScript 可以做更多的事情。

B 站,一開始做前后端分離的時(shí)候,也確實(shí)按照第一種方式去做的, 現(xiàn)在還有一些頁(yè)面仍然是這種模式, 例如:https://www.bilibili.com/account/history (可查看網(wǎng)頁(yè)源代碼)。對(duì)于不需要 seo 的頁(yè)面來(lái)說(shuō),是一個(gè)不錯(cuò)的方式。前端開發(fā)完成之后,通過(guò) webpack 打包出對(duì)應(yīng)的 js 和 css 上傳到 cdn 上面,然后將 webpack 打包出來(lái)的 引用了對(duì)應(yīng)的資源的 html 文件 上傳到一臺(tái)專門的靜態(tài)機(jī)上面,然后運(yùn)維配置路由 將頁(yè)面流量導(dǎo)過(guò)去就好了。后端的同學(xué)只需要提供對(duì)應(yīng)的 api 接口就可以。前后端分開維護(hù),自己按照自己的節(jié)奏走,降低了頁(yè)面與服務(wù)的耦合度

這種方式確實(shí)是一種很快能夠進(jìn)行前后端分離的方法。我們花了一段時(shí)間,在 pc 端使用 vue 進(jìn)行重構(gòu),移動(dòng)端 H5 端 用 react 進(jìn)行了重構(gòu)。 進(jìn)度很快,但是也慢慢展現(xiàn)出了弊端。

首屏的時(shí)候,因?yàn)樗却Y源加載完成,然后再進(jìn)行渲染,會(huì)導(dǎo)致了首屏有白屏,如果是單頁(yè)面還好,如果是 spa 應(yīng)用 那么 他的加載時(shí)間就會(huì)變得很長(zhǎng),白屏?xí)r間會(huì)很影響用戶體驗(yàn),再有就是由于國(guó)內(nèi)的搜索公司 對(duì)于 spa 應(yīng)用沒(méi)有很好的兼容,導(dǎo)致了客戶端渲染會(huì)對(duì) seo 非常的不友好,有 seo 需求的頁(yè)面就很迫切的需要服務(wù)端渲染。
920cd4eab4754a3e9815213e31395fe6.png

那么,依賴 node 進(jìn)行服務(wù)端渲染就被提上了日程。

選型

首先進(jìn)行 node 框架的選型,市面上主流框架有三種,hapi express koa , 還有一些是經(jīng)過(guò)一些封裝和定制的框架,例如:eggjs 等。

一開始我就把 eggjs 排除在外了,第一因?yàn)?eggjs, 的功能很強(qiáng)大,有很多功能,多到有些根本用不著,從而導(dǎo)致了他會(huì)重 不輕量級(jí),第二,eggjs 對(duì)于我來(lái)說(shuō)是個(gè)黑盒,如果有什么問(wèn)題,我解決起來(lái)將會(huì)花費(fèi)很長(zhǎng)的時(shí)間。(但是有很多地方 我還是借鑒了 eggjs 的,畢竟 很強(qiáng)大)

然后剩下的三種框架,express 的使用相對(duì)簡(jiǎn)單,文檔也比較多,比較全面,所以我就選擇了 express(后來(lái)還是重構(gòu)掉了 = =!)

然后是前端框架的選型 因?yàn)榍岸丝蚣苤髁鞯挠泻芏?,ng r v 等等,我站在用的是 react 和 vue, 他們有個(gè)優(yōu)勢(shì)就是可以進(jìn)行前后端同構(gòu),一樣的邏輯不用寫兩份,很棒。
77693cd39a0d403499efca0ffcd6f317.png

由于之前前后端分離的時(shí)候,pc 上面已經(jīng)再用 vue 進(jìn)行了重構(gòu),所以自然,這次服務(wù)端渲染也建立在 vue 上面 用的是 vue ssr (這也為我后面的一個(gè)想法埋下了伏筆)

首先 我們選擇一個(gè)簡(jiǎn)單的頁(yè)面來(lái)做打樣,就用 tag 頁(yè)吧(被神選中的孩子:https://www.bilibili.com/tag/3503159)

開發(fā)

目錄結(jié)構(gòu)

**
**

  • client 【客戶端代碼 同構(gòu)代碼】

  • build 【構(gòu)建相關(guān)】

  • PC 【pc 端 vue 項(xiàng)目】

  • package.json

  • config

  • config.local.js 【本地開發(fā)配置】

  • dist 【構(gòu)建目錄 掛載資源目錄】

  • server 【服務(wù)端代碼】

  • controller 【控制器】

  • PC

  • route.js

  • core [核心代碼庫(kù)]

  • service [方法庫(kù)]

  • view [視圖]

  • PC [vue 構(gòu)建后文件]

  • tag.html [構(gòu)建后的模板]

  • tag.json [構(gòu)建后的 bundle]

  • manifest.json

  • apps.js [啟動(dòng)項(xiàng)]

在一開始設(shè)計(jì)的時(shí)候,客戶端代碼和服務(wù)端代碼放在同一個(gè) git 庫(kù)里面,client 里面是 vue 的代碼和 webpack 的打包邏輯。Server 里是服務(wù)端的代碼,用的是類 mvc 結(jié)構(gòu)。

Client 里面的 vue 的開發(fā)代碼,參照的就是 vue ssr 官方給的例子來(lái)做的,用的是 createBundleRender 方法

const {createBundleRenderer} = require(‘vue-server-renderer’)
const renderer = createBundleRenderer(serverBundle, {
… })

構(gòu)建配置也是用的推薦的配置(參考:https://ssr./zh/build-config.html)

簡(jiǎn)單來(lái)說(shuō),就是提供兩個(gè)入口,一個(gè) entry-client.js, 主要是客戶端的執(zhí)行入口, 打包出來(lái)的是客戶端的引用代碼集合(manifest),另外一個(gè)是 entry-server.js 打包出來(lái)的是服務(wù)端運(yùn)行的邏輯, 整合到了 bundle.json 里面。然后傳給上面的 createBundleRender 方法就可以了

對(duì)于 server 文件夾里面的邏輯就非常簡(jiǎn)單了,core 里面是啟動(dòng)項(xiàng)目的一些 express 的核心代碼 路由注冊(cè)什么的邏輯,值得一說(shuō)的是,這邊的路由,借鑒了 eggjs 的路由注冊(cè)方式,稍微做了一點(diǎn)修改,用的是配置化的方式
6a0285dae2a84af5bf0a4e94bc388551.png

配置優(yōu)于代碼,將訪問(wèn)地址和對(duì)應(yīng)的 controller 做了關(guān)聯(lián)。

這邊還有一個(gè) filter 其實(shí)就是在執(zhí)行 controller 之前 注冊(cè)進(jìn)一個(gè) middlewares 優(yōu)先執(zhí)行(其實(shí)這邊有點(diǎn)局限性,后處理沒(méi)法做)。

這邊我忽略了壓力測(cè)試,壓力測(cè)試我后面再說(shuō)吧。

上線部署

上線部署用的是 docker 來(lái)部署的,配置是 1C 4G 的配置,用了兩個(gè)實(shí)例來(lái)運(yùn)行,(之前的構(gòu)建鏡像邏輯什么的 就不具體介紹了)

上線之后 每天的訪問(wèn)量大概在 100W 左右,服務(wù)表現(xiàn)挺穩(wěn)定,期間出現(xiàn)了一個(gè) bug, 就是 這邊有一個(gè)狀態(tài)與用戶的登陸狀態(tài)有關(guān),所以在服務(wù)端請(qǐng)求接口的時(shí)候,需要帶上 cookie 去請(qǐng)求,當(dāng)時(shí)忘記加了 后來(lái)加上,發(fā)現(xiàn)這個(gè)有點(diǎn)弊端比較麻煩。

需要在調(diào)用 vuesssr 的時(shí)候帶在 context 里面,然后 asyncData 方法里面都要一層一層的傳遞,最后在 action 里面拿到,帶給 api。

這時(shí)候 我們?cè)賮?lái)看下 tag 頁(yè)。
81decf669c3f43f6ae0067e39b6f8aec.png

重構(gòu)

其實(shí)也沒(méi)過(guò)多久,大概三個(gè)月吧,node 的版本漲的很快,在 7.6 版本之后,node 就支持了 async/await 語(yǔ)法糖,不需要再用 yield 和 * 函數(shù)了,那么 無(wú)疑 koa 是對(duì)于 await/async 支持最好的,我們果斷放棄了 express, 選擇了 koa2 進(jìn)行重構(gòu)。

其實(shí)不單單是 koa2 對(duì)于 async 的支持,另外一個(gè)原因在于,我們 koa 是洋蔥式的執(zhí)行方式,這樣就解決了上面我說(shuō)的,只有 controller 的前處理,沒(méi)有后處理,這樣子我就可以很方便的去執(zhí)行前后處理。Koa 的執(zhí)行效率也要好于 express.

上面我說(shuō)過(guò),選擇 vue 對(duì)后面重構(gòu)埋下了一個(gè)伏筆就在這里

首先,我給項(xiàng)目接入了配置中心,配置中心是干嘛用的呢? 用來(lái)記錄腳本的版本號(hào),這樣子我就可以很輕松的通過(guò)配置中心來(lái)控制前端頁(yè)面使用什么版本的腳本。而不用因?yàn)楦牧藗€(gè)腳本的版本號(hào),就需要進(jìn)行一次服務(wù)的重啟更新。

然后,我對(duì) vue 的打包組件進(jìn)行了魔改,將他打包出來(lái)的文件帶上了對(duì)應(yīng)的版本號(hào)(版本號(hào)為 hash 值)。

這樣子我就可以通過(guò)配置中心來(lái)控制,到底我需要使用什么版本的 vue 構(gòu)建產(chǎn)物,vue 前端邏輯更新了,我也只需要通過(guò)配置中心去分發(fā)給服務(wù)端,而不需要重啟服務(wù)了。一舉兩得。
702643634256461098f654ecdf3750b2.png

圖中 conf 就是配置中心,我們的 server 會(huì)與 conf 進(jìn)行一個(gè)長(zhǎng)連接,如果 conf 中的配置更新了,就會(huì)通知到服務(wù),然后服務(wù)去拉去新的 bundle 和 manifest 來(lái)進(jìn)行渲染。Ok 很棒

全民 SSR

重構(gòu)完,那么再接入一個(gè)項(xiàng)目試試吧

首頁(yè),好,就首頁(yè)吧

首頁(yè)跟 tag 頁(yè)

其實(shí)也都差不多,沒(méi)有什么特別的地方,唯一不同的就是 量比較大,可能一天有 1000W 的訪問(wèn)量左右。那么我們就在 CDN 上面加上一層緩存,然后在我們服務(wù)上面也加上一層緩存。破費(fèi)(perfect)!~

服務(wù)端的緩存是通過(guò)文件落地來(lái)的,就是在第一個(gè)請(qǐng)求進(jìn)來(lái)的時(shí)候 在渲染完成之后,寫一個(gè)文件到本地,然后下次訪問(wèn)的時(shí)候就可以直接用這個(gè)丟這個(gè)本地文件出去,不用再次渲染了,然后通過(guò)過(guò)期時(shí)間去控制。

這里發(fā)現(xiàn)了一個(gè)問(wèn)題,就是每次更新 我都會(huì)將 tag 和 index 都進(jìn)行打包,而我需要的是對(duì)項(xiàng)目進(jìn)行單獨(dú)的打包,單獨(dú)的更新,能不能通過(guò)參數(shù)來(lái)控制我打包哪個(gè)呢,可以啊, 首先先把 webpack.config.js 重寫,公用部分整合,然后私有的分開寫成多個(gè),通過(guò) package.json 里面來(lái)多配置幾個(gè) script 就好啦

這樣子每次更新項(xiàng)目的時(shí)候,我就只需要打包對(duì)應(yīng)的項(xiàng)目就可以了,不會(huì)因?yàn)轫?xiàng)目接入了很多之后,打包和開發(fā)時(shí)候的熱加載變得很慢很慢。

由于接入了兩層緩存,首頁(yè)上線的時(shí)候,我們把服務(wù)從 2 個(gè) docker 實(shí)例 擴(kuò)容到了 6 個(gè)(docker 擴(kuò)容真方便),得益于緩存的優(yōu)勢(shì),服務(wù)并沒(méi)有什么壓力

當(dāng)然 首頁(yè)不可能像說(shuō)的那樣,這么隨便就上線了,需要有降級(jí)方案,那么降級(jí)方案得益于 vue 的強(qiáng)大了.

Vue 會(huì)在瀏覽器端檢驗(yàn)(data-server-render=true),是否服務(wù)端渲染了,如果服務(wù)端沒(méi)有渲染,那么客戶端會(huì)再執(zhí)行一次邏輯進(jìn)行渲染。這樣子我們只要再打包的時(shí)候,將原本客戶端渲染的那個(gè) index.html 保留就可以拉,當(dāng)然別忘了,再客戶端執(zhí)行的時(shí)候也要運(yùn)行一下 asyncData 里面的方法,不然會(huì)缺少數(shù)據(jù)哦。So easy~

接下來(lái) 一級(jí)分區(qū) 二級(jí)分區(qū)也分別都接入了,中間也遇到了一些問(wèn)題,不過(guò)最后都順利的解決了,后面有機(jī)會(huì)我再寫一篇文章來(lái)說(shuō)一下其中遇到的問(wèn)題。

再次重構(gòu)

我們的項(xiàng)目在有序的進(jìn)行著從原本靜態(tài)頁(yè) 客戶端渲染,往服務(wù)端渲染遷移的同時(shí),我們也在公司內(nèi)部進(jìn)行這推廣,有幾個(gè)兄弟部門也遇到了我們之前的 seo 的問(wèn)題,或者是希望首屏更快等,所以很愿意使用我們已經(jīng)造好的輪子??墒俏覀兊捻?xiàng)目暫時(shí)并不具有推廣性,如果兄弟部門要使用,只有把我們的庫(kù)拷貝過(guò)去,然后把業(yè)務(wù)邏輯刪減掉,再加上自己的邏輯,成本很高,而且我們這邊一旦更新了什么,他們都需要手動(dòng)去同步,就很麻煩。

我們花了一點(diǎn)時(shí)間,首先,core 核心庫(kù)抽離出來(lái),并且和日志中心的連接方法、配置中心的連接方法等一些公用方法一起,做成一個(gè) npm 包發(fā)布到公司內(nèi)部的 npm 源上面,然后將 client 從庫(kù)里面獨(dú)立出來(lái),變成前端庫(kù),加上一個(gè)簡(jiǎn)單的 server.js,可以獨(dú)立于 server 進(jìn)行開發(fā),而不用在開發(fā)的時(shí)候過(guò)分依賴 node server. 并且得益于配置中心,我們可以將項(xiàng)目分的很散,但是最終又通過(guò)配置中心,集中到同一個(gè)服務(wù)上,又回到了前后端分離上面,但是不止于前后端分離,前端獨(dú)立開發(fā)的同事,還帶上了服務(wù)端渲染,一舉兩得。設(shè)計(jì)架構(gòu)如圖:
8425c0dc0bea47369df1898b6746c785.png

順帶,我們開發(fā)了兩個(gè)腳手架,可以很方便的創(chuàng)建項(xiàng)目,并且加好 webpack 的配置和 package.json 的配置

這樣子拆分之后,項(xiàng)目就變得很清真,前端開發(fā)前端 vue 項(xiàng)目,服務(wù)端有 npm 包可供大家使用,升級(jí)和維護(hù)都很方便,node 服務(wù)也不需要一直去重啟,通過(guò)配置即可更新邏輯,熱更新。

做完之后,很多兄弟部門也都開始了接入。

壓力測(cè)試

因?yàn)槊總€(gè)公司的情況都不一樣,使用組件緩存,頁(yè)面緩存等等方式,都可以達(dá)到優(yōu)化的目的,使其可以達(dá)到能承載項(xiàng)目流量的標(biāo)準(zhǔn),我這邊說(shuō)的情況是沒(méi)有任何緩存的情況下的壓測(cè)結(jié)果。

我們做過(guò)幾次不同層面的壓測(cè),畢竟性能需要達(dá)到要求才行,記得當(dāng)時(shí)出版打樣上線的時(shí)候,VUE 使用的版本是 2.3.x 性能不是很好,因?yàn)?VUE 是基于虛擬 DOM(VNODE)來(lái)實(shí)現(xiàn)的,是 CPU 密集型的項(xiàng)目,所以在壓測(cè)的時(shí)候,CPU 很快就達(dá)到了 100%,TPS 很低,所以我們對(duì)頁(yè)面加了緩存,像首頁(yè)這種 P0 級(jí)頁(yè)面都加兩層緩存,后來(lái) VUE 更新到了 2.4.x 性能變好了許多,但是 CPU 始終是一個(gè)瓶頸。如果項(xiàng)目復(fù)雜,組建嵌套很多的話,1C4G 的服務(wù)器,CPU 打滿也就 40 到 50 的 TPS 就封頂了,再上去,用戶等待時(shí)間就會(huì)呈指數(shù)式上升。

我看過(guò)很多文章,拿 vuessr 和字符串模板進(jìn)行比較的文檔,但是他們的比較 demo 都很簡(jiǎn)單,vue 里面都沒(méi)有組件嵌套,性能相比可能確實(shí)差不多,但是頁(yè)面復(fù)雜度上升,組件嵌套越多,那么 vuessr 的性能就沒(méi)法再跟字符串模板進(jìn)行比較了。

舉個(gè)例子,我們首頁(yè)一二級(jí)分區(qū)每天打到 node 上面的量跟文章的量差不多,但是文章就用了首頁(yè)三分之一的機(jī)器,機(jī)器的 cpu 和內(nèi)存使用量差不多,因?yàn)槲恼马?xiàng)目用的是字符串模板。

總結(jié)

在整個(gè)的過(guò)程中,需要前端同學(xué),后端同學(xué)的通力配合才行,后端 api 的同學(xué)需要將原本直接結(jié)合模板出數(shù)據(jù)的方法全部改成 api 接口,這是前后端分離的基礎(chǔ)。至于基礎(chǔ)建設(shè),可以慢慢發(fā)展來(lái)完善,就像一開始我們構(gòu)建的時(shí)候,構(gòu)建出來(lái)的配置文件的版本號(hào)都是需要手動(dòng)去配置到配置中心的,這很耗時(shí),而且容易出錯(cuò),慢慢的,配置中心開放出了 api 接口,我們接入就很方便了,順利的實(shí)現(xiàn)了配置同步的自動(dòng)化,只要上線的時(shí)候點(diǎn)一下發(fā)布就好了。

在用 node 做中間層的過(guò)程中,也有遇到內(nèi)存泄漏,性能瓶頸等問(wèn)題,后面有機(jī)會(huì),再寫篇文章介紹吧。在這一年中,B 站發(fā)展的很快,前端也有意識(shí)的去在意前端性能,讓頁(yè)面更好,更快。

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

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

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多