去哪儿网前后端分离实践

作者|兴百放

编辑|覃云

来源 | 前端之巅

本文是对去哪儿网前端业务方向负责人兴百放在今年 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 可以实时查看所有错误信息。

去哪儿网前后端分离实践

相关文章