通过 React 在 Node 端做页面直出时,服务器负载是我们首先需要去直面的问题,先看一下流程图,接下来会详细解读
说明
首先我们将访问流量归为两类,第一类是游客,包括爬虫,第二类是会员即登录用户,其主要通过请求头的 cookie 信息来进行区分。接着我们将渲染类型也归为两类,第一类是首屏直出(服务端渲染),第二类是前端路由交互(客户端渲染),其不同的地方在于渲染页面时获取数据的方式。基于上面的类型区分进行优化,我们将 node 承载的服务变得更轻量。
所有访问的流量通过域名解析都会命中 Nginx 服务,通过 Nginx 的负载均衡将流量导入 Varnish 服务,Varnish 是一款高性能的、开源的反向代理服务器和缓存服务器,具有更好的稳定性、更快的访问速度、更多的并发连接支持数,可以通过管理端口管理缓存等优势。接下来我们需要在 Varnish 上通过请求头的 cookie 信息来区分流量类型。
游客
针对游客用户,将根据请求地址找到缓存 html 页面(完整的),直接响应到客户端;如果没有找到对应的缓存页面,将会命中 Node 服务器,由 Node 服务处理并返回对应的 html 页面,同时 Varnish 也会将其缓存下来,下次访问将直接命中缓存。
会员
针对会员,Varnish 将不再进行处理,主要原因是会员访问时需要对用户信息进行检查等处理,那么接下来就被分配到了 Node 服务器进行处理了。
总结
由于不同的登录用户渲染的页面是不一致的,如果针对每个用户都去做缓存,数据量会很大,而且会涉及到服务端 cookie、session 信息的管理,为了让 Node 做的事情更轻量,于是我们将在 Node 端不处理任何接口的请求,利用默认数据(redux 的 store 里面默认的 state 值)渲染出页面,也就是所谓的空壳页面,同时我们会根据 react-router 的 path 规则作为 key 将该页面缓存在 Node 服务的内存中。
至此用户访问在服务端执行的流程已经完成,细心的小伙伴肯定会发现会员用户看到的将是一个空壳页面(由默认数据渲染出来的页面结构),我们自然不能就这么结束了,那么就会引出了对于渲染类型中客户端渲染的介绍。
从上面的介绍中,我们可以看到服务端渲染已经完成了首屏直出的需求,通过缓存也能实现更快的响应,接下来还遗留的问题就是会员访问的空壳页面。
当客户端响应到 html 文档,js 会判断该请求地址响应的页面是否完整,如果不完整,会向服务器发送 fetch 请求重新将该页面渲染完整,这也就是所谓的客户端渲染,这只是当会员(登录用户)访问时,不会影响 SEO(爬虫)对于网站的收录情况。
看到这里,相信你已经大致了解了前后端分离之用户访问时的页面直出处理,那么同时还有一个重要的概念也要一起讲述,那就是数据(接口)是如何处理(优化)的。
自然对于接口处理也要分两方面进行讨论,那就是服务端(Node),客户端(fetch)。
当游客用户第一次访问时,Node 端将直面渲染,同时 Node 端需要提前准备好数据(待渲染),这个阶段的数据可以分为几种方式获取。第一种是通过数据库获取数据,由于目标是轻量的 Node 服务,弃之;第二种是通过 http 协议获取数据,由于存在 DNS 域名解析的时间消耗,弃之;第三种是通过 apiGateWay 获取数据,这是通过 ip 直接走内网服务,速度会较快,但是还是会存在 tcp/ip 协议三次握手的时间消耗的问题,暂留;第四种是通过 thrift,它实现了系统内不同语言之间的 RPC 通信,速度会更快,但是开发成本略高,暂留。所以经过研究,我们选择了第三种(apiGateWay)和第四种(thrift),可以通过配置进行自由切换。
而当会员用户访问时,由于是空壳页面,就会在客户端通过 fetch(类似 ajax 方式)获取后端接口数据进行页面的渲染了。
看到这里,我们不得不提一下体验这个词,虽然我们首屏直出,优化了页面访问速度,但是对于登录用户的体验上还是会大打折扣的,前期我们使用了骨架图来优化客户端渲染时的交互体验,接下来会优化返回的空壳页面。主要是分析页面中和用户无关的部分也一并缓存下来,在客户端只需要处理和用户相关的组件,同时会借助 Varnish 的 esi 标签来达到该效果