原文地址:https://zhuanlan.zhihu.com/p/32487533

2017年即将过去了,总结一下B站的前端进阶之路。

过去的开发模式中,我们采用了以后端为主的 MVC 架构方式。具体来说,每次项目评审后,前后端会先一起约定好接口,之后分别进行开发,开发完,前端需要把页面提供给后端,后端配置上数据,然后返回出来。正式基于这样的开发模式,导致了总工作量的增加,同时沟通和联调成本的消耗也十分显著。

前后端分离

为了摆脱这种前后端过分依赖的情况,(其实前端也不想每次修改或者发布都要后端这边发布,后端也不想每次前端只改个标题,都要发布一下,影响服务的稳定性),那么先从前后端分离开始吧~

前后端分离,最基本的两种模式,有中间层和没有中间层。

第一种,没有web中间层就很简单,提供一个html模板放到静态资源机上面,html模板里面引用了所需的js和css,访问页面的时候 把这个静态模板返回给用户,然后执行js 在浏览器端通过ajax请求api拿到数据,渲染页面。

(前后端分离)

第二种,有node中间层,随着2009年,Node的横空出世,把前端慢慢的推向了后端,有了node之后,JavaScript可以做更多的事情。

B站,一开始做前后端分离的时候,也确实按照第一种方式去做的,现在还有一些页面仍然是这种模式,例如:https://www.bilibili.com/account/history (可查看网页源代码)。对于不需要seo的页面来说,是一个不错的方式。前端开发完成之后,通过webpack打包出对应的js和css 上传到cdn上面,然后将webpack打包出来的 引用了对应的资源的html文件 上传到一台专门的静态机上面,然后运维配置路由 将页面流量导过去就好了。后端的同学只需要提供对应的api接口就可以。前后端分开维护,自己按照自己的节奏走,降低了页面与服务的耦合度

这种方式确实是一种很快能够进行前后端分离的方法。我们花了一段时间,在pc端使用vue 进行重构,移动端H5端 用react进行了重构。 进度很快,但是也慢慢展现出了弊端。

首屏的时候,因为他要等待资源加载完成,然后再进行渲染,会导致了首屏有白屏,如果是单页面还好,如果是spa应用 那么 他的加载时间就会变得很长,白屏时间会很影响用户体验,再有就是由于国内的搜索公司 对于spa 应用没有很好的兼容,导致了客户端渲染会对seo非常的不友好,有seo 需求的页面就很迫切的需要服务端渲染。

(B站的首页,右边模块做了服务端渲染,左边模块没有做服务端渲染)

那么,依赖node 进行服务端渲染就被提上了日程。

选型

首先进行node 框架的选型,市面上主流框架有三种,hapi express koa ,还有一些是经过一些封装和定制的框架,例如:eggjs等。

一开始我就把eggjs 排除在外了,第一因为eggjs,的功能很强大,有很多功能,多到有些根本用不着,从而导致了他会重 不轻量级,第二,eggjs对于我来说是个黑盒,如果有什么问题,我解决起来将会花费很长的时间。(但是有很多地方 我还是借鉴了eggjs的,毕竟 很强大)

然后剩下的三种框架,express的使用相对简单,文档也比较多,比较全面,所以我就选择了express(后来还是重构掉了 = =!)

然后是前端框架的选型 因为前端框架主流的有很多,ng r v 等等,我站在用的是react和vue, 他们有个优势就是可以进行前后端同构,一样的逻辑不用写两份,很棒。

(同构逻辑大概如此吧)

由于之前前后端分离的时候,pc上面已经再用vue 进行了重构,所以自然,这次服务端渲染也建立在vue上面 用的是vue ssr (这也为我后面的一个想法埋下了伏笔)

首先 我们选择一个简单的页面来做打样,就用tag页吧(被神选中的孩子:https://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, {
... })

