LOFTER前后端分离架构设计

达芬奇密码2018-08-06 09:36

首先为什么要做前后端分离呢?

个人觉得最最重要的原因一定是具体项目需求。例如有些项目,前端变化远远比后端频繁。例如活动项目,在活动的各个阶段,交互和视觉会经常修改。前端的独立部署上线不需要依赖后端,比较灵活可控,并且也不会影响后端服务的稳定。

当然还有其它一些通用的好处:

  1. 前端和后端的分离,表现和数据分离,项目架构、组织结构更佳清晰。
  2. 提高前端的开发效率。
  3. 前端不必纠结在如何启动运行后端项目,脱离各种后端复杂的配置。

前后端到底要分离到什么程度呢?是不是前端后端解藕程度一定是越大越好呢?这个问题当然还是要根据具体的项目而定。

整体设计

整体架构设计如下图所示:

详细设计

Node服务器基于express框架搭建,主要负责页面路由,登入和权限判断等,静态资源托管(CDN回源)等功能;Node服务和Java服务采用http协议进行通信,例如鉴权接口,登入判断接口等;前端页面采用restful规范的ajax接口直接向后端Java服务请求数据。分离的前端项目独立运行和部署。

Nginx配置

由于条件限制,前端服务和后端服务没有独立的域名,因此用相同的域名,通过Nginx代理来实现url分离。缺点是url划分不够清晰;优点是不需要额外处理跨域。

以Lofter活动项目为例,Nginx配置如下,url路径中以/spread/开始的预留给前端项目使用。

location / {
    proxy_pass http://lofter;
}

location /spread/ {
    proxy_pass http://lofter_spread;
}

页面路由和静态资源url举例如下,将会代理到Node服务

http://www.lofter.com/spread/html/photography

http://www.lofter.com/spread/html/static/photography/photography.js

向后端获取数据的ajax请求url举例如下,将会代理到Java服务

http://www.lofter.com/act/photography2018

流程说明

页面请求将被代理到Node服务,如果该页面需要登入或者鉴权,Node服务会先进行登入判断及鉴权,如果已经登入并且有权限,直接返回页面模版;如果未登入,返回302重定向,通知浏览器重定向到主站登入服务。

Node服务的登入判断及鉴权

如上流程所诉,Node服务如何进行用户是否已经登入的判断?有以下2个可选方案:

  1. Java服务维护已登入用户缓存,Node直接根据用户cookie查询该缓存来判断用户是否已经登入。
  2. Java服务为Node提供接口,Node通过调用后端接口来判断。

其实类似的问题都可以归纳为以下选择:

  1. Node直接去读去数据库和缓存,甚至还可以给Node权限去写数据库和缓存
  2. Node服务完全不对数据库缓存进行操作,都通过调用Java接口实现

当然不同的项目完全可以有不同的选择。方案二的优点是Node服务避免了复杂的数据库和缓存读取操作,而且在这个场景下后端也不需要额外维护已登入用户缓存。缺点是Node接收到每个需要登入判断的页面请求时,都需要请求Java服务,会给后端服务带来一定压力。但前后端分离的Node服务应该尽量避免复杂的底层业务逻辑(毕竟是前端同学在维护),同时考虑到现在服务器的高性能,个人觉得在满足分离的需求上,应该尽量简化Node服务,比较推荐方案一。

然而在Lofter分离的项目中,登入判断则选择了另一种方式。Node服务和Java服务采用同一种登入cookie的加密和解密算法,Node服务只要将cookie解密,如果解密后的cookie符合约定规则,则判断为用户已经登入,并没有调用后端接口验证,这是最简单的方式,但是却有cookie被伪造的风险。为什么会选择这种方式呢?

在分离后的项目中,前端模版并不是Node服务器同步地去渲染数据,而是之后由前端页面直接向后端Java服务发送ajax请求异步获取,在异步接口中,后端会进一步进行登入判断,就算用户伪造cookie,也只会返回页面得不到数据。所以以上直接由Node服务器验证cookie的方式可以满足需求,并且减少了和后端Java服务的交互,减轻服务器端压力。

登入服务

如上流程所述,如果未登入,Node服务会返回重定向,让浏览器302到“主站登入服务”。而由架构图所示,“主站登入服务”部署在后端Java服务中,不是说前后端分离吗?为什么这里没有分离呢?这就回到了最开始的那个问题,前后端到底要分离到什么程度呢?真的要做到完全解藕才行吗?“登入服务”将是一个很好的例子。

首先,在整个Lofter中,所有的项目如Lofter活动项目、Lofter管理后台、Lofter主站PC/WAP都是共用一个登入服务的,不同项目的登入服务是统一的,所以直接把登入服务放在主站或者以微服务的形式提供就可以了。

其次,就算各个项目的登入服务是独立的,类似的基础服务其实也是可以考虑在后端Java服务中进行实现,因为登入服务是一个十分稳定的基础服务,上线之后改动的可能性很小。当然这也是各有优劣。

在Lofter的前后端分离架构中,Node服务从登入服务中解脱出来,避免了登入过程中和后端的各种交互以及写登入cookie等操作。

Node服务部署

Node是基于单线程非阻塞的异步模型,当有CPU密集型处理时,Node的劣势就会显现出来。而对于一个面向用户的线上项目,还是很有必要实现Node的多核部署的。

PM2是一个很好的选择,PM2是一个带有负载均衡功能的Node应用的进程管理器,实现了Node服务的多核多线程部署,最大程度地利用好了服务器资源,提高Node服务器的性能和响应速度。

在ndp部署平台上也有支持PM2的node模版。

ndp部署Node服务遇到的问题

Lofter项目的ndp上的模版使用的是node(pm2)-v8.9.0,在使用过程中遇到过以下两个问题:

  • 基于pm2的模版,执行启动脚本的目录并不在项目的根目录下,在.pm2目录下,webpack打包的配置文件中的路径需要用绝对路径,不能用相对路径。

  • 删除发布实例,重新新增后,重新发布项目,发现之前的发布实例的进程也会被启动。 如果确实需要删除发布实例并重新新增的话,需要登入服务器,将之前发布实例产生的目录手动删除,并且将部署时产生的目录(本项目使用的模版产生的目录是:.pm2和.ndp)删除。

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者陈瑶授权发布。