微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

去哪儿网前后端分离实践

2018-08-10 17:42 兴百放
作者|兴百放
编辑|覃云
本文是对去哪儿网前端业务方向负责人兴百放在今年 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 可以实时查看所有错误信息。

 课程推荐


 
前端之巅 更多文章 Angular的这10个特性,你可能不知道 2018年,你需要了解这些React Native组件库 以Vue为例,解释JavaScript的反应性 我们用Flutter重写了一个React Native应用 如何构建无渲染Vue组件?
猜您喜欢 超融合基础架构概念解析 汇通达研发管理团队走进途牛 圣诞快乐! 从Java到Golang快速入门 TLS协议分析 (二) 架构总览