构建配置也是用的推荐的配置(参考:https://ssr.vuejs.org/zh/build-config.html)

简单来说,就是提供两个入口,一个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页。

ssr html

(不错 把数据都带上了)

重构

其实也没过多久,大概三个月吧,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 页

其实也都差不多,没有什么特别的地方,唯一不同的就是 量比较大,可能一天有1000W的访问量左右。那么我们就在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站发展的很快,前端也有意识的去在意前端性能,让页面更好,更快。

哔哩哔哩(B站)的前端之路相关推荐

  1. b站 前端构架_技术干货:哔哩哔哩(B站)功能框架图 ——以B站为例分析面对秋招必须要掌握的前后端...

    本次夏令营知了堂项目经理以B站为原型,带着大家熟悉了软件的开发流程及还原了部分功能模块.现在就将B站功能架构图及前后端技术栈给大家.同时从以B站技术为例给大家分析作为应届毕业生,面对秋季校招时必须要掌 ...

  2. 哔哩哔哩2018校招前端笔试

    前言 前几日,哔哩哔哩在电子科大清水河校区举行了校招宣讲会.B站不用多说,中国最大的同性交友网站,不去工作也能去看看. 当晚便进行了部分笔试,下面是我参加的前端笔试试题和我自己的解答,分享给大家. 博 ...

  3. android442电视怎么投屏,哔哩哔哩怎么投屏电视? b站投屏的方法

    B站看视频想要投放到电视中观看,该怎么投屏呢?下面我们就来看看详细的教程. 软件名称:Bilibili客户端( B站弹幕网安卓版) v5.20.1 for android(安卓)版软件大小:47.8M ...

  4. b站电脑客户端_B站(哔哩哔哩) 视频批量下载工具#电脑版##更新

    长按识别下方二维码关注 B站(哔哩哔哩) 视频下载工具#电脑版 软件介绍 这是一款可以在电脑端下载B站视频的工具 相信看过我文章的都知道以前已经推荐过一款 >>点我跳转<< 但 ...

  5. HTML高仿哔哩哔哩(B站)视频网站整站模板

    简介: 100%高仿哔哩哔哩(B站)视频网站模板,包含首页.列表和内容页整站模板. 网盘下载地址: http://kekewangLuo.net/D94pktwZmYc0 图片:

  6. B站陈睿:70 后也正在爱上哔哩哔哩

    5月28日,第七届中国网络视听大会在成都正式召开.哔哩哔哩董事长兼CEO陈睿出席并发表题为<守正创新,激发内容新活力>的主题演讲,他表示在过去的三年当中,更广大年龄层和区域的用户正在爱上哔 ...

  7. 抢注“哔哩哔哩”商标卖成人用品?A站回应:不符合价值观 已申请注销

    A站.B站是宿敌吗?不知道是不是,但两家的摩擦从来没少过,最近一次交锋是A站被曝抢注"哔哩哔哩"商标一事. A站抢注B站商标,听起来就很有故事性.据公开资料显示,"哔哩哔 ...

  8. 【报告分享】2021B站创作者生态报告-哔哩哔哩(附下载)

    摘要:B站创作者群体迅速壮大,月均活跃UP主已达270万,同比增长61%.哪些人在B站搞创作?报告显示,24-30岁人群是B站创作者的主力,而31岁以上创作者人数增速显著,高达80%.无论是大学新生. ...

  9. 一场胆战心惊的B站面试,哔哩哔哩也太难进了

    此次哔哩哔哩Java开发面试之旅可谓惊险,不过通过对大部分面试题套路的掌握,不出意外还是拿下了,下面我们来看看这些题是不是常见的不能再常见的了.这些面试题看了就能面上?当然不是,只是通过这些题让自己知 ...

最新文章

  1. A16Z内部万字报告:人类与AI结合的最佳形态
  2. Python 技术篇-1行代码实现语音识别,speech库快速实现简单的语音对话
  3. Android按比例布局之layout_weight和weightSum的使用
  4. Cloud for Customer custom BO创建时间随着行项目数量增加而增加的关系
  5. android richtext显示html,【报Bug】关于rich-text显示html 的问题
  6. Fastjson反序列化泛型类型时候的一个问题
  7. 因果推断——借微软EconML测试用DML和deepIV进行反事实预测实验(二十五)
  8. mysql_连接查询
  9. 三星香港 android8.0,三星A8 Star官方港版安卓8.0固件刷机包:TGY-G8850ZHS3ASD1
  10. tracert和traceroute区别在哪?
  11. OCCT命令集1(速查笔记)
  12. 【Android工具】音频频率发生器,声音测试,音响测试,各种频率声音合成工具...
  13. JSP概述——什么是JSP、JSP运行原理
  14. Android Studio 学习记录-图形定制
  15. 使用muscle进行多序列比对
  16. 动态数组的使用之char *res=new char(strlen(src)+1)
  17. 新氧打出精细化运营组合拳 激活医美发展新动力
  18. 运行官方byfn.sh跑通网络
  19. 用计算机处理医学信息处理,医学信息处理
  20. Word文档如何进行分栏设置

热门文章

  1. 初等三四阶行列式计算器
  2. 勾股定理用计算机怎用,勾股定理公式计算器
  3. python勾股定理中三个数的关系是、找出三十以内的_从勾股定理到余弦相似度-程序员的数学基础...
  4. 云计算导论课后习题第一章
  5. 如何轻松搞定 笔记本搜不到WIFI信号问题
  6. 由电影Matrix(骇客帝国)联想到的操作系统知识
  7. 【夏目鬼鬼分享】SpringBoot2.0整合mybatis
  8. 极客时间 算法训练营 第一周总结
  9. sql函数能判断字段是否包含英文
  10. 域服务器统一修改ie首页,通过AD域策略对IE做统一设定